Odpowiedzi:
Podnoszenie jest bardziej wzorcem projektowym niż koncepcją matematyczną (chociaż spodziewam się, że ktoś tutaj teraz obali mnie, pokazując, jak windy są kategorią lub czymś).
Zwykle masz pewien typ danych z parametrem. Coś jak
data Foo a = Foo { ...stuff here ...}
Załóżmy, że można zauważyć, że wiele zastosowań Foowziąć typów numerycznych ( Int, Doubleetc) i zachować konieczności wprowadź kod, który rozpakowuje te numery, dodaje lub pomnaża je, a następnie owija je z powrotem do góry. Możesz to zewrzeć, pisząc raz kod rozpakowywania i zawijania. Ta funkcja jest tradycyjnie nazywana „windą”, ponieważ wygląda tak:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
Innymi słowy, masz funkcję, która przyjmuje funkcję dwuargumentową (taką jak (+)operator) i zamienia ją w równoważną funkcję dla Foos.
Więc teraz możesz pisać
addFoo = liftFoo2 (+)
Edycja: więcej informacji
Oczywiście można mieć liftFoo3, liftFoo4i tak dalej. Jednak często nie jest to konieczne.
Zacznij od obserwacji
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Ale to jest dokładnie to samo, co fmap. Więc zamiast liftFoo1pisać
instance Functor Foo where
fmap f foo = ...
Jeśli naprawdę chcesz całkowitej regularności, możesz powiedzieć
liftFoo1 = fmap
Jeśli możesz zrobić Fooz niego funktor, być może możesz uczynić go funktorem aplikacyjnym. W rzeczywistości, jeśli możesz pisać, liftFoo2to instancja aplikacyjna wygląda następująco:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
(<*>)Operator Foo ma typ
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Stosuje opakowaną funkcję do opakowanej wartości. Więc jeśli możesz wdrożyć, liftFoo2możesz to napisać w kategoriach tego. Lub możesz to zaimplementować bezpośrednio i nie zawracać sobie głowy liftFoo2, ponieważ Control.Applicativemoduł zawiera
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
i podobnie są liftAi liftA3. Ale w rzeczywistości nie używasz ich zbyt często, ponieważ jest inny operator
(<$>) = fmap
Dzięki temu możesz napisać:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
Termin myFunction <$> arg1zwraca nową funkcję opakowaną w Foo. To z kolei można zastosować do następnego argumentu za pomocą (<*>)i tak dalej. Więc teraz zamiast mieć funkcję podnoszenia dla każdego rodzaju, masz po prostu łańcuch aplikacji.
lift id == idi lift (f . g) == (lift f) . (lift g).
idi .są odpowiednio strzałką identyfikacyjną i kompozycją strzałek w jakiejś kategorii. Zwykle mówiąc o Haskell, kategorią, o której mowa, jest „Haskell”, którego strzałki są funkcjami Haskella (innymi słowy idi .odnoszą się do funkcji Haskella, które znasz i lubisz).
instance Functor Foo, nie instance Foo Functor, prawda? Sam bym edytował, ale nie jestem w 100% pewien.
Paul i yairchu to dobre wyjaśnienia.
Chciałbym dodać, że podnoszona funkcja może mieć dowolną liczbę argumentów i nie muszą one być tego samego typu. Na przykład możesz również zdefiniować liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Ogólnie rzecz biorąc, podnoszenie funkcji, które przyjmują 1 argument, jest przechwytywane w klasie typu Functor, a operacja podnoszenia jest nazywana fmap:
fmap :: Functor f => (a -> b) -> f a -> f b
Zwróć uwagę na podobieństwo z liftFoo1typem. W rzeczywistości, jeśli tak liftFoo1, możesz utworzyć Fooprzykład Functor:
instance Functor Foo where
fmap = liftFoo1
Ponadto uogólnienie podnoszenia na dowolną liczbę argumentów nazywane jest stylem aplikacyjnym . Nie przejmuj się tym, dopóki nie zrozumiesz podnoszenia funkcji za pomocą ustalonej liczby argumentów. Ale kiedy to zrobisz, Learn you a Haskell zawiera dobry rozdział na ten temat. Typeclassopedia to kolejny dobry dokument, który opisuje funktora i aplikacyjnych (jak również inne zajęcia typu; przewijania w dół do prawego rozdział w tym dokumencie).
Mam nadzieję że to pomoże!
Zacznijmy od przykładu (dla lepszej prezentacji dodano trochę spacji):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2przekształca funkcję zwykłych typów na funkcję tego samego typu, która jest zawinięta w ankietęApplicative , taką jak listy IOitp.
Inna wspólna winda jest liftz Control.Monad.Trans. Przekształca monadyczne działanie jednej monady w działanie przekształconej monady.
Ogólnie rzecz biorąc, „lift” podnosi funkcję / akcję do typu „opakowanego” (tak więc oryginalna funkcja zaczyna działać „pod opakowaniem”).
Najlepszym sposobem na zrozumienie tego, monad itp. Oraz zrozumienia, dlaczego są one przydatne, jest prawdopodobnie kodowanie i używanie ich. Jeśli jest coś, co zakodowałeś wcześniej i podejrzewasz, że może to skorzystać (tj. Skróci to kod itp.), Po prostu wypróbuj to, a łatwo zrozumiesz koncepcję.
Podnoszenie to koncepcja, która pozwala przekształcić funkcję w odpowiadającą jej funkcję w innym (zwykle bardziej ogólnym) ustawieniu
zajrzyj na http://haskell.org/haskellwiki/Lifting
Według tego błyszczącą tutorialu , funktorem jest jakiś pojemnik (jak Maybe<a>, List<a>albo Tree<a>że elementy sklepie można z jakiegoś innego typu, a). Użyłem notacji generycznej Java <a>dla typu elementu ai myślę o elementach jak o jagodach na drzewie Tree<a>. Istnieje funkcja fmap, która przyjmuje funkcję konwersji elementów a->bi kontener functor<a>. Odnosi się a->bdo każdego elementu pojemnika, skutecznie go przekształcając functor<b>. Gdy podano tylko pierwszy argumenta->b , fmapczeka na functor<a>. Oznacza to, że a->bsamo dostarczanie zamienia tę funkcję na poziomie elementu w funkcję, functor<a> -> functor<b>która działa na kontenerach. Nazywa się to podnoszeniemfunkcji. Ponieważ kontener nazywany jest także funktorem, Warunkiem wstępnym podnoszenia są raczej Funktory niż Monady. Monady są swego rodzaju „równolegle” do podnoszenia. Obie opierają się na pojęciu Functor i tak robią f<a> -> f<b>. Różnica polega na tym, że lifting wykorzystuje się a->bdo konwersji, podczas gdy Monad wymaga od użytkownika zdefiniowania a -> f<b>.
rdo typu ( cużyjmy dla odmiany) to Functory. Nie „zawierają” żadnych c. W tym przypadku fmap jest kompozycją funkcji, przyjmując a -> bfunkcję i jedynkę r -> a, aby dać ci nową r -> bfunkcję. Nadal żadnych pojemników, więc gdybym mógł, zanotowałbym to ponownie na ostatnie zdanie.
fmapjest to funkcja, a nie „czekać” na cokolwiek; „Kontener” będący funktorem to cały punkt podnoszenia. Ponadto, monady są, jeśli w ogóle, podwójnym pomysłem na podnoszenie: Monada pozwala ci użyć czegoś, co zostało podniesione kilka dodatnich razy, tak jakby zostało podniesione tylko raz - jest to lepiej znane jako spłaszczenie .
To wait, to expect, to anticipatesą synonimami. Mówiąc „funkcja czeka” miałem na myśli „funkcja przewiduje”.
b = 5 : ai f 0 = 55 f n = g noba obejmują pseudo-mutację „pojemnika”. Również fakt, że listy są zwykle przechowywane w całości w pamięci, podczas gdy funkcje są zwykle przechowywane jako obliczenia. Ale listy zapamiętywane / monorficzne, które nie są przechowywane między wywołaniami, obalają ten pomysł.