Haskell: comporre funzione con due argomenti mobile fallisce
-
30-09-2019 - |
Domanda
Sto cercando di comporre una funzione del tipo (Floating a) => a -> a -> a
con una funzione di tipo (Floating a) => a -> a
per ottenere una funzione di tipo (Floating a) => a -> a -> a
. Ho il seguente codice:
test1 :: (Floating a) => a -> a -> a
test1 x y = x
test2 :: (Floating a) => a -> a
test2 x = x
testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)
Tuttavia, quando compilo in GHCI, ottengo il seguente errore:
/path/test.hs:8:11:
Could not deduce (Floating (a -> a)) from the context (Floating a)
arising from a use of `test2'
at /path/test.hs:8:11-15
Possible fix:
add (Floating (a -> a)) to the context of
the type signature for `testBoth'
or add an instance declaration for (Floating (a -> a))
In the first argument of `(.)', namely `test2'
In the expression: test2 . test1
In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.
Si noti che la versione commentata-out di compilazioni testBoth
. La cosa strana è che se tolgo i vincoli (Floating a)
da tutte le firme di tipo o se cambio test1
a basta prendere x
invece di x
e y
, compila testBoth
.
Ho cercato StackOverflow, Haskell wiki, Google, ecc e non ha trovato nulla di una restrizione alla composizione funzione relative a questa situazione particolare. Qualcuno sa perché questo sta accadendo?
Soluzione
\x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1
Queste due cose non sono come ogni altro.
test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
Altri suggerimenti
Non Sei problema non ha nulla a che fare con Floating
, anche se il typeclass non rendere il vostro errore più difficile da capire. Prendere il sottostante Codice come esempio:
test1 :: Int -> Char -> Int
test1 = undefined
test2 :: Int -> Int
test2 x = undefined
testBoth = test2 . test1
Qual è il tipo di testBoth? Bene, prendiamo il tipo di (.) :: (b -> c) -> (a -> b) -> a -> c
e girare la manovella per ottenere:
-
b ~ Int
(l'argomento ditest2
unificato con il primo argomento di(.)
) -
c ~ Int
(il risultato ditest2
unificato con il risultato del primo argomento di(.)
) -
a ~ Int
(test1
argomento 1 unificato con argomento 2 di(.)
) -
b ~ Char -> Int
(risultato ditest1
unificato con argomento 2 di(.)
)
Ma aspettate! tale tipo variabile, 'b' (4 #, Char -> Int
), deve unificare con l'argomento di tipo test2
(1 #, Int
). Oh No!
Come si dovrebbe fare? Una soluzione corretta è:
testBoth x = test2 . test1 x
Ci sono altri modi, ma considero questo il più leggibile.
Edit: Allora, qual è stato l'errore cercando di dirti? E 'stato detto che unificando Floating a => a -> a
con Floating b => b
richiede un instance Floating (a -> a)
... mentre questo è vero, davvero non voleva GHC per cercare di trattare una funzione come un numero in virgola mobile.
Il tuo problema non ha nulla a che fare con Floating
, ma con il fatto che si vuole comporre una funzione con due argomenti e una funzione con un argomento in un modo che non TYPECHECK. Ti do un esempio in termini di una funzione composta reverse . foldr (:) []
.
reverse . foldr (:) []
ha il tipo [a] -> [a]
e funziona come previsto: restituisce una lista invertita (foldr (:) []
è essenzialmente id
per le liste)
Tuttavia reverse . foldr (:)
non digita controllo. Perché?
Quando tipi di corrispondenza per la composizione di funzione
Rivediamo alcuni tipi:
reverse :: [a] -> [a]
foldr (:) :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
typechecks reverse . foldr (:) []
, perché crea un'istanza (.)
a:
(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
In altre parole, nel tipo di annotazione per (.)
:
-
a
diventa[a]
-
b
diventa[a]
-
c
diventa[a]
Quindi reverse . foldr (:) []
ha il tipo [a] -> [a]
.
Quando i tipi non corrispondono per la composizione di funzione
reverse . foldr (:)
non digita controllo, però, perché:
foldr (:) :: [a] -> [a] -> [a]
Essendo il diritto operante di (.)
, sarebbe istanziare il tipo da a -> b
a [a] -> ([a] -> [a])
. Cioè, in:
(b -> c) -> (a -> b) -> a -> c
- tipo di variabile
a
sarebbe sostituito con[a]
- Inserisci
b
variabile sarebbe stato sostituito con[a] -> [a]
.
Se il tipo di foldr (:)
era a -> b
, il tipo di (. foldr (:))
sarebbe:
(b -> c) -> a -> c`
(foldr (:)
viene applicato come operante diritto di (.)
).
Ma perché il tipo di foldr (:)
è [a] -> ([a] -> [a])
, il tipo di (. foldr (:))
è:
(([a] -> [a]) -> c) -> [a] -> c
reverse . foldr (:)
non digitare controllo, perché reverse
ha il tipo [a] -> [a]
, non ([a] -> [a]) -> c
!
Owl operatore
Quando la gente prima imparare la funzione di composizione in Haskell, vengono a sapere che quando si ha l'ultimo argomento della funzione in più a destra del corpo della funzione, si può cadere sia da argomenti e dal corpo, la sostituzione o la parentesi (o dollaro-segni) con i puntini. In altre parole, le sottostanti 4 definizioni di funzione sono equivalente :
f a x xs = g ( h a ( i x xs))
f a x xs = g $ h a $ i x xs
f a x xs = g . h a . i x $ xs
f a x = g . h a . i x
Quindi, la gente ottiene un'intuizione che dice “ho appena rimuovere la variabile più a destra locale dal corpo e dagli argomenti”, ma questa intuizione è difettoso, perché una volta che è stato rimosso xs
,
f a x = g . h a . i x
f a = g . h a . i
non equivalente ! Si dovrebbe capire quando composizione typechecks funzione e quando non lo fa. Se quanto sopra 2 erano equivalenti, allora vorrebbe dire che il sotto 2 sono anche equivalenti:
f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs
che non ha senso, perché x
non è una funzione con xs
come parametro. x
è un parametro alla funzione i
, e xs
è un parametro alla funzione (i x)
.
C'è un trucco per fare una funzione con 2 parametri punto-libera. E questo è quello di utilizzare un operatore “civetta”:
f a x xs = g . h a . i x xs
f a = g . h a .: i
where (.:) = (.).(.)
Queste due definizioni di funzione sono equivalenti. Leggi più “civetta” operatore.
Bibliografia
programmazione Haskell diventa molto più facile e semplice, una volta capito le funzioni, i tipi di applicazioni, parziale e strigliare, composizione funzione e dollaro-operatore. Per inchiodare questi concetti, leggere le seguenti risposte StackOverflow:
- e la funzione di composizione
- funzioni di ordine superiore, currying e composizione funzione
- Haskell
-
const
-
const
,flip
e tipi -
curry
euncurry
Leggi anche: