Search code examples
haskellnewtype

Constructive handling of sequence numbers


I am implementing a simple protocol where messages have a sequence number that must increment strictly between messages. To handle this I wrote:

newtype SequenceNo = SequenceNo Int64
  deriving (Show, Eq)

validSequence :: SequenceNo -> SequenceNo -> Bool
validSequence (SequenceNo firstS) (SequenceNo secondS) = firstS + 1 == secondS

I use it something like this:

applyUpdates :: ProtocolState -> UpdateMessage -> Either String ProtocolState
applyUpdates oldState upd =
  if validSequence (seqNo oldState) (updSeqNo upd)
    then Right (ProtocolState {seqNo=updSeqNo upd, …})
    else Left "invalid sequence"

But this has the same problem of boolean blindness that isJust :: Maybe a -> Bool has. How do I do better?


Solution

  • I would just define a type isomorphic to Bool, but with more descriptive constructor names.

    date Validity = Valid | Invalid
    

    then write a function that returns the kind of sequence represented by the arguments:

    classifySequence :: SequenceNo -> SequenceNo -> Validity
    classifySequence (SequenceNo x) (SequenceNo y) | x + 1 == y = Valid
                                                   | otherwise = Invalid
    

    It's even simpler if you define an Enum instance for your type.

    newtype SequenceNo = SequenceNo Int64 deriving (Show, Read, Eq, Enum)
    
    classifySequence :: SequenceNo -> SequenceNo -> Validity
    classifySequence x y | succ x == y = Valid
                         | otherwise = Invalid
    

    Either way, you can define

    applyUpdates :: ProtocolState -> UpdateMessage -> Either String ProtocolState
    applyUpdates oldState upd =
      case validSequence (seqNo oldState) (updSeqNo upd) of
        Valid -> Right (ProtocolState {seqNo=updSeqNo upd, …})
        Invalid -> Left "invalid sequence"
    

    although you might consider an explicit error type as well:

    data SequenceError = InvalidSequence
    
    applyUpdates :: ProtocolState -> UpdateMessage -> Either SequenceError ProtocolState
    applyUpdates oldState upd =
      case validSequence (seqNo oldState) (updSeqNo upd) of
        Valid -> Right (ProtocolState {seqNo=updSeqNo upd, …})
        Invalid -> Left InvalidSequence