Jak stworzyć wielowariadową funkcję Haskella?

Potrzebuję funkcji, która pobiera dowolną liczbę argumentów (wszystkich tego samego typu), robi coś z nimi, a następnie zwraca wynik. Lista argumentów jest niepraktyczna w moim konkretnym przypadku.

Kiedy przejrzałem biblioteki Haskella, zobaczyłem, że funkcja printf (z modułu Text.Printf) używa podobnej sztuczki. Niestety, nie mogłem zrozumieć tej magii patrząc na źródło.

Czy ktoś może wyjaśnić jak to osiągnąć, a przynajmniej jakieś strona www / papier/gdzie mogę znaleźć dobry opis do tego?

Motywacja:

Powód, dla którego tego potrzebuję, jest bardzo prosty. W szkole (klasa informatyczna), jesteśmy zobowiązani do napisania modułu, który jest w stanie" zapisać " wyrażenie matematyczne, wyrazić je jako ciąg znaków (poprzez napisanie instancji Num/Real/etc dla własnego typu danych) i wykonać na nim różne operacje.

Ten typ danych zawiera specjalny konstruktor zmiennej, który może być zastąpiony przez wartość lub cokolwiek przez określoną funkcję. Jednym z celów jest napisanie funkcji, która przyjmuje takie wyrażenie z pewną liczbą zmiennych (pary typu (Char,Rational)) i oblicza wynik wyrażenia. Powinniśmy przyjrzeć się, jak najlepiej wyrazić cel funkcji. (Mój pomysł: funkcja zwraca inną funkcję, która pobiera dokładnie tyle argumentów, ile var zdefiniowanych w funkcji-wydaje się być niemożliwe).

Author: Nicholas Montaño, 2010-08-12

5 answers

Kluczowymi punktami printf jest możliwość zwracania ciągu znaków lub funkcji. Skopiowane z http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html,

printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

A podstawową strukturą jaką możemy wyodrębnić jest

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r
variadicFunction reqArgs = variadicImpl reqArgs mempty

class VariadicReturnClass r where
   variadicImpl :: RequiredArgs -> AccumulatingType -> r

instance VariadicReturnClass ActualReturnType where
   variadicImpl reqArgs acc = constructActualResult reqArgs acc

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where
   variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc)

Na przykład:

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

Then we could use

*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0  :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59
 94
Author: kennytm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 13:07:18

Wiele osób mówi ci, jak tworzyć różne funkcje, ale myślę, że w tym przypadku lepiej będzie użyć listy typu [(Char, Rational)].

 10
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 13:56:14

W artykule wiki na temat funkcji zmiennych, Ten artykuł został odwołany. Przypuszczam, że to właśnie robi printf, ale ja też tego nie rozumiem. W każdym razie, to jest z pewnością przesada, zwłaszcza, że twoje argumenty są tego samego typu. Po prostu umieść je wszystkie na jednej liście. Do tego są dobre listy-arbiterowa liczba rzeczy tego samego typu. Dobrze, to nie jest zbyt piękne, ale nie będzie brzydsze niż pełna funkcja wielowariadyczna.

 7
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 12:05:18

Spojrzałem na przykład linkowany z artykułu, do którego odwoływał się delnan. Po wpatrzeniu się w niego przez chwilę, myślę, że w końcu zrozumiałem, co się dzieje.]}

Zaczyna się od tego typu klasy:

class BuildList a r  | r-> a where
    build' :: [a] -> a -> r

Ten bit po potoku (|) jest zależnością funkcjonalną. Mówi, że typ reprezentowany przez a może być określony przez typ reprezentowany przez r. Innymi słowy, nie można definiować dwóch wystąpień typeklasy BuildList za pomocą tego samego r (Typ return), ale inny a.

Przeskakując trochę do miejsca, w którym funkcja build' jest rzeczywiście używana:

> build True :: [Bool]

Ponieważ build jest wywołaniem build' z pustą listą jako pierwszym parametrem, jest to to samo co:

> build' [] True :: [Bool]

W tym przykładzie {[12] } wyraźnie zwraca listę. Ze względu na zależność funkcjonalną, możemy wiązać się tylko z tą instancją klasy typu BuildList:

instance BuildList a [a] where
    build' l x = reverse$ x:l

Całkiem proste. Drugi przykład jest bardziej interesujący. Rozszerzenie definicji build, staje się:

> build' [] True False :: [Bool]

Jaki jest typ build' w tym przypadku? Cóż, zasady pierwszeństwa Haskella oznaczają, że powyższe może być również napisane w ten sposób: {]}

> (build' [] True) False :: [Bool]

Teraz staje się jasne, że przekazujemy dwa parametry do build', a następnie stosujemy wynik tego wyrażenia do parametru o wartości 'False'. Innymi słowy, oczekuje się, że wyrażenie (build' [] True) zwróci funkcję typu Bool -> [Bool]. I to wiąże nas z drugim przykład typu BuildList:

instance BuildList a r => BuildList a (a->r) where
    build' l x y = build'(x:l) y

W tym inwokacji, l = [] i x = True i y = False, więc definicja rozszerza się do build' [True] False :: [Bool]. Ten podpis wiąże się z pierwszą instancją build', i jest dość oczywiste, dokąd zmierza.

 6
Author: Daniel Pratt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 13:44:21

Odpowiedź KennyTM jest świetna. Poniżej znajduje się przykład procesu exec sumOf 1 4 7 10 :: Integer, aby dać lepszą ilustrację.

sumOf 1 4 7 10
(( \ x -> ( sumOf . (x +) . toInteger ) 1 ) 4 7 10
((sumOf . (1 + ) . toInteger) 4 ) 7 10
( sumOf 5 ) 7 10
( sumOf . (5 + ) . toInteger ) 7 10
sumOf 12 10
sumOf . (12 + ) . toInteger 10
sumof 22
id 22
22
 5
Author: lispc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-04 14:37:09