Open the door with types, part 5
In the previous post, I implemented SingState
and SingStateI
manually.
With the help of singletons, you no longer need to implement them manually.
{-# LANGUAGE DataKinds,
GADTs,
StandaloneDeriving,
StandaloneKindSignatures,
TemplateHaskell,
TypeFamilies
#-}
module Door
( Door
, State(Opened, Closed, Locked)
, SState(SOpened, SClosed, SLocked)
, SomeDoor(SomeDoor)
, makeLocked
, name
, open
, close
, lock
, unlock
, knock
) where
import Data.Kind (Constraint, Type)
import Data.Singletons.TH
import Data.Text (Text)
singletons [d|
data State = Opened | Closed | Locked
|]
data Door :: State -> Type where
OpenedDoor :: Text -> Door 'Opened
ClosedDoor :: Text -> Door 'Closed
LockedDoor :: Text -> Text -> Door 'Locked
deriving instance Show (Door state)
data SomeDoor = forall state. SingI state => SomeDoor (Door state)
You need to enable some language extensions and import Data.Singletons.TH
to do this. As you can see, I put the declaration of State
in singletons
template function.
singletons
generates Sing
and SingI
for State
(actually, it generates more but you can ignore them here). Sing
is a generalized version of SingState
and SingI
is a generalized version of SingStateI
. So you can write forceOpen
by replacing SingStateI
to SingI
, and singState
to sing
.
forceOpen :: forall state. SingI state => Text -> Door state -> Maybe (Door 'Opened)
forceOpen key door =
case sing @state of
SOpened -> Just door
SClosed -> Just $ open $ knock door
SLocked -> case unlock key $ knock door of
Right closedDoor -> Just $ open closedDoor
Left _ -> Nothing
forceOpenSomeDoor :: Text -> SomeDoor -> Maybe (Door 'Opened)
forceOpenSomeDoor key (SomeDoor door) = forceOpen key door