This is more or the less the functionality I want to implement:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeInType #-}
type family ReturnType arr where
ReturnType (a -> b) = ReturnType b
ReturnType a = a
type family ReplaceReturnType t r where
ReplaceReturnType (a -> b) r = a -> ReplaceReturnType b r
ReplaceReturnType _ r = r
class CollectArgs f where
collectArgs :: ((forall r. ReplaceReturnType f r -> r) -> ReturnType f) -> f
instance CollectArgs f => CollectArgs (a -> f) where
collectArgs :: ((forall r. (a -> ReplaceReturnType f r) -> r) -> ReturnType f) -> a -> f
collectArgs f a = collectArgs (\ap -> f (\k -> ap (k a)))
instance (ReturnType a ~ a, ReplaceReturnType a dummy ~ dummy) => CollectArgs a where
collectArgs :: ((forall r. ReplaceReturnType a r -> r) -> a) -> a
collectArgs f = f id
What I eventually want to do with this is to write functions which are polymorphic in the number of incoming arguments, while they don't have to be part of a type class definition (which would correspond to printf var args style). So, for example:
wrapsVariadicFunction :: (CollectArgs f) => f -> Int -> f
wrapsVariadicFunction f config = collectArgs $ \apply ->
if odd config
then error "odd config... are you nuts?!"
else apply f
Only that the return type of f might not coicide with that of wrapsVariadicFunction.
Now, in a perfect world where I can associate a type class with a closed type family (a closed type class, so to speak), this would be easy to implement, because the connection ReplaceReturnType a r ~ r would be clear.
Since I can't state that connection, it is, quite understandably, not clear to GHC 8.2.1:
* Could not deduce: ReplaceReturnType a r ~ r
from the context: (ReturnType a ~ a,
ReplaceReturnType a dummy ~ dummy)
bound by the instance declaration
`r' is a rigid type variable bound by
a type expected by the context:
forall r. ReplaceReturnType a r -> r
Expected type: ReplaceReturnType a r -> r
Actual type: r -> r
* In the first argument of `f', namely `id'
In the expression: f id
In an equation for `collectArgs': collectArgs f = f id
* Relevant bindings include
f :: (forall r. ReplaceReturnType a r -> r) -> a
collectArgs :: ((forall r. ReplaceReturnType a r -> r) -> a) -> a
|
29 | collectArgs f = f id
|
A solution here would be universally quantifying over dummy in the instance context, but that's not possible (yet, judging from what I saw at ICFP). Also it's really cumbersome.
So, the actual question here is: How do I associate a value-level definition with a closed type family, much like a closed type class? Or is this impossible because types cannot be erased anymore? If so, is there some other workaround?
The standard trick to have these type classes that look like they are overlapping is to add a second parameter to the typeclass which will be distinct in each instance and whose value can be computed from the other ones.
The idea distilled to its very core is as follows (we need some scary extensions like UndecidableInstances but that's fine: we're writing total programs):
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
type family IsBase arr :: Bool where
IsBase (a -> b) = 'False
IsBase a = 'True
class SillyId a b where
sillyId :: IsBase a ~ b => a -> a
instance SillyId b (IsBase b) => SillyId (a -> b) 'False where
sillyId f = \x -> sillyId (f x)
instance SillyId b 'True where
sillyId t = t
Now, in your case it's a bit more complicated because you not only want this extra argument to do the dispatch, you also want other type level functions to reduce based on it. The trick is simply... to define these functions in terms of that dispatch!
Of course a type level Bool won't do anymore: you'll need to keep all of the information around. So instead of IsBase you'll have IsArrow:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
type family IsArrow arr :: Either (*, *) * where
IsArrow (a -> b) = 'Left '(a, b)
IsArrow a = 'Right a
type family ReturnType arr where
ReturnType ('Left '(a, b)) = ReturnType (IsArrow b)
ReturnType ('Right a) = a
type family ReplaceReturnType t r where
ReplaceReturnType ('Left '(a, b)) r = a -> ReplaceReturnType (IsArrow b) r
ReplaceReturnType _ r = r
class CollectArgs f (f' :: Either (*, *) *) where
collectArgs :: IsArrow f ~ f' => ((forall r. ReplaceReturnType f' r -> r) -> ReturnType f') -> f
instance CollectArgs f (IsArrow f) => CollectArgs (a -> f) ('Left '(a, f)) where
collectArgs :: ((forall r. (a -> ReplaceReturnType (IsArrow f) r) -> r) -> ReturnType (IsArrow f)) -> a -> f
collectArgs g a = collectArgs (\ap -> g (\k -> ap (k a)))
instance CollectArgs a ('Right a) where
collectArgs :: IsArrow a ~ 'Right a => ((forall r. ReplaceReturnType (IsArrow a) r -> r) -> a) -> a
collectArgs f = f id
And voilà. You can of course define type synonyms for ReplaceReturnType (IsArrow a) r to make the notations a bit lighter but that's the gist of it.
Related
The following does not work in Haskell-
{-# LANGUAGE GADTs, DataKinds, TypeFamilies, UndecidableInstances,
RankNTypes, PolyKinds #-}
import Data.Kind
data Payload :: (f :: a -> Type) -> (e :: a) -> Type where
MkPayload :: (e :: a) -> (t :: f e) -> Payload f e
payload :: Payload f e -> f e
payload (MkPayload e t) = t
• Expecting one more argument to ‘f :: a -> Type’
Expected a type, but ‘f :: a -> Type’ has kind ‘a -> Type’
• In the kind ‘(f :: a -> Type) -> (e :: a) -> Type’
In the data type declaration for ‘Payload’
|
6 | data Payload :: (f :: a -> Type) -> (e :: a) -> Type where
| ^^^^^^^^^^^^^^
Is there any other way someone can define dependent types in Haskell?
You cannot use that _ :: _ notation in types as you might be used to from Agda. Instead just leave the names out and just write the types:
{-# LANGUAGE GADTs, DataKinds, TypeFamilies, UndecidableInstances,
RankNTypes, PolyKinds #-}
import Data.Kind ( Type )
data Payload :: (a -> Type) -> a -> Type where
MkPayload :: a -> f e -> Payload f e
payload :: Payload f e -> f e
payload (MkPayload e t) = t
That CUSK (complete user specified kind) notation is discouraged, instead you should use standalone kind signatures:
{-# LANGUAGE GADTs, DataKinds, TypeFamilies, UndecidableInstances,
RankNTypes, PolyKinds, StandaloneKindSignatures #-}
import Data.Kind ( Type )
type Payload :: (a -> Type) -> a -> Type
data Payload f e where
MkPayload :: a -> f e -> Payload f e
payload :: Payload f e -> f e
payload (MkPayload e t) = t
Leaving out the names does mean you lose some expressivity, but that isn't required for this example. There are techniques to recover most of that expressivity such as singletons that David Young mentioned.
I'm writing a distributed programming DSL and I'd like to allow implementations to choose their serialization method (if any, as it might not even be needed for a simulated execution).
Trying to solve this by adding a type family led to the problem below for a standard function I have. I imagine that it would work if I could require, and have the type checker understand, that if two values are serializable their pairing is also serializable. However, adding that as a quantified constraint doesn't seem to work. Can this be solved or is there a better solution for the problem?
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Kind
class (Monad (DistrM t)) => Distributed (t :: *) where
type Sendable t :: * -> Constraint
type DistrM t :: * -> *
-- ...
data FromSendable t a where
FromSendable :: (Sendable t b)
=> (b -> DistrM t a)
-> b
-> FromSendable t a
pairWith :: ( Sendable t a
, Distributed t
, forall a b. (Sendable t a, Sendable t b) => Sendable t (a,b)
)
=> a
-> FromSendable t b
-> FromSendable t (a,b)
pairWith a (FromSendable f b) =
FromSendable (\(a,b) -> (a,) <$> f b) (a,b)
-- >>> Could not deduce: Sendable t (a1, b1) ...
Edit 1
It type checks if I do
pairWith :: ( Sendable t a
, Distributed t
, st ~ Sendable t
, forall a b. (st a, st b) => st (a,b)
)
=> ...
It would get cumbersome to have to repeat these types of constraints, so I tried a type synonym but that doesn't work:
type Cs t = forall (st :: * -> Constraint).
(Sendable t ~ st, forall a b. (st a, st b) => st (a,b))
-- >>> Expected a constraint, but ‘st (a, b)’ has kind ‘*’
This looks weird. I only have a partial answer, but I'll post it anyway.
I simplified your code to
class C t where -- (*)
data T t where
T :: C t => (a -> t) -> a -> T t
foo ::
( C u
, forall a b . (C a , C b) => C (a, b) )
=> u -> T t -> T (u, t)
foo i (T f x) = T (\(a,b) -> (a, f b)) (i, x)
and, in this version, it compiles fine. However, if we replace
class C t where
with
type instance C :: * -> Constraint
then we get an error telling us that C (a, b) can not be deduced.
I can't completely understand what's going on here, but it looks like quantified constraints do not mix well with type families.
It looks like the above type family is treated like it were
type instance C (t :: *) :: Constraint
and in such case, I can't understand what's wrong. Since C now does not refer to a single type class, it is impossible to implement a quantified constraint like forall a b . (C a , C b) => C (a, b) by (say) passing a pointer to a specific instance, since the three C constraints could be anything at all, in an open world.
I still do not understand why type family C :: * -> Constraint is handled in the same way.
Perhaps GHC should reject quantified constraints involving type families ... -> Constraint in such way? I not sure.
I think you've pushed your code to the edges of GHC's type system here. You can fix the kind error on Cs by writing:
type Cs t = (forall (st :: * -> Constraint).
(Sendable t ~ st, forall a b. (st a, st b) => st (a,b))) :: Constraint
but then you run up against "GHC doesn't yet support impredicative polymorphism". Until GHC adds support for class families as per issue 14860, you're maybe out of luck with this approach.
However, you did ask about alternative approaches. Doesn't making Sendable t a a multiparameter type class accomplish basically the same thing?
Certainly, the following type-checks:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Kind
class (Monad (DistrM t)) => Distributed (t :: *) where
type DistrM t :: * -> *
-- ...
class Sendable t a where
data FromSendable t a where
FromSendable :: (Sendable t b)
=> (b -> DistrM t a)
-> b
-> FromSendable t a
type Cs t = forall a b. (Sendable t a, Sendable t b) => Sendable t (a,b) :: Constraint
pairWith :: ( Sendable t a
, Distributed t
, Cs t
)
=> a
-> FromSendable t b
-> FromSendable t (a,b)
pairWith a (FromSendable f b) =
FromSendable (\(a,b) -> (a,) <$> f b) (a,b)
I am reading the Rank-N-Types section of 24 days of GHC Extensions and came across the following GADT:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
import Data.Char
data Some :: * -> * where
SomeInt :: Int -> Some Int
SomeChar :: Char -> Some Char
Anything :: a -> Some a
unSome :: Some a -> a
unSome (SomeInt x) = x + 3
unSome (SomeChar c) = toLower c
unSome (Anything x) = x
unSome (someInt 2) -- 5
Although unSome is polymorphic in its type variables one can give the compiler proof that in the SomeInt case for instance, it is safe to add three to the given value. The author calls this type refinement.
Now I was curious whether I can do the same with a Scrott encoded type. Fortunately, there is an example of such encoding. We merely need the Rank-N-Types and Type-Families extensions turned on:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
newtype SomeC a =
SomeC {
runSomeC ::
forall r.
((a ~ Int) => Int -> r) ->
((a ~ Char) => Char -> r) ->
(a -> r) ->
r
}
However, unSome isn't provided in the article. I am not well-versed in Haskell and don't have a clue how to implement this function with Scott encoding. Especially the type equality constraints (e.g. (a ~ Int) =>) confuse me.
Any help or information on other online sources are appreciated.
You just use the provided function to replace your pattern match, as in:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
import Data.Char
newtype SomeC a =
SomeC {
runSomeC ::
forall r.
((a ~ Int) => Int -> r) ->
((a ~ Char) => Char -> r) ->
(a -> r) ->
r
}
unSome :: SomeC a -> a
unSome (SomeC f) = f (\x -> x+3) (\c -> toLower c) (\x -> x)
In ghci:
> unSome (SomeC (\someInt someChar anything -> someInt 2))
5
Is there a way to define "pairmap" like the following:
pairmap f (x,y) = (f x, f y)
So that the following works:
pairmap (+2) (1::Int, 2::Float)
pairmap succ (1::Int, 'a')
pairmap Just ('a', True)
etc.
Naturally, in the first case, both the elements must be of class Num, and in the second case, both of class Enum. In the third case however, there's no restriction required.
Answer (but could be improved)
The following code (ideone) solves the problem, but note that my functions have to be wrapped in a datatype that encapsulates both the relation between the input and output types and also any constraints on the input type. This works but there's a bit of boilerplate. It would be nice if I could use a bit less boilerplate to achieve this, so any answer would be appreciated (although this solution is reasonably fine for my purposes).
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
import GHC.Exts (Constraint)
class Function f where
type Constraints f a :: Constraint
type instance Constraints f a = ()
type Result f a
type instance Result f a = a
applyFunc :: (Constraints f a) => f -> a -> Result f a
pairmap ::
(Function f, Constraints f a, Constraints f b) =>
f -> (a, b) -> (Result f a, Result f b)
pairmap f (x,y) = (applyFunc f x, applyFunc f y)
data NumFunc where
NumFunc :: (forall a. Num a => a -> a) -> NumFunc
instance Function NumFunc where
type Constraints NumFunc a = (Num a)
applyFunc (NumFunc f) = f
data EnumFunc where
EnumFunc :: (forall a. Enum a => a -> a) -> EnumFunc
instance Function EnumFunc where
type Constraints EnumFunc a = (Enum a)
applyFunc (EnumFunc f) = f
data MaybeFunc where
MaybeFunc :: (forall a. a -> Maybe a) -> MaybeFunc
instance Function MaybeFunc where
type Result MaybeFunc a = Maybe a
applyFunc (MaybeFunc f) = f
y1 = pairmap (NumFunc (+2)) (1::Int, 2::Float)
y2 = pairmap (EnumFunc succ) (1::Int, 'a')
y3 = pairmap (MaybeFunc Just) ('a', True)
main = do
print y1
print y2
print y3
Answer 2
I think this is better and more flexible (ideone), but again, any improvements to reduce the boilerplate welcome:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
import GHC.Exts (Constraint)
data Func (c :: (* -> * -> Constraint)) where
Func :: (forall a b. c a b => a -> b) -> Func c
class (c a, a ~ b) => BasicConstraint c a b
instance (c a, a ~ b) => BasicConstraint c a b
numFunc = Func #(BasicConstraint Num)
enumFunc = Func #(BasicConstraint Enum)
class (c a, t a ~ b) => NewtypeConstraint c t a b
instance (c a, t a ~ b) => NewtypeConstraint c t a b
class EmptyConstraint a
instance EmptyConstraint a
maybeFunc = Func #(NewtypeConstraint EmptyConstraint Maybe)
applyFunc :: Func c -> (forall a b. c a b => a -> b)
applyFunc (Func f) = f
pairmap :: (c a a', c b b') => Func c -> (a, b) -> (a', b')
pairmap f (x,y) = (applyFunc f x, applyFunc f y)
main = do
print $ pairmap (numFunc (+2)) (1::Int, 2::Float)
print $ pairmap (enumFunc succ) (1::Int, 'a')
print $ pairmap (maybeFunc Just) ('a', True)
The first two of your examples are somewhat simpler to generalize than the third.
{-# LANGUAGE RankNTypes, ConstraintKinds, KindSignatures, AllowAmbiguousTypes, TypeApplications #-}
import GHC.Exts (Constraint)
pairmap :: forall (c :: * -> Constraint) d e. (c d, c e) =>
(forall a. (c a) => a -> a) -> (d,e) -> (d,e)
pairmap f (x,y) = (f x, f y)
The caveat with this solution is that you need to explicitly instantiate the constraint you are using:
ghci> pairmap #Num (+1) (1 :: Int, 1.0 :: Float)
(2,2.0)
As for the third, here is a half solutions. If the second type is always a type parametrized over the first (like f a), then you can do the same thing as above (albeit your first examples cease to work - you could make them work by wrapping them in Identity).
pairmap' :: forall (c :: * -> Constraint) f d e. (c d, c e) =>
(forall a. (c a) => a -> f a) -> (d,e) -> (f d,f e)
pairmap' f (x,y) = (f x, f y)
And again, at GHCi
ghci> pairmap' #Num (Just . (+1)) (1 :: Int , 1.0 :: Float)
(Just 2,Just 2.0)
Given the following code, removing forall a r from the type of go fails with "Overlapping instances for Typeable (D r)". I wonder why?
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
module M where
import Data.Typeable (Proxy, Typeable, cast)
class C r where
data D r :: *
deriving instance Typeable D
data A = forall r . A (D r)
go :: forall r a . (Typeable a, Typeable (D r)) => a -> Proxy r -> A
go a _ = case cast a of
Just (b :: D r) -> A b
Nothing -> error "fail to cast"
The error also says "The choice depends on the instantiation of r" - but isn't that pinned by the supplied Proxy r?
This is how scoped type variables work in Haskell. Note that you are re-using r here:
go :: forall r a . (Typeable a, Typeable (D r)) => a -> Proxy r -> A
go a _ = case cast a of
Just (b :: D r) -> A b -- this r is the same as the r above
Without the explicit forall, type variables are interpreted to be local to the signature. That is, your code is read as:
go :: (Typeable a1, Typeable (D r1)) => a1 -> Proxy r1 -> A -- renaming local variables
go a _ = case cast a of
Just (b :: D r) -> A b -- r bears no relation to r1
Hence the type error.
(It is confusing to get an Overlapping instances error, though.)