【问题标题】:Haskell type inference for Functors函子的 Haskell 类型推断
【发布时间】:2026-01-06 12:15:01
【问题描述】:

最近我一直在玩 Haskell,尤其是整个仿函数概念。我越深入了解它,我得到的兴奋时刻就越多,而且它肯定会让我的多巴胺受体发痒。

我遇到的问题如下。这是有效的代码,它提升了函数,然后首先将其应用于 IO 值,然后应用于列表。

replicator1 =
  fmap (replicate 3)

replicator2 =
  fmap (replicate 3)

main = do
  replicated <- replicator1 getLine
  print (replicator2 replicated)

用更简洁的方式写它是很诱人的,即:

replicator =
  fmap (replicate 3)

main = do
  replicated <- replicator getLine
  print (replicator replicated)

我的一部分说它在概念上是正确的,因为replicator 应该适用于 IO 和 List 实例,但是作为强类型语言 Haskell 不允许我这样做。我想我很明白为什么会这样。

问题是:有什么方法可以让我更接近后一种变体?还是和前者一起生活好吗?

谢谢!

【问题讨论】:

  • A l w a y s _ u s e _ t y p e _ s i g n a t u r e s _ a t _ t h e _ t o p _ l e v e l.

标签: haskell functor monomorphism-restriction


【解决方案1】:

您的代码实际上很好,除了您遇到了dreaded monomorphism restriction,这使 Haskell 无法推断replicator 的最普遍的可能类型。

基本上,由于受到限制,Haskell 不会推断看起来不像函数的绑定的多态类型。这意味着它必须replicate 选择一个具体的函子,例如[]IO,如果您尝试在两种不同的上下文中使用它,则会导致错误。

您可以通过三种方式使您的代码工作:

  • 关闭单态限制:在模块顶部添加{-# LANGUAGE NoMonomorphismRestriction #-}

  • 使replicator 看起来像一个函数:

    replicator x = fmap (replicate 3) x
    
  • 为您的代码添加显式类型签名

    replicator :: Functor f => f a -> f [a]
    replicator = fmap (replicate 3)
    

第三个选项是最惯用的。好的 Haskell 风格包括向所有*标识符添加显式类型签名。但是,了解其他两个选项对于了解正在发生的事情以及能够在 Haskell 中编写快速而简单的一次性脚本而不用担心类型签名很有用。

【讨论】:

  • 呃,我感觉多巴胺在流动。这是一个壮观的答案!谢谢!
  • +1 表示“......了解其他两个选项很有用......编写快速而肮脏的 Haskell 脚本......”......探索的可能性世界....
  • @dsign 真的不能同意。要探索可能性,让类型签名指导您的效率要高得多。对于非常简单的任务来说,省略类型签名是可以的,因为您已经确切地知道类型是什么,并且不会费心通过写下来来陈述显而易见的事情。 OTOH,当进入新的地形时,那是你最需要签名的地方,以确定你的方向是正确的。
最近更新 更多