Skip to the content.

Open the door with types, part 2

The most obvious approach is to use a different type for each state.

module Door
    ( OpenedDoor
    , ClosedDoor
    , LockedDoor
    , makeLocked
    , name
    , open
    , close
    , lock
    , unlock
    , knock
) where

import Data.Text (Text)

data Door = Door {
    name :: Text
} deriving (Show, Eq)

data OpenedDoor = OpenedDoor Door deriving (Show, Eq)
data ClosedDoor = ClosedDoor Door deriving (Show, Eq)
data LockedDoor = LockedDoor Door Text deriving (Show, Eq)

Now open function only takes OpenedDoor, and lock takes ClosedDoor. These functions no longer fail, so they no longer need to return Maybe.

makeLocked :: Text -> Text -> LockedDoor
makeLocked name key = LockedDoor (Door name) key

open :: ClosedDoor -> OpenedDoor
open (ClosedDoor door) = OpenedDoor door

close :: OpenedDoor -> ClosedDoor
close (OpenedDoor door) = ClosedDoor door

lock :: Text -> ClosedDoor -> LockedDoor
lock key (ClosedDoor door) = LockedDoor door key

unlock :: Text -> LockedDoor -> Either LockedDoor ClosedDoor
unlock key lockedDoor@(LockedDoor door lockedKey)
    | key == lockedKey = Right $ ClosedDoor door
    | otherwise = Left lockedDoor

Since we use different types, we need to make knock a method of a type class and make ClosedDoor and LockedDoor its instances.

class KnockableDoor door where
    knock :: door -> door
    knock = id
instance KnockableDoor ClosedDoor
instance KnockableDoor LockedDoor

The function to force open a door also becomes a type class method.

{-# LANGUAGE OverloadedStrings #-}

import Data.Either (fromRight)
import Data.Text (Text)
import Door

class ForceOpenableDoor door where
    forceOpen :: Text -> door -> Maybe OpenedDoor

instance ForceOpenableDoor OpenedDoor where
    forceOpen _ = Just
instance ForceOpenableDoor ClosedDoor where
    forceOpen _ = Just . open . knock
instance ForceOpenableDoor LockedDoor where
    forceOpen key lockedDoor
        | Right closedDoor