2017-02-07 47 views
6

chciałbym komponować operatora dodawania (+) aby funkcję tego typu:"łańcuch" operatora dodawania do funkcji, która akceptuje 3 argumenty?

Num a => a -> a -> a -> a 

podobnych, równowartość tej:

(\a b c -> a + b + c) 

ale bez konieczności uciekania się do lambda.


Próbowałem już

((+) . (+)) 

który Liczyłam do pracy, ale zaskakująco nie.

+0

Iirc, opanowanie wymaga, aby funkcja akceptowała tylko 1 argument. Binarni operatorzy biorą 2 – Carcigenicate

+0

Dlaczego nie po prostu spasować listę używając '+'? – Carcigenicate

+0

@Carcigenicate hmm, to dziwne. Nie spodziewałbym się, że tak będzie. Czy jest jednak jakiś sposób na zrobienie tego? - edit: a co spasować i jaka lista? – theonlygusti

Odpowiedz

11

http://pointfree.io podaje wersję bezwzrokową \a b c -> a + b + c jako ((+) .) . (+).

Nieformalnie kompozycja działa "intuicyjnie" tylko dla funkcji pierwszego rzędu, które nie pełnią funkcji jako argumenty ani funkcji zwracania jako wartości. (+) to funkcja wyższego rzędu; przyjmuje wartość typu Num a => a i zwraca funkcję typu Num a => a -> a. Podczas próby komponować funkcje wyższego rzędu w naiwny sposób, wynik nie jest to, czego można się spodziewać:

:t (+) . (+) 
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a 

Rozważmy definicje dwóch funkcji:

(+) :: Num z => z -> z -> z 
(.) :: (b -> c) -> (a -> b) -> (a -> c) 
f . g = \x -> f (g x) 

Następnie

(+) . (+) == (.) (+) (+) 
      == \x -> (+) ((+) x) 

Z powodu curry kończy się przekazywanie funkcji, a nie liczby, jako pierwszego argumentu pierwszego (+).


Jak więc dostać od h a b c = a + b + c do h = ((+) .) . (+)? Zacznij od przepisania wyrażenia infiksowego jako wyrażenia prefiksowego, wykorzystując fakt, że (+) jest skojarzony lewostronnie.

\a b c -> a + b + c 
    == \a b c -> ((+) a b) + c 
    == \a b c -> (+) ((+) a b) c 

Następnie na przemian stosujemy konwersję eta, aby wyeliminować argument i kompozycję, aby przenieść argument na pozycję, aby zostać wyeliminowanym. Starałem się być bardzo jednoznaczny w określaniu funkcji używanych do stosowania kompozycji.

 == \a b -> (+) ((+) a b)  -- eta conversion to eliminate c 
    == \a b -> (+) (((+) a) b) -- parentheses justified by currying 
    --   f  g   -- f = (+), g = ((+) a) 
    -- \a b -> f ( g b) 
    -- \a b -> (f . g) b  -- definition of (.) 
    == \a b -> ((+) . ((+) a)) b 
    == \a -> (+) . ((+) a)  -- eta conversion to eliminate b 
    == \a -> (.) (+) ((+) a)  -- prefix notation 
    == \a -> ((.) (+)) ((+) a) -- parentheses justified by currying 
    == \a -> ((+) .)((+) a)  -- back to a section of (.) 
    --   f  g  -- f = ((+) .), g = (+) 
    -- \a ->  f  (g a) 
    -- \a -> ( f . g) a  -- definition of (.) 
    == \a -> (((+) .) . (+)) a 
    == ((+) .) . (+)    -- eta conversion to eliminate a 
+0

Byłoby to przykładowe przeanalizować działanie '((+).). (+) '. –

6

Chociaż ta wprowadza trochę hałasu, można użyć uncurry :: (a -> b -> c) -> (a,b) -> c i curry :: ((a,b) -> c) -> a -> b -> c do tymczasowego magazynu argumenty drugiej Plus w jednej krotce:

curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a 

lub może bardziej semantycznie czytelny:

curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a 

uncurry w ten sposób przyjmuje funkcję (tutaj (+)) i przekształca ją w funkcję: uncurry (+) :: Num a => (a,a) -> a. W rezultacie przekształcono (+) w funkcję , która zajmuje krotkę.

Teraz możemy użyć (.) zrobić kompozycję z pierwszego (+):

(+) . uncurry (+) :: Num a => (a,a) -> (a -> a) 

Więc teraz mamy funkcję, która pobiera jeden argument (krotki (a,a)) i wywołuje funkcję, która pobiera a (the drugi operand pierwszego (+)) i oblicza sumę. Problem polega oczywiście na tym, że chcemy pozbyć się krotki. Możemy to zrobić, przekazując funkcję do curry. To przekształca funkcję krotki ((a,a) -> (a -> a)) w funkcję pobierającą oddzielnie argumenty (a -> (a -> (a -> a))).

4

Uwaga podpis operatora złożenie funkcji:

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

trwa 2 funkcje, z których każdy ma 1 argumentu, i zwraca do funkcji, które ma argument tego samego typu jak w drugiej funkcji i zwraca ten sam typ co pierwszy.

Twoja próba skomponowania dwóch + s nie zadziałała, ponieważ + przyjmuje 2 argumenty, więc bez jakiegoś hackish/creative workway, nie jest to możliwe.

W tym momencie powiedziałbym, że zmuszanie kompozycji, gdy nie pasuje do problemu, po prostu utrudni życie.

Jeśli chcesz, aby podsumować kilka numerów, można napisać funkcję, jak:

sum :: [Int] -> Int 
sum nums = foldl (+) 0 nums 

Albo, ponieważ nums pojawia się w tylnej części definicji, to może zostać usunięte całkowicie, dając „punkt- wolne”forma:

sum :: [Int] -> Int 
sum = foldl (+) 0 

zmniejsza/fałdy + na liście numerów. Jeśli nie korzystałeś jeszcze z fałd, zajrzyj do nich teraz. Są jednym z głównych sposobów na uzyskanie pętli w Haskell. Zasadniczo jest to "niejawna rekursja", gdy masz do czynienia z listami lub cokolwiek innego, co jest iterowalne.

Dzięki funkcji określonej powyżej, można go używać jak:

sum [1, 2 3, 4, 5] 
+0

Myślę, że powinieneś wpisać 'foldr (+) 0 nums' (z nawiasami) i' = 'zamiast' :: 'w przypisaniu. Zwykle jest również bardziej efektywne wykorzystanie pamięci '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '. –

+0

@WillemVanOnsem Whoops. Idk jak skończyłem używając dwukropków zamiast równych sobie. I dzięki, zmienię zakładkę. – Carcigenicate

+0

nadal musisz umieścić nawiasy na '(+)' w przeciwnym razie Haskell widzi to jako '(+) foldl 0' ... –

8

Trzeba ten dziwny operator (.).(.), który jest czasami określa jako .: (myślę o 3 kropki ...)

W ghci

Prelude> let (.:) = (.).(.) 
Prelude> let f = (+) .: (+) 
Prelude> f 1 2 3 
> 6 

Uwaga operator może być także określona jako <$$> = fmap . fmap.