Skip to the content.

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