Zabawne pytanie. Oto moje podejście - zobaczmy, czy nigdzie się nie nurzałem!
Na początek będę przeliterować swoje podpisy w (nieco mniej pseudo) Haskell:
return :: a -> PSet (r -> a)
(>>=) :: PSet (r -> a) -> (a -> PSet (r -> b)) -> PSet (r -> b))
Przed kontynuowaniem, warto wspomnieć dwa praktyczne komplikacje. Po pierwsze, jak już zauważyłeś, dzięki ograniczeniom Eq
i/lub Ord
nie jest trywialne wydawanie instancji Functor
lub Monad
; w każdym przypadku, there are ways around it. Po drugie, i co gorsza, z typem proponujecie dla (>>=)
konieczne jest wyodrębnienie a
s od PSet (r -> a)
bez żadnych oczywistych podaży r
s - lub w Innymi słowy, (>>=)
wymaga przechodzenie funkcji funktora (->) r
. To oczywiście nie jest możliwe w ogólnym przypadku i wydaje się być niepraktyczne, nawet jeśli jest to możliwe - przynajmniej jeśli chodzi o Haskella. W każdym razie, dla naszych celów spekulacyjnych, możemy założyć, że możemy przemieścić (->) r
przez zastosowanie tej funkcji do wszystkich możliwych wartości r
. Wskażę to przez ręcznie falowany zestaw universe :: PSet r
, nazwany w hołdzie this package. Będę również korzystał z universe :: PSet (r -> b)
i zakładamy, że możemy stwierdzić, czy dwie funkcje zgadzają się z pewnym r
nawet bez wymagania ograniczenia Eq
. (Pseudo-Haskell jest rzeczywiście całkiem fałszywy!)
Uwagi wstępne wykonane, tutaj są moje wersje pseudo-Haskell twoich metod:
return :: a -> PSet (r -> a)
return x = singleton (const x)
(>>=) :: PSet (r -> a) -> (a -> PSet (r -> b)) -> PSet (r -> b))
m >>= f = unionMap (\x ->
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (f (x r)))
(universe :: PSet (r -> b)))
(universe :: PSet r)) m
where
unionMap f = unions . map f
intersectionMap f = intersections . map f
Następny, prawa Monad:
m >>= return = m
return y >>= f = f y
m >>= f >>= g = m >>= \y -> f y >>= g
(Nawiasem mówiąc, kiedy robi tego rodzaju dobrze jest pamiętać o innych prezentacjach klasy, z którą pracujemy - w tym przypadku mamy join
i (>=>)
jako alternatywy dla (>>=)
- ponieważ zmiana prezentacji może sprawić, że będzie działać z Twoim wystąpieniem wyboru e bardziej przyjemny. Tutaj będę trzymać z prezentacją Monad
(>>=)
.)
dalej do pierwszego prawa ...
m >>= return = m
m >>= return -- LHS
unionMap (\x ->
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (singleton (const (x r))))
(universe :: PSet (r -> b)))
(universe :: PSet r)) m
unionMap (\x ->
intersectionMap (\r ->
filter (\rb ->
const (x r) r == rb r)
(universe :: PSet (r -> b)))
(universe :: PSet r)) m
unionMap (\x ->
intersectionMap (\r ->
filter (\rb ->
x r == rb r)
(universe :: PSet (r -> b)))
(universe :: PSet r)) m
-- In other words, rb has to agree with x for all r.
unionMap (\x -> singleton x) m
m -- RHS
Jeden w dół, dwa iść.
return y >>= f = f y
return y -- LHS
unionMap (\x ->
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (f (x r)))
(universe :: PSet (r -> b)))
(universe :: PSet r)) (singleton (const y))
(\x ->
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (f (x r)))
(universe :: PSet (r -> b)))
(universe :: PSet r)) (const y)
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (f (const y r)))
(universe :: PSet (r -> b)))
(universe :: PSet r)
intersectionMap (\r ->
filter (\rb ->
any (\rb' -> rb' r == rb r) (f y)))
(universe :: PSet (r -> b)))
(universe :: PSet r)
-- This set includes all functions that agree with at least one function
-- from (f y) at each r.
return y >>= f
więc może ewentualnie być znacznie większy zbiór niż f y
. Mamy pogwałcenie drugiego prawa; dlatego nie mamy monady - przynajmniej nie w przypadku proponowanej tutaj instancji.
Dodatek: tutaj jest rzeczywiste, runnable realizacja swoich funkcji, która jest na tyle użyteczny przynajmniej do gry z małych typów. Wykorzystuje wspomniany pakiet universe.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module FunSet where
import Data.Universe
import Data.Map (Map)
import qualified Data.Map as M
import Data.Set (Set)
import qualified Data.Set as S
import Data.Int
import Data.Bool
-- FunSet and its would-be monad instance
newtype FunSet r a = FunSet { runFunSet :: Set (Fun r a) }
deriving (Eq, Ord, Show)
fsreturn :: (Finite a, Finite r, Ord r) => a -> FunSet r a
fsreturn x = FunSet (S.singleton (toFun (const x)))
-- Perhaps we should think of a better name for this...
fsbind :: forall r a b.
(Ord r, Finite r, Ord a, Ord b, Finite b, Eq b)
=> FunSet r a -> (a -> FunSet r b) -> FunSet r b
fsbind (FunSet s) f = FunSet $
unionMap (\x ->
intersectionMap (\r ->
S.filter (\rb ->
any (\rb' -> funApply rb' r == funApply rb r)
((runFunSet . f) (funApply x r)))
(universeF' :: Set (Fun r b)))
(universeF' :: Set r)) s
toFunSet :: (Finite r, Finite a, Ord r, Ord a) => [r -> a] -> FunSet r a
toFunSet = FunSet . S.fromList . fmap toFun
-- Materialised functions
newtype Fun r a = Fun { unFun :: Map r a }
deriving (Eq, Ord, Show, Functor)
instance (Finite r, Ord r, Universe a) => Universe (Fun r a) where
universe = fmap (Fun . (\f ->
foldr (\x m ->
M.insert x (f x) m) M.empty universe))
universe
instance (Finite r, Ord r, Finite a) => Finite (Fun r a) where
universeF = universe
funApply :: Ord r => Fun r a -> r -> a
funApply f r = maybe
(error "funApply: Partial functions are not fun")
id (M.lookup r (unFun f))
toFun :: (Finite r, Finite a, Ord r) => (r -> a) -> Fun r a
toFun f = Fun (M.fromList (fmap ((,) <$> id <*> f) universeF))
-- Set utilities
unionMap :: (Ord a, Ord b) => (a -> Set b) -> (Set a -> Set b)
unionMap f = S.foldl S.union S.empty . S.map f
-- Note that this is partial. Since for our immediate purposes the only
-- consequence is that r in FunSet r a cannot be Void, I didn't bother
-- with making it cleaner.
intersectionMap :: (Ord a, Ord b) => (a -> Set b) -> (Set a -> Set b)
intersectionMap f s = case ss of
[] -> error "intersectionMap: Intersection of empty set of sets"
_ -> foldl1 S.intersection ss
where
ss = S.toList (S.map f s)
universeF' :: (Finite a, Ord a) => Set a
universeF' = S.fromList universeF
-- Demo
main :: IO()
main = do
let andor = toFunSet [uncurry (&&), uncurry (||)]
print andor -- Two truth tables
print $ funApply (toFun (2+)) (3 :: Int8) -- 5
print $ (S.map (flip funApply (7 :: Int8)) . runFunSet)
(fsreturn (Just True)) -- fromList [Just True]
-- First monad law demo
print $ fsbind andor fsreturn == andor -- True
-- Second monad law demo
let twoToFour = [ bool (Left False) (Left True)
, bool (Left False) (Right False)]
decider b = toFunSet
(fmap (. bool (uncurry (&&)) (uncurry (||)) b) twoToFour)
print $ fsbind (fsreturn True) decider == decider True -- False (!)
Jak definicja ">>" odpowiada "bind" w OP? Gdzie są kwantyfikatory i strażnik zawierający porównanie równości? Z oryginalnym pseudokodem 'm >> = return' to' {rb | x <- m, ∀r: rb r == x r} 'co tak naprawdę jest po prostu' m' w teorii zbiorów. Być może nie uda się precyzyjnie uchwycić danej semantyki w prawdziwym Haskellowi, ale nie sądzę, że pytanie o to. – user2407038
Myślę, że zgadzam się z powyższym komentarzem. W OP 'bind'',' f (x r) 'i' rb r' oba odbierają to samo środowisko 'r'. Nie rozumiem, jak jest to zagwarantowane przez twoje (wciąż całkiem piękne) '>>. –
@ user2407038 [1/2] W powyższym pseudo-Haskella kwantyfikator jest reprezentowany przez 'fromList [minBound..maxBound]', oznaczany jako zestaw zawierający wszystkie możliwe wartości 'r'. To powiedziawszy ... – duplode