Type constraints are ending up ambiguous - haskell

In a Haskell app I'm working on, I have an API in which I'm trying to set up a pluggable set of backends. I'll have several different backend types and I want the caller (right now, just the test suite) to determine the actual backends. However, I'm getting an ambiguous type error.
class HasJobQueue ctx queue where
hasJobQueue :: JobQueue queue => ctx -> queue
class JobQueue q where
enqueue :: MonadIO m => Command -> q -> m ()
type CloisterM ctx queue exc m = ( Monad m, MonadIO m, MonadError exc m, MonadReader ctx m
, AsCloisterExc exc
, HasJobQueue ctx queue
, JobQueue queue
)
createDocument :: forall ctx queue exc m. CloisterM ctx queue exc m => Path -> Document -> m DocumentAddr
createDocument path document = do
...
queue <- hasJobQueue <$> ask
enqueue (SaveDocument addr document) queue
...
So, to me this seems pretty clear. In createDocument, I'm wanting to retrieve the context, and from that retrieve the job queue, which the caller would define and attach to the context. But Haskell disagrees and gives me this error:
• Could not deduce (JobQueue q0)
arising from a use of ‘hasJobQueue’
from the context: CloisterM ctx queue exc m
bound by the type signature for:
createDocument :: CloisterM ctx queue exc m =>
Path -> Document -> m DocumentAddr
at src/LuminescentDreams/CloisterDB.hs:32:1-105
The type variable ‘q0’ is ambiguous
• In the first argument of ‘(<$>)’, namely ‘hasJobQueue’
Here is an example of what I am trying to build, this one from my API test suite where I am mocking all of the backends with simple IORefs, where production will have other backend implementations
data MemoryCloister = MemoryCloister WorkBuffer
newtype WorkBuffer = WorkBuffer (IORef [WorkItem Command])
instance JobQueue WorkBuffer where
hasJobQueue (MemoryCloister wb) = wb
instance JobQueue WorkBuffer where
...
So, what exactly do I need to do to help the type checker understand that the context in the MonadReader contains an object that implements the JobQueue class?
The entire data types file, including how I ultimately reformulated the JobQueue for something more flexible than even the above, is in this project

While it’s difficult to know exactly what the right solution to your problem is based on the code and context given, the error you’re seeing stems from the HasJobQueue typeclass, which is extremely general:
class HasJobQueue ctx queue where
hasJobQueue :: JobQueue queue => ctx -> queue
From the typechecker’s point of view, hasJobQueue is a function from a -> b, plus a few constraints (but constraints do not generally affect type inference). This means that, in order to invoke hasJobQueue, both its input and its output must be entirely unambiguously specified by some other source of type information.
If this this confusing, consider a slightly different class that is nearly identical to the typechecker:
class Convert a b where
convert :: a -> b
This typeclass is generally an antipattern (precisely because it makes type inference very hard), but it could theoretically be used to provide instances to convert between any two types. For example, one could write the following instance:
instance Convert Integer String where
convert = show
…then use convert to convert an integer to a string:
ghci> convert (42 :: Integer) :: String
"42"
However, note that the following will not work:
ghci> convert (42 :: Integer)
<interactive>:26:1: error:
• Ambiguous type variable ‘a0’ arising from a use of ‘print’
prevents the constraint ‘(Show a0)’ from being solved.
Probable fix: use a type annotation to specify what ‘a0’ should be.
The issue here is that GHC has no idea what b should be, so it can’t pick which Convert instance to use.
In your code, hasJobQueue is much the same, though the details are a little more complex. The issue arises in the following lines:
queue <- hasJobQueue <$> ask
enqueue (SaveDocument addr document) queue
In order to know which HasJobQueue instance to use, GHC needs to know the type of queue. Well, fortunately, GHC can infer types for bindings based on how they are used, so hopefully, queue’s type can be inferred. It is provided as the second argument to enqueue, so we can understand what’s going on by looking at the type of enqueue:
enqueue :: (JobQueue q, MonadIO m) => Command -> q -> m ()
And here we see the problem. The second argument of enqueue must have type q, which is also unconstrained, so GHC does not gain any additional information. Therefore, it can’t determine the type of q, and it doesn’t know which instance to use for either the call to hasJobQueue or the call to enqueue.
So how can you solve this? Well, one way would be to pick a particular type for queue, but based on your code, I’m betting that isn’t actually what you want. More likely, there is a particular type of queue associated with each particular ctx, so the return type of hasJobQueue should really be implied by its first argument. Fortunately, Haskell has a notion to encode this very thing, and that notion is functional dependencies.
Remember that I said at the beginning that constraints do not generally affect type inference? Functional dependencies change that. When you write a fundep, you state that the typechecker actually can gain information from a constraint because some of the type variables imply some of the others. In this case, you want queue to be implied by ctx, so you can change the definition of HasJobQueue:
class HasJobQueue ctx queue | ctx -> queue where
hasJobQueue :: JobQueue queue => ctx -> queue
The | ctx -> queue syntax can be read as “ctx implies queue”.
Now, when you write hasJobQueue <$> ask, GHC already knows ctx, and it knows it can figure out queue from ctx. Therefore, the code is no longer ambiguous, and it can pick the correct instances.
Of course, nothing is for free. The functional dependency is nice, but what do we give up? Well, it means we promise that, for each ctx, there is exactly one queue, no more. Without the functional dependency, both of these instances can coexist:
instance HasJobQueue FooCtx MyQueueA
instance HasJobQueue FooCtx MyQueueB
These are totally legal, and GHC will pick the instance based on which type of queue is requested by the calling code. With the functional dependency, this is illegal, which makes sense—the whole point is that the second parameter must be implied by the first, and if two different options are possible, GHC can’t disambiguate by the first parameter alone.
In this sense, functional dependencies allow typeclass constraints to have “input” and “output” parameters. Sometimes, functional dependencies are called “type-level Prolog”, since they turn the constraint solver into a relational sublanguage. This is extremely powerful, and you can even write classes with bidirectional relations:
class Add a b c | a b -> c, a c -> b, b c -> a
Usually, though, most uses of functional dependencies involve cases like the one you ran into, where one structure semantically “has” an associated type. For example, one of the classic examples come from the mtl library, which use functional dependencies to represent reader context, writer state, etc.:
class MonadReader r m | m -> r
class MonadWriter w m | m -> w
class MonadState s m | m -> s
class MonadError e m | m -> e
This means they can be equivalently expressed in a slightly different way using associated types (part of the TypeFamilies extension)… but that is probably outside the scope of this answer.

Related

Clarification on Existential Types in Haskell

I am trying to understand Existential types in Haskell and came across a PDF http://www.ii.uni.wroc.pl/~dabi/courses/ZPF15/rlasocha/prezentacja.pdf
Please correct my below understandings that I have till now.
Existential Types not seem to be interested in the type they contain but pattern matching them say that there exists some type we don't know what type it is until & unless we use Typeable or Data.
We use them when we want to Hide types (ex: for Heterogeneous Lists) or we don't really know what the types at Compile Time.
GADT's provide the clear & better syntax to code using Existential Types by providing implicit forall's
My Doubts
In Page 20 of above PDF it is mentioned for below code that it is impossible for a Function to demand specific Buffer. Why is it so? When I am drafting a Function I exactly know what kind of buffer I gonna use eventhough I may not know what data I gonna put into that.
What's wrong in Having :: Worker MemoryBuffer Int If they really want to abstract over Buffer they can have a Sum type data Buffer = MemoryBuffer | NetBuffer | RandomBuffer and have a type like :: Worker Buffer Int
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
As Haskell is a Full Type Erasure language like C then How does it know at Runtime which function to call. Is it something like we gonna maintain few information and pass in a Huge V-Table of Functions and at runtime it gonna figure out from V-Table? If it is so then what sort of Information it gonna store?
GADT's provide the clear & better syntax to code using Existential Types by providing implicit forall's
I think there's general agreement that the GADT syntax is better. I wouldn't say that it's because GADTs provide implicit foralls, but rather because the original syntax, enabled with the ExistentialQuantification extension, is potentially confusing/misleading. That syntax, of course, looks like:
data SomeType = forall a. SomeType a
or with a constraint:
data SomeShowableType = forall a. Show a => SomeShowableType a
and I think the consensus is that the use of the keyword forall here allows the type to be easily confused with the completely different type:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
A better syntax might have used a separate exists keyword, so you'd write:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
The GADT syntax, whether used with implicit or explicit forall, is more uniform across these types, and seems to be easier to understand. Even with an explicit forall, the following definition gets across the idea that you can take a value of any type a and put it inside a monomorphic SomeType':
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
and it's easy to see and understand the difference between that type and:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
Existential Types not seem to be interested in the type they contain but pattern matching them say that there exists some type we don't know what type it is until & unless we use Typeable or Data.
We use them when we want to Hide types (ex: for Heterogeneous Lists) or we don't really know what the types at Compile Time.
I guess these aren't too far off, though you don't have to use Typeable or Data to use existential types. I think it would be more accurate to say an existential type provides a well-typed "box" around an unspecified type. The box does "hide" the type in a sense, which allows you to make a heterogeneous list of such boxes, ignoring the types they contain. It turns out that an unconstrained existential, like SomeType' above is pretty useless, but a constrained type:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
allows you to pattern match to peek inside the "box" and make the type class facilities available:
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
Note that this works for any type class, not just Typeable or Data.
With regard to your confusion about page 20 of the slide deck, the author is saying that it's impossible for a function that takes an existential Worker to demand a Worker having a particular Buffer instance. You can write a function to create a Worker using a particular type of Buffer, like MemoryBuffer:
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
but if you write a function that takes a Worker as argument, it can only use the general Buffer type class facilities (e.g., the function output):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
It can't try to demand that b be a particular type of buffer, even via pattern matching:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
Finally, runtime information about existential types is made available through implicit "dictionary" arguments for the typeclasses that are involved. The Worker type above, in addtion to having fields for the buffer and input, also has an invisible implicit field that points to the Buffer dictionary (somewhat like v-table, though it's hardly huge, as it just contains a pointer to the appropriate output function).
Internally, the type class Buffer is represented as a data type with function fields, and instances are "dictionaries" of this type:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
The existential type has a hidden field for this dictionary:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
and a function like doWork that operates on existential Worker' values is implemented as:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
For a type class with only one function, the dictionary is actually optimized to a newtype, so in this example, the existential Worker type includes a hidden field that consists of a function pointer to the output function for the buffer, and that's the only runtime information needed by doWork.
In Page 20 of above PDF it is mentioned for below code that it is impossible for a Function to demand specific Buffer. Why is it so?
Because Worker, as defined, takes only one argument, the type of the "input" field (type variable x). E.g. Worker Int is a type. The type variable b, instead, is not a parameter of Worker, but is a sort of "local variable", so to speak. It can not be passed as in Worker Int String -- that would trigger a type error.
If we instead defined:
data Worker x b = Worker {buffer :: b, input :: x}
then Worker Int String would work, but the type is no longer existential -- we now always have to pass the buffer type as well.
As Haskell is a Full Type Erasure language like C then How does it know at Runtime which function to call. Is it something like we gonna maintain few information and pass in a Huge V-Table of Functions and at runtime it gonna figure out from V-Table? If it is so then what sort of Information it gonna store?
This is roughly correct. Briefly put, each time you apply constructor Worker, GHC infers the b type from the arguments of Worker, and then searches for an instance Buffer b. If that is found, GHC includes an additional pointer to the instance in the object. In its simplest form, this is not too different from the "pointer to vtable" which is added to each object in OOP when virtual functions are present.
In the general case, it can be much more complex, though. The compiler might use a different representation and add more pointers instead of a single one (say, directly adding the pointers to all the instance methods), if that speeds up code. Also, sometimes the compiler needs to use multiple instances to satisfy a constraint. E.g., if we need to store the instance for Eq [Int] ... then there is not one but two: one for Int and one for lists, and the two needs to be combined (at run time, barring optimizations).
It is hard to guess exactly what GHC does in each case: that depends on a ton of optimizations which might or might not trigger.
You could try googling for the "dictionary based" implementation of type classes to see more about what's going on. You can also ask GHC to print the internal optimized Core with -ddump-simpl and observe the dictionaries being constructed, stored, and passed around. I have to warn you: Core is rather low level, and can be hard to read at first.

Cloud Haskell - How to write "pure" for Closures?

I've been playing with Cloud Haskell. I've noticed in the hackage documentation there's a kind of applicative interface. But in particular I'm trying to find or write a function closurePure with the following signature:
closurePure :: (Typeable a, Binary a) => a -> Closure a
This is basically a restricted version of pure.
Whilst the Closure datatype itself is abstract, the following closure provided:
closure :: Static (ByteString -> a) -> ByteString -> Closure a
So I can get this far:
closurePure :: (Typeable a, Binary a) => a -> Closure a
closurePure x = closure ??? (encode x)
The problem is what to put where the ???s are.
My first attempt was the following:
myDecode :: (Typeable a, Binary a) => Static (ByteString -> a)
myDecode = staticPtr (static decode)
But upon reading the GHC docs on static pointers, the show example suggested to me that you can't have a constraint because a constrained function doesn't have a Typeable instance. So I tried the work around suggested using Dict:
myDecode :: Typeable a => Static (Dict (Binary a) -> ByteString -> a)
myDecode = staticPtr (static (\Dict -> decode))
But now I've got the wrong type that doesn't fit into the closure function above.
Is there anyway to write closurePure or something similar (or have I missed it in the Cloud Haskell docs)? Raising binary plain types to Closures seems essential to using the applicative interface given, but I can't work out how to do it.
Note that I can do this:
class StaticDecode a where
staticPtrDecode :: StaticPtr (ByteString -> a)
instance StaticDecode Int where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Float where
staticPtrDecode = static Data.Binary.decode
instance StaticDecode Integer where
staticPtrDecode = static Data.Binary.decode
-- More instances etc...
myPure :: forall a. (Typeable a, StaticDecode a, Binary a) => a -> Closure a
myPure x = closure (staticPtr staticPtrDecode) (encode x)
Which works well but basically requires me to repeat an instance for each Binary instance. It seems messy and I'd prefer another way.
You're right, Closure has an applicative-like structure, a fact made even more explicit in both the interface and the implementation of distributed-closure. It's not quite applicative, because in the pure case we do have the additional constraint that the argument must somehow be serializable.
Actually, we have a stronger constraint. Not only must the argument be serializable, but the constraint must itself be serializable. Just like it's hard to serialize functions directly, you can imagine that it's hard to serialize constraints. But just like for functions, the trick is to serialize a static pointer to the constraint itself, if such a static pointer exists. How do we know that such a pointer exists? We could introduce a type class with a single method that gives us the name of the pointer, given a constraint:
class GimmeStaticPtr c where
gimmeStaticPtr :: StaticPtr (Dict c)
There's a slight technical trick going on here. The kind of the type index for StaticPtr is the kind *, whereas a constraint has kind Constraint. So we reuse a trick from the constraints library that consists in wrapping a constraint into a data type (Dict above), which like all data types is of kind *. Constraints that have an associated GimmeStaticPtr instance are called static constraints.
In general, it's sometimes useful to compose static constraints to get more static constraints. StaticPtr is not composable, but Closure is. so what distributed-closure actually does is define a similar class, that we'll call,
class GimmeClosure c where
gimmeClosure :: Closure (Dict c)
Now we can define closurePure in a similar way that you did:
closurePure :: (Typeable a, GimmeClosure (Binary a)) => a -> Closure a
It would be great if in the future, the compiler could resolve GimmeClosure constraints on-the-fly by generating static pointers as needed. But for now, the thing that comes closest is Template Haskell. distributed-closure provides a module to autogenerate GimmeClosure (Cls a) constraints at the definition site for class Cls. See withStatic here.
Incidentally, Edsko de Vries gave a great talk about distributed-closure and the ideas embodied therein.
Let's take a moment to consider what you are asking for. Recall that typeclasses are basically shorthand for dictionary passing. So let's rewrite:
data BinaryDict a = BinaryDict
{ bdEncode :: a -> ByteString
, bdDecode :: ByteString -> a
}
Now you wish to write a function:
closurePure :: (Typeable a) => BinaryDict a -> a -> Closure a
Your attempt is:
closurePure bdict = closure (staticPtr (static (bdDecode bdict))) . bdEncode bdict
Now that we can see what's going on explicitly, we can see that static's argument cannot be closed. If BinaryDicts were allowed to be created willy nilly, say from user data, this function would be impossible. We would instead need:
closurePure :: (Typeable a) => Static (BinaryDict a) -> a -> Closure a
That is, we need entries for the needed Binary instances in the static pointer table. Hence your enumeration solution, and why I suspect that such a solution is required. We also can't expect to enumerate it too automatically, because there are infinitely many instances.
It seems silly to me, however, since instances seem like just the sorts of things that you would want to be static automatically. They are static by nature (what's that, reflection? I can't hear you). This was probably at least ruminated about in the distributed Haskell papers (I haven't read them).
We could solve this problem in general by simply creating a class that concretely enumerates every instance of every class (déjà vu?).
class c => StaticConstraint c where
staticConstraint :: StaticPtr (Dict c)
instance StaticConstraint (Show Int) where
staticConstraint = static Dict
-- a handful more lines...
Somewhat more seriously, if you really don't want to enumerate (I don't blame you), you can at least ease the pain with a calling convention:
closurePure :: (Typeable a, Binary a) => StaticPtr (ByteString -> a) -> a -> Closure a
closurePure decodePtr = closure (staticPtr decodePtr) . encode
someClosure :: Closure Int
someClosure = closurePure (static decode) 42
This nonsense is necessary because static is a "syntactic form" rather than a function -- by mentioning it, we indicate that the Binary instance for Int must actually be generated and recorded in the static pointer table.
If you are feeling cheeky you could enable {-# LANGUAGE CPP #-} and
-- PURE :: (Binary a, Typeable a) => a -> Closure a, I promise
#define PURE (closurePure (static decode))
someClosure :: Closure Int
someClosure = PURE 42
Maybe someday Haskell will take the next step and graduate to the time-tested Segmentation fault (core dumped) of its predecessors instead of spouting off those arrogant type errors.

Deserializing an existential data type

I need to write a Serialize instance for the following data type:
data AnyNode = forall n . (Typeable n, Serialize n) => AnyNode n
Serializing this is no problem, but I can't implement deserialization, since the compiler has no way to resolve the specific instance of Serialize n, since the n is isolated from the outer scope.
There's been a related discussion in 2006. I am now wondering whether any sort of solution or a workaround has arrived today.
You just tag the type when you serialize, and use a dictionary to untag the type when you deserialize. Here's some pseudocode omitting error checking etc:
serialAnyNode (AnyNode x) = serialize (typeOf n, serialize x)
deserialAnyNode s = case deserialize s of
(typ,bs) -> case typ of
"String" -> AnyNode (deserialize bs :: String)
"Int" -> AnyNode (deserialize bs :: Int)
....
Note that you can only deserialize a closed universe of types with your function. With some extra work, you can also deserialize derived types like tuples, maybes and eithers.
But if I were to declare an entirely new type "Gotcha" deriving Typeable and Serialize, deserialAnyNode of course couldn't deal with it without extension.
You need to have some kind of centralised "registry" of deserialization functions so you can dispatch on the actual type (extracted from the Typeable information). If all types you want to deserialize are in the same module this is pretty easy to set up. If they are in multiple modules you need to have one module that has the mapping.
If your collection of types is more dynamic and not easily available at compile time, you can perhaps use the dynamic linking to gain access to the deserializers. For each type that you want to deserialize you export a C callable function with a name derived from the Typeable information (you could use TH to generate these). Then at runtime, when you want to deserialize a type, generate the same name and the use the dynamic linker to get hold of the address of the function and then an FFI wrapper to get a Haskell callable function. This is a rather involved process, but it can be wrapped up in a library. No, sorry, I don't have such a library.
It's hard to tell what you're asking here, exactly. You can certainly pick a particular type T, deserialize a ByteString to it, and store it in an AnyNode. That doesn't do the user of an AnyNode much good, though -- you still picked T, after all. If it wasn't for the Typeable constraint, the user wouldn't even be able to tell what the type is (so let's get rid of the Typeable constraint because it makes things messier). Maybe what you want is a universal instead of an existential.
Let's split Serialize up into two classes -- call them Read and Show -- and simplify them a bit (so e.g. read can't fail).
So we have
class Show a where show :: a -> String
class Read a where read :: String -> a
We can make an existential container for a Show-able value:
data ShowEx where
ShowEx :: forall a. Show a => a -> ShowEx
-- non-GADT: data ShowEx = forall a. Show a => ShowEx a
But of course ShowEx is isomorphic to String, so there isn't a whole lot point to this. But note that an existential for Read is has even less point:
data ReadEx where
ReadEx :: forall a. Read a => a -> ReadEx
-- non-GADT: data ReadEx = forall a. Read a => ReadEx a
When I give you a ReadEx -- i.e. ∃a. Read a *> a -- it means that you have a value of some type, and you don't know what the type is, but you can a String into another value of the same type. But you can't do anything with it! read only produces as, but that doesn't do you any good when you don't know what a is.
What you might want with Read would be a type that lets the caller choose -- i.e., a universal. Something like
newtype ReadUn where
ReadUn :: (forall a. Read a => a) -> ReadUn
-- non-GADT: newtype ReadUn = ReadUn (forall a. Read a => a)
(Like ReadEx, you could make ShowUn -- i.e. ∀a. Show a => a -- and it would be just as useless.)
Note that ShowEx is essentially the argument to show -- i.e. show :: (∃a. Show a *> a) -> String -- and ReadUn is essentially the return value of read -- i.e. read :: String -> (∀a. Read a => a).
So what are you asking for, an existential or a universal? You can certainly make something like ∀a. (Show a, Read a) => a or ∃a. (Show a, Read a) *> a, but neither does you much good here. The real issue is the quantifier.
(I asked a question a while ago where I talked about some of this in another context.)

Transparently implementing a particular form of dynamic typing

The basic idea is that I have a range of functions that work on any types from a particular class, but at runtime the program is supposed to read a configuration file and extract an element of one of the types in the class.
For instance, I have a 'Coefficient' class, various instances of it, and functions of various types that are polymorphic over types of that class; at runtime one particular type of that class is to be determined, and passed around.
I'm unsure how to properly address this; I tried making up 'compound' types, doing something like:
data CompoundCoeff = CompoundInt Int | CompoundDouble Double | ...
where Int, Double, ... are instances of the class 'Coefficient'.
However, it started to become a big effort to adapt all the functions involved in the code to work with these compound types (and it's not a nice solution either, really). It would be OK if all functions had the same, easy type, e.g.
Coefficient a => a -> (stuff not involving a anymore)
but that's unfortunately not the case.
Another issue I ran into, is that I'm using type families, and have something like
class (Monoid (ColourData c), Coordinate (InputData c)) => ColourScheme c where
type ColourData c :: *
type InputData c :: *
colouriseData :: c -> (ColourData c) -> AlphaColour Double
processInput :: c -> InputData c -> ColourData c
This doesn't go through cleanly if I have to use some sort of compound ColourData datatype, like the previous one; in particular I can no longer guarantee that the data stream gives a consistent type (and not just different 'subtypes' of a compound type), and would (among other things) have to make up a bogus Monoid instance if I did make up a compound ColourData type.
I've also looked into Data.Dynamic, but again I can't see how it would properly address the issues; the exact same problems seem to appear (well, slightly worse even, given that there is only one 'generic' Dynamic type as I understand it).
Question: How can I implement dynamic datatypes subordinate to particular classes, without having to rewrite all the functions involving those data types? It would be best if I didn't have to sacrifice any type safety, but I'm not too optimistic.
The program is supposed to read a configuration file at runtime, and all the requisite functions, polymorphic over the relevant class, are to be applied.
The traditional way to provide an object that guarantees that it is an instance of typeclass Foo, but makes no additional guarantees, is like so:
{-# LANGUAGE ExistentialTypes #-}
data SomeFoo = forall a . Foo a => SomeFoo a
instance Foo SomeFoo where
-- all operations just unwrap the SomeFoo straightforwardly
or, with GADTs, which might be more readable...
data SomeFoo where
SomeFoo :: Foo a => a -> SomeFoo
One proposal would be to write a single top-level function that does all the finishing touches once you've chosen a type:
topLevel :: SomeTypeClass a => a -> IO ()
Your program can then be written something like this:
main = do
config <- readConfig
case config of
UseDouble n -> topLevel n
UseSymbolic x -> topLevel x
UseWidgetFrobnosticator wf -> topLevel wf

Polymorphic Return Types Depending on Context

I'm playing around with implementing a Redis client-library in Haskell and it is my goal to encode, as much as possible, the semantics of the Redis commands in Haskell's type system. Redis, for those who don't know, is a datastore, accessed over the network. I will use it to exemplify my problem, but Redis is not the focus of this question.
An Example Function
Consider the function
get :: (RedisValue a) => Key -> Redis a
get k = decodeValue <$> sendCommand ["GET", key]
It sends a command to the datastore and returns a value stored under the given Key (for this example, you can consider type Key = String). As for the return-type:
Redis is an instance of Monad and MonadIO. It encapsulates information about the network connection. sendCommand sends the request and returns the datastore's reply.
a is polymorphic, for example either Strings or ByteStrings can be returned, depending on the context.
The following code should clarify the text above.
data Redis a = ...
instance MonadIO Redis where ...
instance Monad Redis where ...
sendCommand :: [String] -> Redis String
class RedisValue a where
decodeValue :: String -> a
-- example instances
instance RedisValue String where ...
instance RedisValue ByteString where ...
Different Context, Different Types
Redis supports a simple form of transactions. In a transaction most commands can be sent the same as outside of a transaction. However their execution is delayed until the user sends the commit command (which is called exec in Redis). Inside the transaction, the datastore only returns an acknowledgment that the command is stored for later execution. Upon commit (exec) all results of all stored commands are returned.
This means that the get-function from above looks a bit different in the context of a transaction:
get :: (RedisStatus a) => Key -> RedisTransaction a
get k = decodeStatus <$> sendCommand ["GET", key]
Note that:
The monadic type is now RedisTransaction to indicate the transaction context.
The a return type is now any instance of RedisStatus. There is an overlap between instances of RedisValue and RedisStatus. For example String is in both classes. A specialized Status data type might be only in the RedisStatus class.
The Actual Question
My question is, how can I write a function get that works in both contexts, with context-appropriate return type classes. What I need is
a way to give get a return type "either Redis or RedisTransaction",
The type a to be an instance of RedisValue in the Redis context and an instance of RedisStatus in the RedisTransaction context.
A function decode that automagically does the right thing, depending on the context. I assume this must come from a (multi-param) type class.
If you know how I can do this or have a pointer to some example code or even an article, you will have my thanks!
First, I think that it would be better to have two different get commands. That said, here's an approach.
class RedisGet m a where
get :: Key -> m a
instance (RedisValue a) => RedisGet Redis a where...
instance (RedisStatus a) => RedisGet RedisTransaction a where...
You need
MPTCs, but no FunDeps or Type Families. Every use of get requires that enough information be available to determine both the m and a uniquely.
I agree that multiparameter type classes are a good fit here. Here's an approach:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
newtype Redis a = Redis (IO a) deriving Monad
newtype RedisTransaction a = RedisTransaction (IO a) deriving Monad
newtype Key = Key {unKey :: String}
newtype Value = Value {unValue :: String}
newtype Status = Status {unStatus :: String}
class Monad m => RedisMonad m a | m -> a where
sendCommand :: [String] -> m a
instance RedisMonad Redis Value where
sendCommand = undefined -- TODO: provide implementation
instance RedisMonad RedisTransaction Status where
sendCommand = undefined -- TODO: provide implementation
class Decodable a b where
decode :: a -> b
instance Decodable Status String where
decode = unStatus
instance Decodable Value String where
decode = unValue
get :: (RedisMonad m a, Decodable a b) => Key -> m b
get k = do
response <- sendCommand ["GET", unKey k]
return (decode response)
Note the use of the type isomorphisms for Value and Status: it makes things slightly stronger typed as the Strings you are having produced by your implementations of sendCommand obviously are not just arbitrary sequences of characters but instead adhere to some fixed formats for return values and statuses.
Keep in mind that there's nothing special about a type depending on context--that happens all the time with type inference. The type of [] is [a], but when you use it in something like True : [] the type will be specialized to [Bool] in context.
What changes matters is if you want the implementation of a function, or the definition of a value, to depend on its type. If that type is then inferred from context in the normal way, you end up with a function that does something "different" depending on context. Type-dependent implementation is the main purpose of using type classes.
Now, to answer your specific questions:
a way to give get a return type "either Redis or RedisTransaction",
This requires only a variable in the type signature of get, e.g. get :: Key -> f a. The f will be filled in as either Redis or RedisTransaction depending on context.
The type a to be an instance of RedisValue in the Redis context and an instance of RedisStatus in the RedisTransaction context.
Since both a and the context type will be inferred from use, what you're really after here is restricting the possible types, which amounts to expecting a type-checking error if they don't match up. This is another purpose of type classes, and could be implemented with an appropriate class constraint on the context type variable:
get :: (ContextValue (f a)) => Key -> f a
class ContextValue a
instance (RedisValue a) => ContextValue (Redis a)
instance (RedisStatus a) => ContextValue (RedisTransaction a)
Or something like that. But that alone isn't enough for your purposes, because...
A function decode that automagically does the right thing, depending on the context. I assume this must come from a (multi-param) type class.
This implies selecting an implementation for decode based on the type, which means making it part of a type class such as the above ContextValue. How you handle this depends on what the type for decode needs to be--if the result type needs to be something like f String -> f a where f is the monadic context, then you'll probably need something a bit more elaborate, like in dblhelix's answer. If you only need String -> f a, then you could add it to the above ContextValue class directly.

Resources