Motivation of having Functional Dependencies - haskell

What is the motivation of having functional dependencies in Haskell ?
One example of a functional dependency:
class (Monad m) => MonadSupply s m | m -> s where
next :: m (Maybe s)
It is stated in the RWH book, that functional dependency helps the type checker. How does it actually help ?
Also, this piece of code actually compiles:
class (Monad m) => MonadSupply s m where
next :: m (Maybe s)
But I guess, it will produce an runtime error.

It's perfectly fine to write code not using functional dependencies, it's just a pain to use since the inference sucks.
Basically without FDs, the function get :: MonadState m s => m s will have to figure out m and s independently. Usually m is quite easily inferred, but often s would require an explicit annotation.
Moreover, this is much more general than we need, so instead we can restrict our typechecker to say "For m, there is exactly 1 s", this way, once m is inferred, s is obvious to the type inference algorithm

Related

How to abstract over monads without fighting the type system in Haskell?

I'm currently building a server in haskell and as a newbie to the language, I'd like to try a new approach zu Monad composition. The idea is that we can write library methods like
isGetRequest :: (SupportsRequests m r) => m Bool
isGetRequest = do
method <- liftRequests $ requestMethod
return $ method == GET
class (Monad m, RequestSupport r) => SupportsRequests m r | m -> r where
liftRequests :: r a -> m a
class (Monad r) => RequestSupport r where
requestMethod :: r Method
which work without knowing the underlying monad. Of course in this example, it would have been sufficient to make isGetRequest operate directly on the (RequestSupport r) monad but the idea is that my library might also have more than one constraint on the monad. Yet, I do not want to implement all of those different concerns in the same module nor spread them across different modules (orphan instances!).
That's why the m monad only implements the Supports* classes, delegating the real concerns to other monads.
The above code should work perfectly (with some language extensions to GHC). Unfortunately, I got some problems with the CRUD (Create Read Update Delete) concern:
class (Monad m, CRUDSupport c a) => SupportsCRUD m c a | m a -> c where
liftCRUD :: c x -> m x
class (Monad c) => CRUDSupport c a | c -> a where
list :: c [a] -- List all entities of type a
No I get an error:
Could not deduce (SupportsCRUD m c a0) from the context [...]
The type variable 'a0' is ambiguous [...]
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method: liftCRUD [...]
Seems like the type checker doesn't like that the a parameter does not directly arise in the signature of liftCRUD. That's understandable because a cannot be derived from the functional dependencies.
The type checker in my brain tells me that it should not be a problem to infer the type a later on, using AllowAmbiguousTypes, when some method regarding CRUD is executed in a library method. Unfortunately, GHC seems unable to do this inference step, for example
bookAvailable :: (SupportsCRUD m c Book) => m Bool
bookAvailable = do
books <- liftCRUD (list :: c [Book]) -- I use ScopedTypeVariables
case books of
[] -> return False
_ -> return True
yields
Could not deduce (SupportsCRUD m c0 a1) arising from a use of 'liftCRUD' [...]
The type variables c0, a1 are ambiguous [...]
It seems that I am still unable to reason about the compiler. I there a way to resolve this problem? Or at least a way to understand what the compiler is able to infer?
Best Regards,
bloxx
To use ScopedTypeVariables you also need to bind the variables you want to be in scope with forall. So it should be
bookAvailable :: forall m c. (SupportsCRUD m c Book) => m Bool
...
That was all that was necessary (after some trivial fixes I made which I assume were typos from entering your question) for me to get the code to compile.

How to use a typeclass like `HasDynFlags m` in GHC

While playing with GHC code base, I find a typeclass named HasDynFlags:
class HasDynFlags m where
getDynFlags :: m DynFlags
Although the typeclass name looks self-explanatory, I couldn't find other
constraints in the typeclass definition that says m has to be Monad or at least Functor so we can get access to that value.
However, most use of it I find in the code base is inside a do-notation, e.g dynFlag <- getDynFlags where m is further constrainted to be an instance of Monad.
My questions are:
For HasDynFlags m, does m have to be at least Functor to make this typeclass useful?
If the answer to the first question is no, then how are we supposed to get access to a value of DynFlags given getDynFlags :: m DynFlags, without any further knowledge about m?
According to the class definition,
class HasDynFlags m where
getDynFlags :: m DynFlags
m is satisfied by kind (* -> *). The kind (* -> *) is implied by the type m DynFlags, which demonstrates that m is a type constructor taking exactly one type parameter.
There are no further constraints on m here. Specifically, the resulting type needn't be a Functor (or Monad), although given common naming conventions for type variables in Haskell, there's a good chance Monad is the motivating case.
EDIT: To answer the second question, the Functor or Monad class constraints we expect are introduced in more specific contexts. For example, consider the type,
(HasDynFlags m, Monad m) => m DynFlags
I think that's all there is to it.

Why were Haskell 98's standard classes made inferior to Haskell 1.3's?

Before Haskell 98, there were Haskell 1.0 through 1.4. It's pretty interesting to see the development throughout the years, as features were added to the earliest versions of standardized Haskell.
For instance, the do-notation was first standardized by Haskell 1.3 (published 1996-05-01). In the Prelude, we find the following definitions (page 87):
-- Monadic classes
class Functor f where
map :: (a -> b) -> f a -> f b
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
m >> k = m >>= \_ -> k
class (Monad m) => MonadZero m where
zero :: m a
class (MonadZero m) => MonadPlus m where
(++) :: m a -> m a -> m a
The same definitions are found in Haskell 1.4. I do have a few problems with this (e.g. the MonadPlus reform hasn't happened here yet), but overall, it is a very nice definition.
This is very different from Haskell 98, where the following definition is found:
-- Monadic classes
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
-- Minimal complete definition:
-- (>>=), return
m >> k = m >>= \_ -> k
fail s = error s
This is also the definition in Haskell 2010. I have the following problems with this definition:
MonadZero and MonadPlus are gone. They were useful classes.
In case of a pattern match failure in a do-notation...
Haskell 1.3 uses zero. The Left Zero law applies (zero >>= k = zero), so you know what's supposed to happen.
Haskell 98 uses fail msg, where msg is compiler-generated in case of GHC. Anything can happen, no guarantees about its semantics. Therefore, it's not much of a function for users. As a consequence, the behaviour of pattern match failures in Haskell 98's do-notation is unpredictable!
Names are less general (e.g. map vs. fmap). Not a big problem, but it's a thorn in my eye.
All in all, I think these changes weren't for the best. In fact, I think they were a step backwards from Haskell 1.4. Why were these things changed for Haskell 98, and why in this way?
As an aside, I can imagine the following defenses:
"fail allows for locating errors." Only for programmers, and only at runtime. The (unportable!) error message is not exactly something you want to parse. If you really care about it, you should track it explicitly. We now have Control.Failure from the failure package, which does a much better job at this (failure x behaves mostly like zero).
"Having too many classes makes development and use too hard." Having too few classes breaks their laws, and those laws are just as important as types.
"Instance-restricted functions are easier to learn." Then why isn't there a SimplePrelude instead, with most of the classes removed? It's only one magical declaration away for students, they can manage that much. (Perhaps {-# LANGUAGE RebindableSyntax #-} is needed too, but again, students are very good at copy-pasting stuff.)
"Instance-restricted functions make errors more readable." I use fmap much more often than map, so why not map and listMap instead?
Why were these things changed for Haskell 98, and why in this way?
Haskell 98 involves a lot of simplification to the language (much of which has since been reversed). The goal was to improve Haskell as a teaching language, and to make relatively conservative choices.
See e.g.
We regarded Haskell 98 as a reasonably conservative design. For
example, by that time multi-parameter type classes were being widely
used, but Haskell 98 only has single-parameter type classes (Peyton
Jones et al., 1997).
In: History of Haskell
And:
Haskell 98 will by no means be the last revision of Haskell. On the
contrary, we design it knowing that new language extensions
(multi-parameter type classes, universal and existential
quantification, pattern guards, etc, etc) are well on the way.
However, Haskell 98 will have a special status: the intention is that
Haskell compilers will continue to support Haskell 98 (given an
appropriate flag) even after later versions of the language have been
defined, and so the name `Haskell 98' will refer to a fixed, stable
language.
In: Haskell98 report
So, things were simplified, with the goal of producing a simpler standard.

Why aren't monad transformers constrained to yield monads?

In the MonadTrans class:
class MonadTrans t where
-- | Lift a computation from the argument monad to the constructed monad.
lift :: Monad m => m a -> t m a
why isn't t m constrained to be a Monad? i.e., why not:
{-# LANGUAGE MultiParamTypeClasses #-}
class Monad (t m) => MonadTrans t m where
lift :: Monad m => m a -> t m a
If the answer is "because that's just the way it is", that's fine -- it's just confusing for a n008.
You suggested the following:
class Monad (t m) => MonadTrans t m where
lift :: Monad m => m a -> t m a
...but does that really mean what you want? It seems you want to express something like "a type t may be an instance of MonadTrans if, for all m :: * -> * where m is an instance of Monad, t m is also an instance of Monad".
What the class definition above actually says is more like "types t and m may constitute an instance of MonadTrans if, for those specific types, t m is an instance of Monad". Consider carefully the difference, and the implied potential for instances that may not be what you'd want.
In the general case, every parameter of a type class is an independent "argument", a fact which has been a bountiful source of both headaches and GHC extensions as people have attempted to use MPTCs.
Which isn't to say that such a definition couldn't be used anyway--as you point out, the current definition is not ideal either. The age-old problem "Why Data.Set Is Not a Functor" is related, and such issues helped motivate the recent ConstraintKinds tomfoolery.
The ultimate answer to "why not" here is almost certainly the one given by Daniel Fischer in the comments--because MonadTrans is pretty core functionality, it would be undesirable to make it depend on some terrifying cascade of increasingly arcane GHC extensions.

Linking/Combining Type Classes in Haskell

Say I have two type classes defined as follows that are identical in function but different in names:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
class PhantomMonad p where
pbind :: p a -> (a -> p b) -> p b
preturn :: a -> p a
Is there a way to tie these two classes together so something that is an instance of PhantomMonad will automatically be an instance of Monad, or will instances for each class have to be explicitly written? Any insight would be most appreciated, thanks!
Good answer: No, what you're hoping to do isn't really viable. You can write an instance that looks like it does what you want, possibly needing some GHC extensions in the process, but it won't work the way you you'd like it to.
Unwise answer: You can probably accomplish what you want using scary type-level metaprogramming, but it may get complicated. This really isn't recommended unless you absolutely need this to work for some reason.
Officially instances can't really depend on other instances, because GHC only looks at the "instance head" when making decisions, and class constraints are in the "context". To make something like a "type class synonym" here, you'd have to write what looks like an instance of Monad for all possible types, which obviously doesn't make sense. You'll be overlapping with other instances of Monad, which has its own problems.
On top of all that, I don't think such an instance will satisfy the termination check requirements for instance resolution, so you'd also need the UndecidableInstances extension, which means the ability to write instances that will send GHC's type checker into an infinite loop.
If you really want to go down that rabbit hole, browse around on Oleg Kiselyov's website a bit; he's sort of the patron saint of type-level metaprogramming in Haskell.
It's fun stuff, to be sure, but if you just want to write code and have it work, probably not worth the pain.
Edit: Okay, in hindsight I've overstated the issue here. Something like PhantomMonad works fine as a one-off and should do what you want, given the Overlapping- and UndecidableInstances GHC extensions. The complicated stuff starts up when you want to do anything much more complicated than what's in the question. My sincere thanks to Norman Ramsey for calling me on it--I really should have known better.
I still don't really recommend doing this sort of thing without good reason, but it's not as bad as I made it sound. Mea culpa.
That's an unusual design. Can you not just remove the PhantomMonad, since it is isomorphic to the other class.
Is there a way to tie these two classes together so something that is an instance of PhantomMonad will automatically be an instance of Monad?
Yes, but it requires the slightly alarming language extensions FlexibleInstances and UndecidableInstances:
instance (PhantomMonad m) => Monad m where
return = preturn
(>>=) = pbind
FlexibleInstances is not so bad, but the risk of undecidability is slightly more alarming. The issue is that in the inference rule, nothing is getting smaller, so if you combine this instance declaration with another similar one (like say the reverse direction), you could easily get the type checker to loop forever.
I'm generally comfortable using FlexibleInstances, but I tend to avoid UndecidableInstances without a very good reason. Here I agree with Don Stewart's suggestion that you'd be better off using Monad to begin with. But your question is more in the nature of a thought experiment, the answer is that you can do what you want without getting into an Oleg level of scariness.
Another solution is to use newtype. This isn't exactly what you want, but is often used in such cases.
This allows linking different ways of specifying the same structure. For example, ArrowApply (from Control.Arrow) and Monad are equivalent. You can use Kleisli to make an ArrowApply out of a monad, and ArrowMonad to make a monad out of ArrowApply.
Also, one-way wrappers are possible: WrapMonad (in Control.Applicative) forms an applicative out of a monad.
class PhantomMonad p where
pbind :: p a -> (a -> p b) -> p b
preturn :: a -> p a
newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a }
newtype WrapReal m a = WrapReal { unWrapReal :: m a }
instance Monad m => PhantomMonad (WrapPhantom m) where
pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f))
preturn = WrapPhantom . return
instance PhantomMonad m => Monad (WrapReal m) where
WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f))
return = WrapReal . preturn
Though this doesn't really make sense, try
instance Monad m => PhantomMonad m where
pbind = (>>=)
preturn = return
(maybe with some compiler warnings deactivated).

Resources