Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functions that only work with one constructor of a type

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?

like image 969
Sean Clark Hess Avatar asked Oct 12 '16 21:10

Sean Clark Hess


1 Answers

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 ()
like image 158
danidiaz Avatar answered Sep 19 '22 13:09

danidiaz