我正在尝试一个mtl
式的课程,这个课程允许我在外部monad上举起Pipe
作文。为此,我必须定义该类型的哪两个变量是Pipe
组合的域和codomain。
我尝试使用相关的类型系列方法,但无济于事:
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Trans.Free
import Control.Monad.Trans.State
import Control.Pipe hiding (Pipe)
data Pipe a b m r = Pipe { unPipe :: FreeT (PipeF a b) m r }
class MonadPipe m where
type C a b (m :: * -> *) :: * -> *
idT :: C a a m r
(<-<) :: C b c m r -> C a b m r -> C a c m r
instance (Monad m) => MonadPipe (Pipe i o m) where
type C a b (Pipe i o m) = Pipe a b m
idT = Pipe idP
(Pipe p1) <-< (Pipe p2) = Pipe (p1 <+< p2)
instance (MonadPipe m) => MonadPipe (StateT s m) where
type C a b (StateT s m) = StateT s (C a b m)
idT = StateT $ \s -> idT
(StateT f1) <-< (StateT f2) = StateT $ \s -> f1 s <-< f2 s
但是,上面的代码没有进行类型检查。 GHC给出以下错误:
family.hs:23:15:
Could not deduce (C a a m ~ C a0 a0 m0)
from the context (MonadPipe m)
bound by the instance declaration at family.hs:21:14-52
NB: `C' is a type function, and may not be injective
Expected type: C a a (StateT s m) r
Actual type: StateT s (C a0 a0 m0) r
In the expression: StateT $ \ s -> idT
In an equation for `idT': idT = StateT $ \ s -> idT
In the instance declaration for `MonadPipe (StateT s m)'
family.hs:24:10:
Could not deduce (C b c m ~ C b0 c0 m1)
from the context (MonadPipe m)
bound by the instance declaration at family.hs:21:14-52
NB: `C' is a type function, and may not be injective
Expected type: C b c (StateT s m) r
Actual type: StateT s (C b0 c0 m1) r
In the pattern: StateT f1
In an equation for `<-<':
(StateT f1) <-< (StateT f2) = StateT $ \ s -> f1 s <-< f2 s
In the instance declaration for `MonadPipe (StateT s m)'
<<Two other errors for 'C a b m' and 'C a c m'>>
我很难理解为什么这些类型不会统一,特别是对于idT
定义,因为我希望内idT
在a
上进行普遍量化,所以它会匹配外层的。
所以我的问题是这是否可以用类型系列实现,如果不能用类型系列实现,它怎么能实现呢?
答案 0 :(得分:11)
默认情况下,类型推断是一种猜谜游戏。 Haskell的表面语法使得明确关于哪些类型应该实例化forall
相当尴尬,即使你知道你想要什么。这是Damas-Milner完整性的美好时光遗留下来的遗产,当时有趣的想法需要明确的输入被禁止。
让我们假设我们允许使用Agda样式的f {a = x}
表示法在模式和表达式中显式使用类型应用程序,有选择地访问类型签名{{1}中与a
对应的类型参数}}。您的
f
应该是指
idT = StateT $ \ s -> idT
这样左边的类型为idT {a = a}{m = m} = StateT $ \ s -> idT {a = a}{m = m}
,右边的类型为C a a (StateT s m) r
,根据类型族的定义,它们相等,并且世界各地都有欢乐。但这不是你所写内容的意义。用于调用多态事物的“变量规则”要求每个StateT s (C a a m) r
都使用新的存在类型变量进行实例化,然后通过统一来解决。所以你的代码意味着什么
forall
计算类型族之后的可用约束是
idT {a = a}{m = m} = StateT $ \ s -> idT {a = a'}{m = m'}
-- for a suitably chosen a', m'
但这并不简化,也不应该,因为没有理由认为C a a m ~ C a' a' m'
是单射的。令人抓狂的是,机器比你更关心找到最通用解决方案的可能性。您已经考虑了合适的解决方案,但问题是当默认假设为 guesswork 时,实现通信。
有许多策略可以帮助您解决这个问题。一种是使用数据系列。亲:注意力没问题。 Con:结构。 (警告,未经测试的推测如下。)
C
另一个缺点是结果数据族不是自动monadic,也不是很容易打开它或使它以统一的方式monadic。
我正在考虑为你保留你的类型系列的东西尝试一个模式,但是为它定义一个newtype包装器
class MonadPipe m where
data C a b (m :: * -> *) r
idT :: C a a m r
(<-<) :: C b c m r -> C a b m r -> C a c m r
instance (MonadPipe m) => MonadPipe (StateT s m) where
data C a b (StateT s m) r = StateTPipe (StateT s (C a b m) r)
idT = StateTPipe . StateT $ \ s -> idT
StateTPipe (StateT f) <-< StateTPipe (StateT g) =
StateTPipe . StateT $ \ s - f s <-< g s
然后在操作类型中使用newtype WrapC a b m r = WrapC {unwrapC :: C a b m r}
以使类型检查器保持在正确的轨道上。我不知道这是不是一个好策略,但我打算在其中一天找到答案。
更直接的策略是使用代理,幻像类型和作用域类型变量(尽管此示例不应该使用它们)。 (再次,猜测警告。)
WrapC
这只是使类型应用程序显式化的一种糟糕方式。请注意,有些人使用data Proxy (a :: *) = Poxy
data ProxyF (a :: * -> *) = PoxyF
class MonadPipe m where
data C a b (m :: * -> *) r
idT :: (Proxy a, ProxyF m) -> C a a m r
...
instance (MonadPipe m) => MonadPipe (StateT s m) where
data C a b (StateT s m) r = StateTPipe (StateT s (C a b m) r)
idT pp = StateTPipe . StateT $ \ s -> idT pp
本身而不是a
并传递Proxy a
作为参数,因此无法在类型中将代理标记为此类并且依赖于不会意外地评估它。 undefined
最近的进展可能至少意味着我们只能拥有一种多态幻像代理类型。至关重要的是,PolyKinds
类型构造函数是单射的,所以我的代码实际上是在说“与此相同的参数”。
但有时我希望我可以在源代码中删除系统FC级别,或者以其他方式表达类型推断的明确手动覆盖。在依赖类型的社区中,这样的事情是非常标准的,没有人会想象机器可以在没有轻推的情况下解决所有问题,或者隐藏的参数没有值得检查的信息。很常见的是,函数的隐藏参数可以在使用站点被抑制,但需要在定义中明确。 Haskell目前的情况是基于一种文化假设,即“类型推断就足够了”,这种假设已经脱离了一代人的轨道,但仍然存在一段时间。
答案 1 :(得分:4)
编辑三次:查看数据系列版本的底部。并将GADT版本改为m。
让我猜一下:剩菜?
让我先解决类型错误,即解决方案。
The class defines :
type C a0 b0 m where a0 and b0 are fresh.
idT :: C a a m r, where a and r are fresh.
The idT in the (Pipe i o m0) instance is okay by what I think is the logic:
LHS is idT :: C a0 a0 (Pipe i o m0) r0 which becomes Pipe a0 a0 m0 r0
RHS is Pipe idP :: Pipe a1 a1 m1 r1 starts fresh
And then these unify
because Pipe is a data constructor.
The idT in the MonadPipe m0 => (StateT s0 m0) instance:
LHS is idT :: C a0 a0 (StateT s0 m0) which becomes StateT s0 (C a0 a0 m0)
RHS is StateT (\s -> idT) :: StateT s1 m1 r1
Some unification seems to happen...
RHS is StateT (\s -> idT) :: StateT s1 (C a0 a0 m0) r1
where expression idT :: MonadPipe m1 => (C a2 a2 m2) r2 starts fresh
context of idT :: (C a0 a0 m0) (a1, s1)
And then (C a0 a0 m0) does not unify with (C a1 a2 m2)
because C is a type constructor.
如果类型系列成为数据系列,那么以前的newtype
方式可以使类别实例在这里工作。
编辑:你改变参数的顺序和newtype StateT来解决它:
{-# LANGUAGE TypeFamilies, MultiParamTypeClasses #-}
import Control.Monad.Trans.Free
import Control.Monad.Trans.State
import Control.Pipe hiding (Pipe)
data Pipe m a b r = Pipe { unPipe :: FreeT (PipeF a b) m r }
newtype StatePipe s mp a b r = SP (StateT s (mp a b) r)
class MonadPipe mp where
idT :: mp a a r
(<-<) :: mp b c r -> mp a b r -> mp a c r
instance (Monad m) => MonadPipe (Pipe m) where
idT = Pipe idP
(Pipe p1) <-< (Pipe p2) = Pipe (p1 <+< p2)
instance (MonadPipe mp) => MonadPipe (StatePipe s mp) where
idT = SP . StateT $ \s -> idT
(SP (StateT f1)) <-< (SP (StateT f2)) = SP . StateT $ \s -> f1 s <-< f2 s
虽然MonadTrans现在可能很伤心。另一种方法是通过使用GADT来保持参数顺序,或许可以更清晰地表达您要构建的内容:
{-# LANGUAGE MultiParamTypeClasses, GADTs, FlexibleInstances #-}
import Control.Monad.Trans.Free
import Control.Monad.Trans.State
import Control.Pipe hiding (Pipe)
data Pipe s a b m r where
FPipe :: { unPipe :: FreeT (PipeF a b) m r } -> Pipe () a b m r
LPipe :: StateT s1 (Pipe s2 a b m) r -> Pipe (s1,s2) a b m r
class MonadPipe s where
idT :: Monad m => Pipe s a a m r
(<-<) :: Monad m => Pipe s b c m r -> Pipe s a b m r -> Pipe s a c m r
instance MonadPipe () where
idT = FPipe idP
(FPipe p1) <-< (FPipe p2) = FPipe (p1 <+< p2)
instance MonadPipe s2 => MonadPipe (s1,s2) where
idT = LPipe (StateT $ \s -> idT)
(LPipe (StateT f1)) <-< (LPipe (StateT f2)) =
LPipe (StateT $ \s1 -> (f1 s1 <-< f2 s1))
我可以把它翻译成一个更好的数据系列吗?
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Trans.Free
import Control.Monad.Trans.State
import Control.Pipe hiding (Pipe)
data family GPipe s :: * -> * -> (* -> *) -> * -> *
newtype instance GPipe () a b m r = Pipe { unPipe :: FreeT (PipeF a b) m r }
newtype instance GPipe (s1,s2) a b m r = LPipe ( StateT s1 (GPipe s2 a b m) r )
class MonadPipe s where
idT :: Monad m => GPipe s a a m r
(<-<) :: Monad m => GPipe s b c m r -> GPipe s a b m r -> GPipe s a c m r
instance MonadPipe () where
idT = Pipe idP
(Pipe p1) <-< (Pipe p2) = Pipe (p1 <+< p2)
instance MonadPipe s2 => MonadPipe (s1,s2) where
idT = LPipe (StateT (\s -> idT))
(LPipe (StateT f1)) <-< (LPipe (StateT f2)) = LPipe (StateT (\s -> f1 s <-< f2 s))