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.
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).
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
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)].
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.
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.
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
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