【问题标题】:Why GHC.Types.Any here?为什么 GHC.Types.Any 在这里?
【发布时间】:2020-08-31 11:48:13
【问题描述】:

我刚才在 Haskell 中做一些代码高尔夫,我遇到了一个当时对我没有多大意义的错误。决定在 GHCi 中检查一下,现在我真的很困惑。

λ> :t replicate <$> readLn
replicate <$> readLn :: IO (a -> [a])
λ> f <- replicate <$> readLn
-- I type 4 and press Enter
λ> :t f
f :: GHC.Types.Any -> [GHC.Types.Any]

为什么f 不是a -&gt; [a] 类型?我当然可以unsafeCoerce,但那是冗长而可怕的。

【问题讨论】:

  • 我认为这是由于 monomorphism restriction 但我对 Haskell 的理解不够好,无法解释。 This question 解释了一个类似的案例(但不够接近,不能重复)。
  • @Gilles'SO-stopbeingevil',这实际上更多的是关于禁言类型,而不是关于多态限制。无论如何,默认情况下 GHCi 中的单态限制是关闭的。

标签: haskell types ghc coercion


【解决方案1】:

IO (a -&gt; [a]) 是一种多态类型。展开后,表示forall a. IO (a -&gt; [a])。现在,有两件事在这里行不通。一,这是一个产生单态函数的多态IO 动作。本质上,这个动作的每次执行都会产生一个 one 类型的函数。 a -&gt; [a] 并不是一个真正有效的类型,但如果你的意思是你想要一个 forall a. a -&gt; [a],你不会得到一个:

main = do
    f <- replicate <$> readLn
    print (f (5 :: Int)) -- f can be *one of* Int -> [Int] or Float -> [Float], but not both
    print (f (5 :: Float)) -- doesn't compile, comment either line out and it will

第二,GHC 不支持暗示性多态性。从理论上讲,如果您正确编写了 IO 动作,您可以将其设为 IO (forall a. a -&gt; [a]),但 GHC 不支持将多态类型(如 forall a. a -&gt; [a])放入容器中,如 IO

在你的情况下,由于你不使用f,GHC 不知道它应该在哪种类型上实例化动作,但它必须选择一个,所以它默认为Any

编辑:绕过“无强制类型”限制的常规方法是将它们隐藏到newtypes

{-# LANGUAGE RankNTypes #-}
-- interestingly, this is a numeric type (it represents the natural numbers)
newtype Replicator = Replicator { runReplicator :: forall a. a -> [a] }
mkReplicator :: Int -> Replicator
mkReplicator i = Replicator (replicate i)
-- mkReplicator =# replicate
main = do
    Replicator f <- mkReplicator <$> readLn
    print (f (5 :: Int))
    print (f (5 :: Float)) -- should work now

可能不值得……

【讨论】:

  • “GHC 不支持将多态类型(如forall a. a -&gt; [a])放入容器,如 IO。”您的回答让我很好奇 RankNTypes 扩展是否允许这样做。通常它用于将这样的多态类型放在函数箭头的左侧,但我从没想过它是否允许在类型构造函数中使用。
  • 正是我正在寻找的答案。当我尝试使用f . f 时出现了最初的错误——第一段完美地解释了它。非常感谢!
  • @RobinZigmond 是的,RankNTypes 是一个例外。但这不是这个,您需要ImpredicativeTypes 才能完成这项工作。目前,我认为一个完全坏了。它很好,我认为让它发挥作用是一个(成为?)一个长期目标。
【解决方案2】:

这里有两个问题。这段代码说明了一个:

Prelude> do { f <- return (replicate 4); print (f 'a'); print (f 'b') }
"aaaa"
"bbbb"
Prelude> do { f <- return (replicate 4); print (f 'a'); print (f "b") }
    [...]
    * Couldn't match expected type `Char' with actual type `[Char]'
    [...]

Haskell 类型系统的限制导致f 是单态的,也就是说,只能用于一种类型(您选择的类型)。 HTNW 已经对此进行了报道。

第二个问题是 GHCi 特有的。这段代码说明了这一点:

Prelude> do { f <- return (replicate 4); print (f 'a') }
"aaaa"
Prelude> f <- return (replicate 4)
Prelude> print (f 'a')
    [...]
    * Couldn't match expected type `GHC.Types.Any'
                  with actual type `Char'
    [...]

GHC 有两种方法可以将类型分配给类型不明确的表达式。一种是数字默认值,这里不相关。另一个适用于对类型、数字或其他类型没有类型类约束的情况。在这种情况下,类型不会影响程序的运行时行为,所以你选择什么并不重要。 GHC 使用GHC.Types.Any

通常,您永远不会看到第二种默认设置的结果,因为它只有在编译器知道类型无关紧要之后才会发生。

不过,GHCi 在您键入的每一行之后应用这些默认规则,让您没有机会用以后的代码来限制类型。因此,只要是Any,您就可以获得任何您想要的类型,而不是您想要的任何类型。

【讨论】:

  • 我认为一般来说没有任何明智的方法来推迟默认设置。当您在 GHC 提示符下写入 x &lt;- e 时,e 执行的 I/O 很可能取决于其类型。对于不受约束的多态性的特殊情况,是的,最好将类型的选择推迟到使用它,或者在某些情况下启用ImpredicativeTypes 时,改为泛化类型变量。但我想这会增加相当大的复杂性,但收益相对有限。
  • @dfeuer 我删除了将其称为错误的文本。你说得对,在x &lt;- return 3print xprint (x+1.5) 等情况下,它无法匹配非交互式 Haskell 的行为。您可能是对的,它会增加显着的复杂性。
  • 啊,是的,好点,因为第一个print x 将导致Integer 默认,而print (x + 1.5) 将要求它已默认为Double。耶。
猜你喜欢
  • 1970-01-01
  • 2017-06-02
  • 2018-12-10
  • 2010-12-25
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-01
相关资源
最近更新 更多