2016-01-12 28 views
5

Próbuję zrozumieć funkcjonalne programowanie przez Haskella i mam tak dużo problemów z komponowaniem funkcji.Kompozycja Haskella z dwoma parametrami

Właściwie mam te dwie funkcje:

add:: Integer -> Integer -> Integer 
add x y = x + y 

sub:: Integer -> Integer -> Integer 
sub x y = x - y 

Chcę, aby móc je komponować. To nie ma sensu, ale jest to cel nauki.

Co próbowałem:

foo:: (Integer -> Integer) -> (Integer -> Integer) -> Integer 
foo = add . sub 

Co mam rozumieć:

Haskell wykorzystuje funkcje tylko jeden args, więc wracamy nową funkcję do wykonania po każdym wykonywanie funkcji.

Tak więc pierwszy Integer jest typem param, a drugi jest typem zwrotnym wygenerowanej funkcji, która musi dodać drugą liczbę.

Spowoduje to powrót inną funkcję (sub), który będzie sprawia, że ​​ten sam przepływ (powrót funkcji z params etc ...)

mam rację?

Oto mój rzeczywisty kod błędu:

src\Main.hs:23:7: 
    Couldn't match type `Integer' with `Integer -> Integer' 
    Expected type: Integer -> (Integer -> Integer) -> Integer 
     Actual type: Integer -> Integer -> Integer 
    In the first argument of `(.)', namely `add' 
    In the expression: add . sub 

src\Main.hs:23:13: 
    Couldn't match type `Integer -> Integer' with `Integer' 
    Expected type: (Integer -> Integer) -> Integer 
     Actual type: Integer -> Integer -> Integer 
    Probable cause: `sub' is applied to too few arguments 
    In the second argument of `(.)', namely `sub' 
    In the expression: add . sub 

Nie wiem co robię źle.

Czy możesz pomóc mi zrozumieć nieco więcej tego błędu, aby znaleźć rozwiązanie?

+5

Masz rację, że "nie ma sensu" - nie ma pojęcia, jak komponować funkcje binarne w matematyce.Jeśli chcesz, aby miało to sens, musisz najpierw zdefiniować, co oznacza "komponowanie", dodawanie i odejmowanie. – molbdnilo

+0

Twój typ dla foo mówi, że skład dodawania i odejmowania jest funkcją, która bierze dwie * funkcje * ('Integer -> Integer') i zwraca liczbę całkowitą. – molbdnilo

Odpowiedz

6

Biorąc pod uwagę funkcję

add :: Integer -> Integer -> Integer 

to warto pamiętać (jak wskazał siebie w Co zrozumiałem rozdział), które -> w podpisach typu współpracowników w prawo, tzn powyższego typu jest taka sama jak

add :: Integer -> (Integer -> Integer) 

teraz rozważyć rodzaj (.):

(.) :: (b -> c) -> (a -> b) -> a -> c 

Oznacza to, że w wyrażeniu

(.) add 

b w rodzaju (.) jest Integer i c odpowiada Integer -> Integer. Innym sposobem, aby napisać to

b ~ Integer 
c ~ Integer -> Integer 

Więc mamy

(.) add :: (a -> Integer) -> a -> (Integer -> Integer) 

Jeśli teraz zastosować (.) add do sub, zawiadomienia kompilatora że a -> Integer nie mogą być wykonane, aby dopasować Integer -> Integer -> Integer.

Podejrzewam, że prawdopodobnie chcesz skład wziąć trzy argumenty: dwa zastosować sub do, a wynik, który jest następnie - wraz z trzecim argumentem - przeszedł do add. Tak więc możliwe określenie, które komponuje dwie funkcje byłoby

foo :: (Integer -> Integer -> Integer) -> (Integer -> Integer -> Integer) -> Integer -> Integer -> Integer 
foo f g x y = f (g x y) y 

Na co warto, jest to związane z problemem: komponowanie funkcji dwóch argumentów z funkcją jeden argument, na przykład komponowanie

+0

Nieco inna definicja: 'foo f g x y = f (g x y) y' może zostać skrócony do' foo = liftM2 (.) '. –

4

Chcę móc je komponować. To nie ma sensu, ale jest to cel nauki.

To jest właściwie problem tutaj. Jak chcesz je skomponować? Daj nam rzucić okiem na kilka możliwych kompozycji:

foo x y = sub x (add x y)   -- x + y - x = y 
foo x y = sub y (add x y)   -- x + y - y = x 
foo x y = sub x (add y y)   -- 2 * y - x 
foo x y = sub y (add y y)   -- 2 * y - y = y 
foo x y = sub y (sub y (add x x)) -- 2 * x - 2 * y 

Mając na uwadze powyższe, niech nam zbadać typu błędu, sprawdzając typy od strony:

type I = Integer -- otherwise the lines are going to be very long 

(.)   :: (b -> c ) -> (a -> b ) -> a -> c 
add   :: I -> (I -> I) 
sub   ::     I -> (I -> I) 
--        ||||||||||||| 
(.) add  ::     (a -> I ) -> a -> (I -> I) 
--        ^^^^^^^^^^^^^ 

Jak widać, (.) add już mandatów, które druga funkcja może mieć tylko typ a -> Integer dla arbitralnego a. Ale typ sub to Integer -> (Integer -> Integer) (pamiętaj, (->) jest prawostronny).

Co możesz zrobić, aby to naprawić? Po pierwsze, pozwala nam kontrolować swój przewidzianego typu foo:

foo :: (Integer -> Integer) -> (Integer -> Integer) -> Integer 

to rzeczywiście bardzo interesujący rodzaj funkcji. W jaki sposób uzyskasz swój wynik? Jesteś po prostu mieć dwie funkcje w zasięgu ręki, ale bez wartości:

> foo f g = 

Problem ten można rozwiązać za pomocą stałego punktu jednej z funkcji, a następnie zastosować inne:

> let x = f x in g x 
> 
> example = foo (const 12) (+1) -- returns 13 

Ale to nie to, co miałeś na myśli, prawda? W tym momencie bardzo ważne jest, aby pomyśleć o semantyce kompozycji. Ponieważ nie są one jasne, nie możesz napisać ogólnego sposobu komponowania obu funkcji tutaj.

Jeśli jednak faktycznie oznaczało

foo :: Integer -> Integer -> Integer -> Integer 
foo x y z = add (sub x y) z 

to jest to możliwe z

foo = (add .) . sub 

od

(.) add  :: (a -> I) -> a -> (I -> I) 
(.) ((.) add) :: (a -> b -> Integer) -> a -> b -> Integer -> Integer 

Ale (add .) . sub nie jest naprawdę łatwe do oka więcej. Lepiej wypisz punktową definicję foo, jeśli ten rodzaj funkcji był twoim pierwotnym celem.