2013-10-31 10 views
10

Co usiłuję zrobić jest trywialny do określenia przez strony, w zasadzieJak w prosty sposób używać wartości may monoid i łączyć wartości z niestandardową operacją?

maybeCombine :: (a->a->a) -> Maybe a -> Maybe a -> Maybe a 
maybeCombine _ Nothing Nothing = Nothing 
maybeCombine _ (Just a) Nothing = Just a 
maybeCombine _ Nothing (Just a) = Just a 
maybeCombine f (Just a) (Just a') = Just $ f a a' 

to nie jest wielka sprawa define this locally when needed, ale nadal cumbersone i jest tak proste i ogólnie wydaje się, że powinno być standardem realizacji, ale nie mogę tego znaleźć.

Może po prostu coś przeoczyłem. To, czego chcę, wydaje się zupełnie niezwiązane z zachowaniem monady, więc myślę, że nie znajdę niczego w szufladach Monada/Strzała; ale na pewno przypomina Monoid instancji

Prelude Data.Monoid> tylko "" <> Nic
tylko "a"
Prelude Data.Monoid> Tak "a" <> Po prostu "b"
po prostu "ab"
...

... co jednak wymaga a być sama monoid, to znaczy, że to w zasadzie ma a->a->a "wbudowany". Instancja MonadPlus zachowuje również podobnie jak chcę, ale to po prostu wyrzuca jedną z wartości, a nie pozwalając mi dostarczyć funkcję połączeniu

Prelude Data.Monoid Control.Monad> Just 4 `mplus` Nic
tylko 4
Prelude Data.Monoid Control.Monad> Nic `mplus` tylko 4
tylko 4
Prelude Data.Monoid Control.Monad> tylko 4` mplus` tylko 5
tylko 4

Jakie byłoby rozwiązanie kanoniczne? Lokalne dopasowywanie wzorców? Coś z kombinatorami z np. Data.Maybe? Definiowanie niestandardowego monoidu do łączenia?

Odpowiedz

11

Masz rację, gdy zauważysz, że f jest jak operacja Monoid dla podstawowego typu a. Dokładniej chodzi o to, że podnosisz Semigroup do Monoid przez sąsiadujące zero (mempty), Nothing.

To jest dokładnie to, co widać w Haddocks dla MaybeMonoid w rzeczywistości.

podnieść półgrupa w Może tworząc monoid według http://en.wikipedia.org/wiki/Monoid: „Każdy półgrupa S może być przekształcony w monoid tylko przez sąsiadujące elementu E. nie w sekundach i określenie e e = e i e S = S = s * e dla wszystkich s ∈ S. " Ponieważ nie ma typografii "Semigroup" zapewniającej tylko mappend, zamiast tego używamy Monoid.

Albo, jeśli chcesz pakiet semigroups, to nie Option który ma dokładnie ten problem, odpowiednio uogólnione użyć stanowiącego podstawę Semigroup zamiast.


Więc sugeruje najwyraźniejszy sposób jest zdefiniowanie albo Monoid lub Semigroup wystąpienie na bazowym typu a. To czysty sposób na powiązanie z tym typem jakiegoś sumatora f.

Co zrobić, jeśli nie kontrolujesz tego typu, nie chcesz mieć sierocych instancji i uważasz, że opakowanie newtype jest brzydkie? Zwykle będziesz miał pecha, ale jest to miejsce, w którym przydaje się użycie całkowitej czarnej magii, skutecznie tylko pakiet GHC-reflection. Dokładne wyjaśnienia istnieją: in the paper itself, ale Ausin Seipp's FP Complete Tutorial zawiera przykładowy kod, który umożliwia "wstrzykiwanie" dowolnych półgrupowych produktów do typów bez szumu o definicji typu ... kosztem dużo bardziej przerażających podpisów.  

Prawdopodobnie jest to jednak znacznie większe obciążenie niż jego wartość.

+1

nie jestem pecha, w Problem Pracuję nad tym, mogę teraz użyć półgrupy "Max" i uzyskać naprawdę fajne rozwiązanie! – leftaroundabout

+3

Awesome! Często kończę definiowanie "Option (Max a)" jako łączącego "negatywną nieskończoność" z typem, więc absolutnie. Myślę, że to naprawdę elegancki "Monoid". –

11

zawsze można użyć

f <$> m <*> n <|> m <|> n 

Ale to, niestety, nie posiada kanoniczną realizację wszędzie.

Można użyć reflection aby ta (a -> a -> a) „pieczone w” jako Semigroup do użytku z Option, który jest dostarczany przez semigroups jako ulepszona wersja Maybe że ma „prawo” instancję Monoid pod względem Semigroup. Jest to jednak zbyt trudne zadanie. =)

Być może to powinno być dodane jako kombinator do Data.Maybe.

+0

Dobrze, zapomniałem o "Alternative"! To całkiem dobrze, ale myślę, że wolę elegancję "półgrupy". – leftaroundabout

2
import Data.Monoid 
maybeCombine :: (a->a->a) -> Maybe a -> Maybe a -> Maybe a 
maybeCombine f mx my = let combine = mx >>= (\x -> my >>= (\y -> Just (f x y))) 
         in getFirst $ First combine `mappend` First mx `mappend` First` my 

w GHCi, to daje mi

*Main> maybeCombine (+) Nothing Nothing 
Nothing 
*Main> maybeCombine (+) (Just 3) Nothing 
Just 3 
*Main> maybeCombine (+) (Just 3) (Just 5) 
Just 8 

współpracuje także z getLast jeśli umieścisz Last combine na końcu sekwencji mappend

+0

Przyznaję, że to nie jest tak ogólne rozwiązanie jak J. Abrahamsona;) – itsbruce

+0

+1 za odpowiedź na poprzednio nieskrępowany (i być może najbardziej bezpośredni) aspekt tego pytania ... ale szczerze mówiąc, ta implementacja nie jest ani krótsza, ani bardziej czytelna niż mój oryginalny głupi wzór - pasujący do jednego. Nie mogę też zobaczyć, jak można go lepiej dostosować do zmian wymagań. – leftaroundabout

+1

Możesz grać w golfa w tę metodę aż do czegoś tak banalnego, jak rozwiązanie Eda - 'getFirst. mconcat. map First $ [liftA2 f mx my, mx, my] ', a nawet zamień' getFirst. mconcat. map Pierwsze 'z' msum' choć jest prawdopodobnie mniej jasne bez 'First'. –

Powiązane problemy