函子的Haskell类型推断



最近我一直在研究Haskell,特别是整个函子概念。我越投入其中,我得到的惊喜时刻就越多,它肯定会让我的多巴胺受体很痒。

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

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不允许我这样做。我想我很明白为什么会这样。

问题是:有没有办法让我更接近后一种变体?还是和前者生活在一起就好?

谢谢!

你的代码实际上很好,除了你遇到了可怕的单态限制,它使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中编写快速而肮脏的一次性脚本,而不必担心类型签名。

最新更新