Skip to the content.

Open the door with types, part 4

To avoid passing SingState explicitly, you can define a type class whose method returns SingState from State.

class SingStateI (state :: State) where
    singState :: SingState state

instance SingStateI 'Opened where
    singState = SOpened
instance SingStateI 'Closed where
    singState = SClosed
instance SingStateI 'Locked where
    singState = SLocked

You no longer need to pass SingState explicity to SomeDoor.

data SomeDoor = forall state. SingStateI state => SomeDoor (Door state)

forceOpen will have SingStateI as its context instead of taking SingState explicity.

forceOpen :: forall state. SingStateI state => Text -> Door state -> Maybe (Door 'Opened)
forceOpen key door =
    case singState @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

Now we can write a type-safe door, but writing State, SingState and SingStateI manually every time is troublesome. I’ll use singletons to define them automatically in the next post.