【问题标题】:How to print type of polymorphic function (or value) in ghci with type defaulting rules applied?如何在应用了类型默认规则的 ghci 中打印多态函数(或值)的类型?
【发布时间】:2018-02-21 22:55:21
【问题描述】:

当我在 GHCi 中输入 :t 命令时,我看到了多态类型:

ghci> :t 42
42 :: Num t => t
ghci> :t div
div :: Integral a => a -> a -> a

但是在我实际评估这些函数之后,我看到了类型默认规则的结果。在根据 Haskell 报告和/或 ghc 实现应用类型默认规则后,是否有一些命令或能力可以在 ghci 中观察类型将如何更改?

【问题讨论】:

  • 您如何看待类型默认规则的结果? :t 42 `div` 2 显示 Integral a => a,let-bindings 等的结果也是如此。
  • @n.m.我说的是实际结果。就像2^100 `div` 2 打印633825300114114700748351602688 因为这种情况下的默认类型是Integer。即使ghci 说这个常量的类型是多态的,实际上它是特定的,我想看看使用的是哪种单态类型。
  • @Shersh 不过这只是 GHCi。 REPL 选择一个类型(在本例中为Integer),因为它必须显示一些东西。在“真实”代码中,它将是多态的,直到上下文选择一种类型。
  • @BenjaminHodgson 在实际代码中,我可以编写main = print (2 ^ 100),它会选择Integer 类型,尽管会发出警告说我是个坏人。发生这种情况是因为应用了类型默认规则。我想看看ghcighc 会选择哪个特定类型。我不在乎它如何选择默认类型,我只想看看选择了什么类型。

标签: haskell types polymorphism ghci monomorphism-restriction


【解决方案1】:

您可以通过打开单态限制然后将其绑定到一个新名称来做到这一点:

Prelude> :set -XMonomorphismRestriction 
Prelude> let n = 42
Prelude> :t n
n :: Integer
Prelude> let p = (^)
Prelude> :t p
p :: Integer -> Integer -> Integer
Prelude> let e = (**)
Prelude> :t e
e :: Double -> Double -> Double
Prelude> let d = div
Prelude> :t d
d :: Integer -> Integer -> Integer

如果你不想总是定义一个新变量,你可以通过使用来解决这个问题

Prelude> :def monotype (\e -> return $ ":set -XMonomorphismRestriction\nlet defaulted = "++e++"\n:t defaulted")

(您可能希望将其放入您的 .ghci 文件中以始终使命令可用)然后

Prelude> :monotype (^)
defaulted :: Integer -> Integer -> Integer

当然,启用单态限制的隐藏全局副作用非常难看,但是哦...

【讨论】:

  • 它确实适用于我的div (8.0.2)。也许要求:t d :)
  • 是的,在 8.3 和 7.10 中也可以使用,实际上是写 :t d 而不是 :t div。我的手指在那里打字太快了......
  • 这真的很有帮助!不完美(因为我不会写:t div)但已经足够好了。
  • @Shersh 我添加了一个 GHCi 宏来为我的答案提供“完美”解决方案。
  • 如果要默认为NoMonomorphismRestriction,则可以在:monotype的定义末尾添加:set -XNoMonomorphismRestriction。此外,我会使用像 __defaulted__ 这样不常见的标识符来避免名称冲突的可能性,或者使用 it 以便 GHCi 无论如何都会自动覆盖绑定。
【解决方案2】:

不是一个完美的解决方案,但它可能是第一步。

> import Data.Typeable
> let withType x = (x, typeOf x)
> withType []
([],[()])
> withType 56
(56,Integer)

请注意,由于a 类型更改为(a,TypeRep),GHCi 不会使用其所有默认魔法。不过,其中一些可以显示。

GHCi 的:set +t 选项也很有趣,但似乎在 GHCi 默认之前打印多态类型。

【讨论】:

  • 这是一个有趣的解决方案!
【解决方案3】:

从 GHC 8.4.1 开始,可以使用:type +d(或简称:t +d)选项来打印表达式的类型,如果可能的话,默认类型变量。

ghci> :t 42
42 :: Num p => p
ghci> :t +d 42
42 :: Integer
ghci> :t div
div :: Integral a => a -> a -> a
ghci> :t +d div
div :: Integer -> Integer -> Integer

【讨论】:

    【解决方案4】:

    ghci 不可能为您提供与 GHC 类似的默认行为,这正是(现在)在 ghci 中默认关闭单态限制的原因。

    如@Shersh 的回答所示,您现在可以询问 GHCi 它会将给定表达式默认为什么。

    Prelude> :t 2^100 `div` 2
    2^100 `div` 2 :: Integral a => a
    
    Prelude> :t +d 2^100 `div` 2
    2^100 `div` 2 :: Integer
    
    Prelude> 2^100 `div` 2
    633825300114114700748351602688
    

    但这不一定反映 GHC 对相同表达式的作用,因为 GHC 在整个模块的上下文中编译表达式。 GHC 可以考虑表达式的所有用途,而 GHCi 只能访问表达式的组成部分。 GHC 仅默认在考虑所有额外上下文后保持模棱两可的事物,因此不能保证使用您在 GHCi 中使用:t +d 看到的表达式的类型。

    例如:

    n = 2^100 `div` 2
    
    xs = "ABCD"
    
    main = print $ xs !! n
    

    这将打印'A',这显然不是该(4 元素)列表的第 633825300114114700748351602688 个元素。因为表达式2^100 `div` 2使用 作为!! 的参数(非本地,通过n 绑定),并且(!!) :: [a] -> Int -> a 类型被选择为Int,而不是在没有此上下文的情况下默认选择的类型 (Integer)。将该表达式评估为 Int 会得到不同的结果(0,由于溢出)。

    这意味着,当您为 GHC 中的类型错误而摸不着头脑并在 GHCi 中使用 :t +d 来尝试获取更多信息时,您需要注意您可能仍然没有看到与GHC实际上是在使用。保证多态类型与 GHC 正在使用的一个兼容,但在任何其他上下文中默认它可能会导致另一个不兼容的类型。

    【讨论】:

    • 这是对答案非常有用的补充!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2011-03-19
    • 2020-09-01
    • 1970-01-01
    相关资源
    最近更新 更多