I'm writing a lib for message queues. Queues can be either Direct
or Topic
. Direct
queues have a static binding key, while Topic
queues can have dynamic ones.
I want to write a function publish
that only works on Direct
queues. This works:
{-# LANGUAGE DataKinds #-}
type Name = Text
type DirectKey = Text
type TopicKey = [Text]
data QueueType
= Direct DirectKey
| Topic TopicKey
data Queue (kind :: a -> QueueType)
= Queue Name QueueType
This requires two separate constructors
directQueue :: Name -> DirectKey -> Queue 'Direct
topicQueue :: Name -> TopicKey -> Queue 'Topic
But when I go to write publish, there's an extra pattern that I need to match which should be impossible
publish :: Queue 'Direct -> IO ()
publish (Queue name (Direct key)) =
doSomething name key
publish _ =
error "should be impossible to get here"
Is there a better way to model this problem so that I don't need that pattern match? Direct
queues should always have that Text
metadata, and Topic
queues should always have that [Text]
metadata. Is there a better way to enforce this at both the type and value level?
How about making Queue
a plain polymorphic type
data Queue a = Queue Name a
And then defining separate Queue DirectKey
and Queue TopicKey
types? Then you wouldn't need to pattern-match in publish :: Queue DirectKey -> IO ()
.
If, apart from that, you need functions that should work in any Queue
, perhaps you could define some common operations in a typeclass of which DirectKey
and TopicKey
would be instances, and then have signatures like
commonFunction :: MyTypeclass a => Queue a -> IO ()
Maybe you could put such functions directly in the typeclass
class MyTypeclass a where
commonFunction :: Queue a -> IO ()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With