In Haskell, eine Basisversion von diesem mit einem GADT durch einen Shop von Kuchen (durch eine Liste von Nat
-s dargestellt) indiziert ausgedrückt werden könnte:
{-# LANGUAGE
TypeFamilies, GADTs, TypeOperators, PartialTypeSignatures,
DataKinds, PolyKinds #-}
import GHC.TypeLits
import Data.Proxy
import GHC.Exts
-- Allocate a new cake
type family New cs where
New '[] = 0
New (c ': cs) = c + 1
-- Constraint satisfiable if "c" is in "cs"
type family Elem c cs :: Constraint where
Elem c (c ': cs) =()
Elem c (c' ': cs) = Elem c cs
type family Remove c cs where
Remove c '[] = '[]
Remove c (c ': cs) = cs
Remove c (c' ': cs) = c' ': Remove c cs
data Bake :: [Nat] -> [Nat] -> * -> * where
Pure :: a -> Bake cs cs a
Bake :: (Proxy (New cs) -> Bake (New cs ': cs) cs' a) -> Bake cs cs' a
Eat :: Elem c cs => Proxy c -> Bake (Remove c cs) cs' a -> Bake cs cs' a
Keep :: Elem c cs => Proxy c -> Bake cs cs' a -> Bake cs cs' a
ok :: Bake '[] _ _
ok =
Bake $ \cake1 ->
Bake $ \cake2 ->
Eat cake1 $
Keep cake2 $
Eat cake2 $
Pure()
not_ok :: Bake '[] _ _
not_ok =
Bake $ \cake1 ->
Bake $ \cake2 ->
Eat cake1 $
Keep cake1 $ -- we already ate that
Eat cake2 $
Pure()
Leider ist der Typ nicht entfernen können wir Anmerkungen aus Bake
Aktionen und lassen Typen abgeleitet werden:
foo =
Bake $ \cake1 ->
Bake $ \cake2 ->
Eat cake1 $
Pure()
-- Error: Could not deduce (Elem (New cs0) (New cs0 + 1 : New cs0 : cs0))
Offensichtlich (Elem (New cs0) (New cs0 + 1 : New cs0 : cs0))
erfüllbar ist dies für alle cs0
, aber GHC kann nicht sehen, weil es nicht darum, obentscheiden können,ist ungleich New cs0 + 1
, weil GHC nichts über die flexible cs0
Variable annehmen kann.
Wenn wir NoMonomorphismRestriction
hinzufügen, würde foo
typecheck, aber das würde sogar falsche Programme typecheck machen, indem Sie alle Elem
Einschränkungen an die Spitze schieben. Dies würde zwar immer noch verhindern, dass etwas Nützliches mit falschen Begriffen gemacht wird, aber es ist eine ziemlich hässliche Lösung.
Allgemeiner können wir Bake
als indiziertes frei Monade ausdrücken, die uns do
-Notation mit RebindableSyntax
wird, und ermöglicht eine Definition für BakeF
, die etwas klarer ist als das, was wir bisher gesehen haben. Es könnte auch Boilerplate viel wie die einfache alte Free
Monade reduzieren, obwohl ich es eher unwahrscheinlich finde, dass Leute Gebrauch für indizierte freie Monaden zu zwei verschiedenen Gelegenheiten im praktischen Code finden würden.
{-# LANGUAGE
TypeFamilies, GADTs, TypeOperators, PartialTypeSignatures, StandaloneDeriving,
DataKinds, PolyKinds, NoImplicitPrelude, RebindableSyntax, DeriveFunctor #-}
import Prelude hiding (Monad(..))
import GHC.TypeLits
import Data.Proxy
import GHC.Exts
class IxFunctor f where
imap :: (a -> b) -> f i j a -> f i j b
class IxFunctor m => IxMonad m where
return :: a -> m i i a
(>>=) :: m i j a -> (a -> m j k b) -> m i k b
fail :: String -> m i j a
infixl 1 >>
infixl 1 >>=
(>>) :: IxMonad m => m i j a -> m j k b -> m i k b
ma >> mb = ma >>= const mb
data IxFree f i j a where
Pure :: a -> IxFree f i i a
Free :: f i j (IxFree f j k a) -> IxFree f i k a
liftf :: IxFunctor f => f i j a -> IxFree f i j a
liftf = Free . imap Pure
instance IxFunctor f => IxFunctor (IxFree f) where
imap f (Pure a) = Pure (f a)
imap f (Free fa) = Free (imap (imap f) fa)
instance IxFunctor f => IxMonad (IxFree f) where
return = Pure
Pure a >>= f = f a
Free fa >>= f = Free (imap (>>= f) fa)
fail = error
-- Old stuff for Bake
type family New cs where
New '[] = 0
New (c ': cs) = c + 1
type family Elem c cs :: Constraint where
Elem c (c ': cs) =()
Elem c (c' ': cs) = Elem c cs
type family Remove c cs where
Remove c '[] = '[]
Remove c (c ': cs) = cs
Remove c (c' ': cs) = c' ': Remove c cs
-- Now the return type indices of BakeF directly express the change
-- from the old store to the new store.
data BakeF cs cs' k where
BakeF :: (Proxy (New cs) -> k) -> BakeF cs (New cs ': cs) k
EatF :: Elem c cs => Proxy c -> k -> BakeF cs (Remove c cs) k
KeepF :: Elem c cs => Proxy c -> k -> BakeF cs cs k
deriving instance Functor (BakeF cs cs')
instance IxFunctor BakeF where imap = fmap
type Bake = IxFree BakeF
bake = liftf (BakeF id)
eat c = liftf (EatF c())
keep c = liftf (KeepF c())
ok :: Bake '[] _ _
ok = do
cake1 <- bake
cake2 <- bake
eat cake1
keep cake2
eat cake2
-- not_ok :: Bake '[] _ _
-- not_ok = do
-- cake1 <- bake
-- cake2 <- bake
-- eat cake1
-- keep cake1 -- already ate it
-- eat cake2
Könnten Sie Phantomtypen verwenden, um anzuzeigen, ob ein Kuchen gegessen wurde - wie 'Daten Kuchen a = Cake' und' Daten Eaten', 'Daten Fresh' dann' backen = Kuchen :: Kuchen Fresh' und ' essen :: Cake Fresh -> Kuchen gegessen; Kuchen essen = Kuchen? – epsilonhalbe
@epsilonhalbe Sicher, aber wenn du dann 'appetitedCookie <- uneatenCookie 'gemacht hast, würde dich nichts davon abhalten,' uneatenCookie' danach zu benutzen. – sepp2k
Ich sehe - also müssten Sie Ihre eigene Version von '<-' schreiben, die sich darum kümmert,' uneatenCookie' zu löschen, aber ich denke, das liegt jenseits meiner Vorstellungskraft. – epsilonhalbe