For a uni assignment, we have been given a line of Haskell code which shows:
newtype TC a = TC ([Id] -> Either TypeError ([Id], a))
Firstly, TypeError is something which needs to be implemented by us for the assignment so I can't post the data declaration here, but my question is this. How do I read the code above? What is the a right after the newtype TC? I also don't understand how TC is being reused to the right of the equals sign.
I think a here is a type variable since newtype works similarly to data. I don't know how knowing this will help my understanding.
What is the a right after the newtype TC?
The a in the newtype declaration
newtype TC a = ...
expresses much the same as the x in a function declaration
f x = ...
a is a type parameter. So you'll be able to use TC as, for example, TC Int or TC Bool, similar to how you're able to use f like f 1 or f "bla bla" (depending on its type).
The case TC Int is equivalent to the following alternative:
newtype TCInt = TCInt ([Id] -> Either TypeError ([Id], Int))
I also don't understand how TC is being reused to the right of the equals sign.
That is a bit of a confusing quirk in Haskell. Actually TC is not reused, rather you're declaring two separate entities which are both called TC. You could also call them differently:
newtype TC_T a = TC_V ([Id] -> Either TypeError ([Id], a))
TC_T is a type constructor. This is the thing that will appear in type signatures, i.e. TC_T Int or TC_T Bool.
TC_V is a value constructor. You use this when, well, generating values of type TC_T Int or TC_T Bool.
So for example, you might write these:
tci :: TC_T Int
tci = TC_V (\ids -> Right (ids, 37))
tcb :: TC_T Bool
tcb = TC_V (\ids -> Right (reverse ids, False))
With your original version it looks like this:
tci :: TC Int
tci = TC (\ids -> Right (ids, 37))
tcb :: TC Bool
tcb = TC (\ids -> Right (reverse ids, False))
...but it's still two separate things both called TV here. Most newtypes in Haskell call the type- and value constructors the same, but often only the type constructor is exported from a module and the value constructor left as an implementation detail.
Most of this is the same for data as for newtype. You could as well have
data TC a = TC ([Id] -> Either TypeError ([Id], a))
... the only difference to the newtype version is a subtle indirection: if it's data, then the compiler inserts an indirection which allows a bit more lazyness, but in this case there's hardly a point to doing that.
In practice, you generally use data only when you need multiple constructors and/or a constructor with multiple fields, which newtype does not support.
The benefit of newtype over data is that you can derive via the type that underlies it:
{-# Language DerivingVia #-}
{-# Language StandaloneKindSignatures #-}
import Control.Applicative (Alternative)
import Control.Monad (MonadPlus)
import Control.Monad.Except (ExceptT(..), Except)
import Control.Monad.Fix (MonadFix)
import Control.Monad.State (StateT(..), MonadState)
import Data.Functor.Identity (Identity(..))
import Data.Kind (Type)
type TC :: Type -> Type
newtype TC a = MkTC ([Id] -> Either TypeError (a, [Id]))
deriving
( Functor, Applicative, Alterantive
, Monad, MonadPlus, MonadState [Id], MonadFix
)
via StateT [Id] (Except TypeError)
-- Alternative and MonadPlus rely on these
instance Semigroup TypeError ..
instance Monoid TypeError ..
I had to swap the tuple but this works because of three newtypes: TC a, StateT s m a and ExceptT. The compiler generates an instance for each newtype witnessing that it has the same runtime representation as the underlying type.
This is called representational equality and is witnessed by Coercible.
-- instance Coercible (StateT s m a) (s -> m (a, s))
type StateT :: Type -> (Type -> Type) -> (Type -> Type)
newtype StateT s m a = MkStateT (s -> m (a, s))
-- instance Coercible (ExceptT e m a) (m (Either e a))
type ExceptT :: Type -> (Type -> Type) -> (Type -> Type)
newtype ExceptT e m a = MkExceptT (m (Either e a))
This entails that TC a is coercible to StateT [Id] (Except TypeError) a and thus we can use coerce-based deriving strategies to carry instances between them (like GeneralizedNewtypeDeriving or DerivingVia).
TC a
={Coercible}
[Id] -> Either TypeError (a, [Id])
={Coercible}
[Id] -> ExceptT TypeError Identity (a, [Id])
={Except e = ExceptT e Identity}
[Id] -> Except TypeError (a, [Id])
={Coercible}
StateT [Id] (Except TypeError) a
GeneralizedNewtypeDeriving only works on newtypes but this does not mean that DerivingVia only works on newtypes. You can dervive many instances for data, for example by Applicative lifting methods over TC (Ap TC a).
Making it a newtype does simplify things, the Applicative can be derived through the state transformer so we don't need to write any instance by hand.
data TC a = MkTC ..
..
deriving (Semigroup, Monoid, Num, Bounded)
via Ap TC a
The TC name serves two purposes in your code ('punning'). I separate them into two different names, for TC and the transformers.
The left-hand side is the type constructor:
>> :k TC
TC :: Type -> Type
>> :k StateT
StateT :: Type -> (Type -> Type) -> (Type -> Type)
>> :k ExceptT
ExceptT :: Type -> (Type -> Type) -> (Type -> Type)
and the right-hand side is the value constructor, which I added a Mk for disambiguation:
>> :t MkTC
MkTC :: ([Id] -> Either TypeError (a, [Id])) -> TC a
>> :t MkStateT
MkStateT :: (s -> m (a, s)) -> StateT s m a
>> :t MkExceptT
MkExceptT :: m (Either e a) -> ExceptT e m a
The a :: Type is an argument to the type constructor:
TC :: Type -> Type
TC a :: Type
Without a type argument none of those instaces would kind-check because all of those instances require an "unary type constructor" (Type -> Type) as an argument. That is to say, a type parameterised by a type:
Functor :: (Type -> Type) -> Constraint
Applicative :: (Type -> Type) -> Constraint
Alternative :: (Type -> Type) -> Constraint
Monad :: (Type -> Type) -> Constraint
MonadPlus :: (Type -> Type) -> Constraint
MonadState [Id] :: (Type -> Type) -> Constraint
MonadFix :: (Type -> Type) -> Constraint
Related
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
In Haskell I can do something like
class C a where
c :: a -> a
instance C (f Integer) where
c = id
As seen, the instance of C is polymorphic, but only on the type constructor and not on the type parameter. What is the point of this feature? I have never seen this used, but since it is allowed I assume there is some case where it is useful. Is it?
You've seen it used. Here's some examples:
return :: Monad m => a -> m a
and :: Foldable f => f Bool -> Bool
traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
Each of these is polymorphic in a type with arrow kind -- respectively, m, f, and both f and t. If you must see it in an instance head, it's not hard to lift any of these to an analogous instance. For example:
class Bullish a where bull :: a -> Bool
instance Bullish Bool where bull = id
instance Bullish Int where bull = (0/=)
instance Foldable f => Bullish (f Bool) where bull = and
As a terminology note: "type constructor" is not actually synonymous with "type with arrow kind", for essentially the same reasons that "data constructor" is not synonymous with "value with arrow type".
arrow kind not arrow kind
constructor Maybe Bool
not constructor State Int Maybe Char
This code fails to compile with this error
src/JsonParser.hs:33:37-62: error:
* Couldn't match type `b1' with `b'
`b1' is a rigid type variable bound by
the type signature for:
resultantRunParse :: forall b1. String -> Maybe (String, b1)
at src/JsonParser.hs:29:5-52
`b' is a rigid type variable bound by
the type signature for:
fmap :: forall a b. (a -> b) -> Parser a -> Parser b
at src/JsonParser.hs:27:11-42
Expected type: Maybe (String, b1)
Actual type: Maybe (String, b)
The code:
import Prelude
newtype Parser a = Parser
{ runParser :: String -> Maybe (String, a)
}
instance Functor Parser where
fmap :: (a -> b) -> Parser a -> Parser b
fmap f (Parser p) = Parser resultantRunParse where
-- THIS FUNCTION TYPE DECLARATION IS THE PROBLEM
-- IF I REMOVE THE TYPE DECLARATION, IT WORKS
resultantRunParse :: String -> Maybe (String, b)
resultantRunParse input =
case p input of
Nothing -> Nothing
Just (remaining, parsed) -> Just (remaining, f parsed)
I tried the signature resultantRunParse :: forall b1. String -> Maybe (String, b1) suggested in the error message and that also didnt work.
Also, making the resultantRunParse = undefined and keeping the type signature also worked. Very strange!
What is the correct signature for this function?
The type signature must contain b, but it must be the same one as the b used in the enclosing function. This is impossible without scoped type variables:
https://wiki.haskell.org/Scoped_type_variables
To us this, add a comment to the top of your file:
{-# LANGUAGE ScopedTypeVariables #-}
Then, use an explicit forall b. in the enclosing function’s type signature.
Without scoped type variables, there is no way to write b in the type signature and make it the same b as the one used in the enclosing function.
(My personal recommendation is to just not use a type signature here. It is not necessary.)
First get comfortable writing the instance by hand, then you can derive it. This type looks a lot like StateT, the state monad transformer where the order of the tuple is swapped.
type StateT :: Type -> MonadTransformer
newtype StateT s m a = StateT (s -> m (a, s))
Your Functor Parser instance can be derived via it if you are willing to swap the order
{-# Language DerivingVia #-}
{-# Language StandaloneKindSignatures #-}
import Control.Monad.State
import Data.Kind
type Parser :: Type -> Type
newtype Parser a = Parser
{ runParser :: String -> Maybe (a, String)
}
deriving Functor
via StateT String Maybe
You can derive more instances in this manner.
import Control.Applicative
import Control.Monad.Catch
import Control.Monad.Except
import Control.Monad.State
import Data.Kind
import Data.Monoid
newtype Parser a = Parser ..
deriving
( Functor, Applicative, Alternative
, Monad, MonadPlus, MonadFix, MonadThrow
, MonadFail, MonadError (), MonadState String )
via StateT String Maybe
-- pointwise lifting
-- (<>) = liftA2 (<>)
-- mempty = pure mempty
-- deriving (Semigroup, Monoid, Num, Bounded)
-- via Ap Parser a
-- or Alternative
-- (<>) = (<|>)
-- mempty = empty
deriving (Semigroup, Monoid)
via Ap Parser a
See the output of :instances command
:instances StateT String Maybe
:instances Ap Parser _
:instances Alt Parser _
The last two require partial type signatures :set -XPartialTypeSignatures -Wno-partial-type-signatures.
Using RankNTypes one can enforce various kinds of parametricity. For example, A id :: A:
newtype A = A { unA :: forall a. a -> a }
But how about cases where we only care about the parametricity of the function on its argument? For a specific kind of case, the following will work:
newtype X b = X { unX :: forall a. a -> (a, b) }
For example, X (\a -> (a, ())) :: X ().
I would like to understand how to (or whether one can) construct a parametricity test that works more generally for functions of the form \a -> (a, f a), where f may be constant (as above) or potentially parametric itself. This can't be accomplished with X. E.g., X (\a -> (a, id a)) is a type error. Can this be done?
Edit: I'd like to repose or elaborate on the question somewhat. Suppose we have a type of parameterized state transformers:
type PState i o a = i -> (a, o)
Suppose also that we are interested in statically enforcing that the a in an arbitrary m :: PState i o a does not depend on i in any way. In other words, is it possible to define a function f such that f m is well typed when m's value doesn't depend on the input (and in that case evaluates to m), and is not well typed otherwise?
You'll need to make an explicit type-level function to accomplish this. Normally quantified Type -> Type variables are actually assumed to be type constructors (injective functions), which is not sufficient here. But it is possible to have noninjective ones too, they're just called type families. The flip side to this is that you need some separate means of actually indexing which one you want, because type inference wouldn't work without that.
{-# LANGUAGE TypeFamilies, KindSignatures, EmptyDataDecls, TypeInType
, RankNTypes, UnicodeSyntax #-}
import Data.Kind
type family MyTyFun (f :: Type) (a :: Type) :: Type
newtype GState f = GState { unGS :: ∀ a . a -> (a, MyTyFun f a) }
data Af
type instance MyTyFun Af a = ()
type A = GState Af
data Xf b
type instance MyTyFun (Xf b) a = b
type X b = GState (Xf b)
data Wf
type instance MyTyFun Wf a = a
type W = GState Wf
> unGS (GState (\a -> (a, a)) :: W) 4
(4,4)
When having a parametrized type:
data A a=X a| Y
I have tried (successfully) implementing Functor and Applicative without specifiying the type parameter:
instance Functor A where instead of instance Functor (A a) where.Why does it work ? Looking in LYAH it seems all examples specify the type parameter in all their typeclass instances.
When should you neglect the type parameter ?
instance Functor A where instead of instance Functor (A a) where.
Why does it work ?
I find this easier to understand using GHC's kinding system. Let's start from a simple case, and experiment in GHCi:
> :k Eq Int
Eq Int :: Constraint
This tells us that Eq Int is a constraint, some property that might be verified during type checking. Indeed, if we type check (12 :: Int) == (42 :: Int), the compiler will verify that integers can be compared, resolving the constraint Eq Int.
What is Eq alone, the name of the class without the Int parameter?
> :k Eq
Eq :: * -> Constraint
This tells us that Eq can be thought of a function from types (* is the kind of types) to constraint.
Indeed, in Eq Int, Int is a type, so we have Int :: * making Int a well-kinded argument to pass to Eq.
Enough of type classes, what about type constructors?
> :k Maybe Int
Maybe Int :: *
No surprise, Maybe Int is a type
> :k Maybe
Maybe :: * -> *
Maybe instead, is a function from types to types (*->*). This is indeed what the Maybe type constructor does: mapping a type (Int) to a type (Maybe Int).
Back to the original question. Why can't we write instance Functor (A a) but we can instead write instance Functor A? Well, we have that
> :k A Int
A Int :: *
> :k A
A :: * -> *
and, most importantly,
> :k Functor
Functor :: (* -> *) -> Constraint
This tells us the the kind of the Functor type class is not the same kind of the Eq type class. Eq expects a type as an argument, while Functor expects something of kind (* -> *) as an argument. A fits that kind, while A Int does not.
This happens when, in the definition of the class, the argument is applied to some other type. E.g.
class C1 a where
foo :: a -> Bool
results in C1 :: * -> Constraint. Instead,
class C2 f where
bar :: f Int -> Bool
results in C2 :: (* -> *) -> Constraint, since f is not itself used as a type, f Int is, so f must be a parameterized type of kind * -> *.
When should you neglect the type parameter?
The type parameter is not "neglected". Let us first take a look at the Functor type class:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Notice the f a and f b in the type signature. We thus here "construct types" for the type signature of fmap.
Here f is thus basically a function that takes a type and transforms it into a type. For example if f ~ A if a ~ Int, then f a generates the A Int type.
Learn You a Haskell for the Greater Good! actually explains this in its chapter about Functors, Applicatives and Monoids:
Many times, we want to make our types instances of certain type
classes, but the type parameters just don't match up for what we want
to do. It's easy to make Maybe an instance of Functor, because
the Functor type class is defined like this:
class Functor f where
fmap :: (a -> b) -> f a -> f b
So we just start out with:
instance Functor Maybe where
And then implement fmap. All the type parameters add up because the Maybe takes the place of f in
the definition of the Functor type class and so if we look at fmap
like it only worked on Maybe, it ends up behaving like:
fmap :: (a -> b) -> Maybe a -> Maybe b
(...)
The following file Poly.hs file
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RankNTypes #-}
module Poly () where
type ListModifier s = forall a. s -> [a] -> [a]
instance Monoid (ListModifier s) where
mempty = const id
mappend f g st = f st . g st
Gets the typechecker to complain:
Poly.hs:8:10: Illegal polymorphic or qualified type: ListModifier s …
In the instance declaration for ‘Monoid (ListModifier s)’
Compilation failed.
Initially I though it couldn't compose the rank 2 types but:
λ> :t (undefined :: forall a . a -> String ) . (undefined :: forall b . String -> b)
(undefined :: forall a . a -> String ) . (undefined :: forall b . String -> b)
:: String -> String
I feel the Poly module is in some way inherently inconsistent but I can't put my finger on the problem.
ListModifier is a type alias, not a “real” type. Type aliases are essentially macros at the type level, always expanded by the typechecker before actually typechecking. That means your instance declaration is equivalent to the following:
instance Monoid (forall a. s -> [a] -> [a]) where
Even if that were allowed, it would overlap with the existing Monoid (a -> b) instance, so it still wouldn’t work. The larger problem, however, is that you can’t have an instance defined on a forall-quantified type because it wouldn’t make sense from the perspective of instance resolution.
What you could do instead is define a fresh type instead of a type alias using newtype:
newtype ListModifier s = ListModifier (forall a. s -> [a] -> [a])
Now you can define a Monoid instance, since typeclass resolution only needs to look for the ListModifier type, which is much simpler to match on:
instance Monoid (ListModifier s) where
mempty = ListModifier (const id)
mappend (ListModifier f) (ListModifier g) = ListModifier (\st -> f st . g st)
Alternatively, you could keep your type alias and define a newtype with a different name, like ReifiedListModifier, then define an instance on that, and you could only do the wrapping when you need to store a ListModifier in a container or use a typeclass instance.