为什么函数不能采用仅受类型类约束的类型?

mat*_*ood 4 haskell constraints typeclass ghc

我没有足够的词汇来表达这个问题(因此,为了寻找答案,如果答案很容易获得,那么道歉).考虑以下

class RunFoo m where
  runFoo :: m a -> a

class RunFooWrapper m where
  doRunFoo :: (RunFoo n) => n a -> m a

newtype RunFast a = RunFast a

newtype RunSlow a = RunSlow a

fooExample :: (RunFoo m) => m Bool
fooExample = undefined

fooWrapperExample :: (RunFooWrapper m) => m Bool
fooWrapperExample = doRunFoo fooExample
Run Code Online (Sandbox Code Playgroud)

这不编译:Could not deduce (RunFoo n0) arising from a use of ‘doRunFoo’.

似乎编译器(GHC 7.10)坚持对mfrom 的具体实例化,fooExample因此拒绝继续.但在这种情况下,我无法理解为什么程序是错误的类型 - fooExample明确定义一个RunFoo m和所有doRunFoo要求是一个RunFoo x.那么为什么这不起作用呢?

作为一个补充问题,是否存在某种特殊的扩展(可能与存在类型有关),允许这样的程序?理想情况下,我希望能够说doRunFoo采用存在的任何定义(?)作为RunFoo m => m(而不是采取任何具体的实例RunFoo).

动机

我想创建可组合函数,这些函数在类型类约束的类型的某些方面运行 - 我可以提供一个具体的例子,说明我的意思,如果有必要的话!

编辑更多的动力和替代实施

我对一般情况下这个问题的答案感到好奇,但我想我会在上下文中添加一些我遇到的问题,我真正想要的是monad之间的一种约束委托.所以我想在一个monad中编写函数,只能通过在另一个类型类中调用monad的类型类来约束.然后可以在不同的上下文中运行顶级函数,执行相同的逻辑,但根据包装monad将底层实现换出.由于包装和实现monad之间存在一对一的对应关系,我可以使用类型系列来实现这一点,所以

class (RunFoo (RunFooM m)) => RunFooWrapper m where
  type RunFooM m :: * -> *
  doRunFoo :: RunFooM m a -> m a

instance RunFooWrapper RunFooWrapperSlow where 
  type RunFooM RunFooWrapperSlow = RunSlow
  doRunFoo :: [...]
Run Code Online (Sandbox Code Playgroud)

这意味着它的分辨率fooExample m由包装器monad的类上下文决定,并且似乎工作正常,但与haoformayor提供的解决方案相比,这是一个非常狭窄的解决方案.

hao*_*hao 10

RankNTypes

{-# language RankNTypes #-}    

class RunFoo m where
  runFoo :: m a -> a

class RunFooWrapper m where
  doRunFoo :: (forall n. RunFoo n => n a) -> m a

fooExample :: RunFoo m => m Bool
fooExample = undefined

fooWrapperExample :: RunFooWrapper m => m Bool
fooWrapperExample = doRunFoo fooExample
Run Code Online (Sandbox Code Playgroud)

(forall n. RunFoo n => n a) -> m a是关键的区别.它允许你传入fooExample,它具有类型forall m. RunFoo m => m Bool(forall由编译器隐式添加),因此与它m结合n并且每个人都很高兴.虽然我无法读懂思想,但我相信这种类型反映了你的真实意图.你只需要一个RunFoo实例,仅此而已,并且n将第一个参数的作用域提供给它.

问题是您的给定代码是隐式输入的forall n. RunFoo n => n a -> m a.这意味着你需要首先选择一个n这样的,RunFoo n然后提出一个带有类型的值n a作为第一个参数传入.这种简单的移动括号(增加等级n)的行为完全改变了含义.

有关具有相同问题的人的示例,请参阅此Stack Overflow问题.有关RankNTypes比我能提供的更好的解释,请参阅Oliver的博客文章.