Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Covering all the cases of a promoted datatype

So I recently came up with this neat idea, in the hope of sharing code between the strict and lazy State transformer modules:

{-# LANGUAGE FlexibleInstances, DataKinds, KindSignatures #-}
module State where

data Strictness = Strict | Lazy
newtype State (t :: Strictness) s a = State (s -> (s, a))

returnState :: a -> State t s a
returnState x = State $ \s -> (s, x)

instance Monad (State Lazy s) where
  return = returnState
  State ma >>= amb = State $ \s -> case ma s of
    ~(s', x) -> runState (amb x) s'

instance Monad (State Strict s) where
  return = returnState
  State ma >>= amb = State $ \s -> case ma s of
    (s', x) -> runState (amb x) s'

get :: State t s s
get = State $ \s -> (s, s)

put :: s -> State t s ()
put s = State $ \_ -> (s, ())

You can see that get and put both work without any duplication – no type class instances, no anything – on both the strict and lazy types. However, even though I cover both possible cases for Strictness, I don't have a Monad instance for State t s a in general:

-- from http://blog.melding-monads.com/2009/12/30/fun-with-the-lazy-state-monad/
pro :: State t [Bool] ()
pro = do
  pro
  s <- get
  put (True : s)

-- No instance for (Monad (State t [Bool])) arising from a do statement

The following works fine, albeit requiring FlexibleContexts:

pro :: (Monad (State t [Bool])) => State t [Bool] ()
-- otherwise as before

Then I can instantiate t at Lazy or Strict and run the result and get what I expect. But why do I have to give that context? Is this a conceptual limitation, or a practical one? Is there some reason I'm missing why Monad (State t s a) actually doesn't hold, or is there just no way to convince GHC of it yet?

(Aside: using the context Monad (State t s) doesn't work:

Could not deduce (Monad (State t [Bool])) arising from a do statement from the context (Monad (State t s))

which just confuses me even more. Surely the former is deducible from the latter?)

like image 733
Ben Millwood Avatar asked Jan 11 '13 00:01

Ben Millwood


People also ask

What is data promotion?

Data types can be classified into groups of related data types. Within such groups, a precedence order exists where one data type is considered to precede another data type. This precedence is used to allow the promotion of one data type to a data type later in the precedence ordering.

How is data type promotion done in an expression?

Some data types like char , short int take less number of bytes than int, these data types are automatically promoted to int or unsigned int when an operation is performed on them. This is called integer promotion. For example no arithmetic calculation happens on smaller types like char, short and enum.

What is data type promotion in Java?

Type Promotion in ExpressionsJava automatically promotes each byte, short, or char operand to int when evaluating an expression. If one operand is long, float or double the whole expression is promoted to long, float, or double respectively.


1 Answers

This is a limitation, but one with good reason: if it did not work that way what would the expected semantics of

runState :: State t s a -> s -> (s,a)
runState (State f) s = f s

example :: s -> a
example = snd $ runState ((State undefined) >> return 1) ()

well, it could be

example = snd $ runState ((State undefined) >>= \_ -> return 1) ()
        = snd $ runState (State $ \s -> case undefined s of (s',_) -> (s',1)) ()
        = snd $ case undefined () of (s',_) -> (s',1)
        = snd $ case undefined of (s',_) -> (s',1)
        = snd undefined
        = undefined

or it could be

example = snd $ runState ((State undefined) >>= \_ -> return 1) ()
        = snd $ runState (State $ \s -> case undefined s of ~(s',_) -> (s',1)) ()
        = snd $ case undefined () of ~(s',_) -> (s',1)
        = snd $ (undefined,1)
        = 1

these are not the same. One option would be to define a function an extra class, like

class IsStrictness t where
   bindState :: State t s a -> (a -> State t s b) -> State t s b

and then then define

instance IsStrictness t => Monad (State t s) where
   return = returnState
   (>>=) = bindState

and instead of defining bindState as part of IsStrictness, you can use a singleton

data SingStrictness (t :: Strictness) where
   SingStrict :: SingStrictness Strict
   SingLazy   :: SingStrictness Lazy

class IsStrictness t where
   singStrictness :: SingStrictness t

bindState :: IsStrictness t => State t s a -> (a -> State t s b) -> State t s b
bindState ma' amb' = go singStrictness ma' amb' where
  go :: SingStrictness t -> State t s a -> (a -> State t s b) -> State t s b
  go SingStrict ma amb = ...
  go SingLazy ma amb = ...

which can be enhanced even further using the singelton infrastructures from GHC 7.6 instead of the custom class and singleton type. You will end up with

instance SingI t => Monad (State t s)

which is really not so scary. Get used to having lots of SingI _ in your constraint sets. This is how it is going to work for at least a while, and isn't that ugly.

As to why State t [Bool] is not deducible from State t s: the problem is that State t s is in your top level context, which means that s is quantified at the outermost level. You are defining a function which says "for any t and s such that Monad (State t s) I will give you ...". But, this doesn't say "for any t such that Monad (State t [Bool]) I will give you ...". Sadly, these universally quantified constraints are not so easy in Haskell.

like image 60
Philip JF Avatar answered Sep 30 '22 12:09

Philip JF