高阶多态性是否需要严格的参数顺序



阅读LYAH,我偶然发现了这段代码:

newtype Writer w a = Writer { runWriter :: (a, w) }
instance (Monoid w) => Monad (Writer w) where  
  return x = Writer (x, mempty)  
  (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')  

在试图理解第一行中到底Writer w什么时,我发现这不是一个完整的类型,而是一种带有 1 个参数的类型构造函数,就像 Maybe 表示Maybe String

看起来不错,但是如果初始类型(如果Writer'(是用交换的类型参数定义的,如下所示:

newtype Writer' a w = Writer' { runWriter :: (a, w) } 

现在可以实现Monad实例吗?像这样的东西,但实际上可以编译什么:

instance (Monoid w) => Monad (* -> Writer' * monoid) where

* -> Writer' * monoid的思想与Writer w相同:缺少一个类型参数的类型构造函数 - 这次是第一个。

这在 Haskell 中是不可能的,你需要的是一个不存在的类型级 lambda 函数。

有一些类型同义词可用于定义类型变量的重新排序:

type Writer'' a w = Writer' a w

但是,您不能为部分应用的类型提供类实例同义词(即使使用 TypeSynonymInstances 扩展

(。

我写了我的硕士论文,主题是关于如何将类型级 lambda 添加到 GHC 的主题:https://xnyhps.nl/~thijs/share/paper.pdf 用于类型类实例而不牺牲类型推断。

你在这里看到的是Haskell的一个狭隘的设计选择。 从概念上讲,如果您"省略"了Writer'类型的第一个参数,那么说您的类型是一个函子是完全有意义的。 并且可以发明一种编程语言语法来允许这样的声明。

Haskell社区还没有这样做,因为他们拥有的东西相对简单,而且效果很好。 这并不是说替代设计是不可能的,但要采用这样的设计

,必须:
  1. 在实践中使用起来并不比我们已经拥有的更复杂;
  2. 提供值得转换的功能或优势。

这推广到Haskell社区使用类型的许多其他方式;通常,将某些东西表示为类型区分的选择与语言设计的某些工件有关。 许多单元变压器都是很好的例子,比如MaybeT

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance Functor m => Functor (MaybeT m) where ...
instance Applicative m => Applicative (MaybeT m) where ...
instance Monad m => Monad (MaybeT m) where ...
instance MonadTrans MaybeT where ...

由于它是newtype,这意味着MaybeT IO StringIO (Maybe String);你可以将这两种类型视为同一组值上的两个"透视":

  1. IO (Maybe String) 是生成类型为 Maybe String 的值的IO操作;
  2. MaybeT IO String 是生成类型为 String 的值的MaybeT IO操作。

这些观点之间的区别在于,它们意味着Monad操作的不同实现。 在Haskell中,这也与以下狭隘的技术事实有关:

  • 在一个String中是最后一个类型参数("值"(,在另一个Maybe String是;
  • IOMaybeT IO 具有不同的 Monad 类实例。

但也许有一种语言设计,你可以说IO (Maybe a)类型可以有一个特定于它的monad,并且与更一般的IO a类型的monad不同。 该语言将产生一些复杂性,以一致地进行区分(例如,确定默认Monad实例的规则IO (Maybe String)和允许程序员覆盖默认选择的规则(。 我谦虚地打赌,最终结果不会比我们所拥有的复杂程度低。 博士:唉。

最新更新