Aby dodać doskonałą odpowiedź Daniela, istnieje kilka punktów chciałbym zrobić:
Najpierw here's się aplikacyjnych instancję:
instance Applicative (Either e) where
pure = Right
Left e <*> _ = Left e
Right f <*> r = fmap f r
widać, że jest to „krótko- circuiting "- gdy tylko trafi na Left
, przerywa i zwraca lewy. Możesz to sprawdzić, analizując dokładność analizy słabego człowieka:
ghci> (++) <$> Left "Hello" <*> undefined
Left "Hello" -- <<== it's not undefined :) !!
ghci> (++) <$> Right "Hello" <*> undefined
*** Exception: Prelude.undefined -- <<== undefined ... :(
ghci> Left "oops" <*> undefined <*> undefined
Left "oops" -- <<== :)
ghci> Right (++) <*> undefined <*> undefined
*** Exception: Prelude.undefined -- <<== :(
Po drugie, twój przykład jest nieco skomplikowany. Ogólnie rzecz biorąc, typ funkcji i e
w nie są powiązane. Oto <*>
s Typ:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Jeśli wykonujemy podstawienie f
- >>Either e
, otrzymujemy:
(<*>) :: Either e (a -> b) -> Either e a -> Either e b
Chociaż w przykładzie, e
i a
mecz, w ogóle ich nie będzie , co oznacza, że nie można polimorficznie implementować instancji aplikacji dla Either e
, która stosuje tę funkcję do argumentu po lewej stronie.
Jest to tradycyjne użycie tego, że Prawo reprezentuje wartość, którą jesteś zainteresowany, natomiast lewe przedstawia porażkę. Prawe (poprawne) wartości mogą być łączone i modyfikowane za pomocą Applicative i Functor, natomiast zła wartość nad wartością złą będzie uporczywie utrzymywać się, więc to jest dobre dla rzeczy takich jak zgłaszanie pierwszego błędu w sposób, w jaki może być prosty kompilator. – AndrewC