我可以在 Haskell 中打印多态函数的类型,就像我将具体类型的实体传递给它时那样吗?

Can I print in Haskell the type of a polymorphic function as it would become if I passed to it an entity of a concrete type?

这里有一个 3 种类型的多态函数:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

这里是一个非多态函数:

:t Data.Char.digitToInt
Data.Char.digitToInt :: Char -> Int

如果我们将前者应用到后者,我们将得到一个多态的函数在一种类型中:

:t (.) Data.Char.digitToInt
(.) Data.Char.digitToInt :: (a -> Char) -> a -> Int

这意味着 (.) 被“实例化”(我不确定这是正确的术语;作为 C++ 程序员,我会这样称呼它) b === Charc === Int,因此应用于 digitToInt(.) 的签名如下

(Char -> Int) -> (a -> Char) -> a -> Int

我的问题是:给定 (.)digitToInt 和我想将前者应用到后者的“信息”,有没有办法在屏幕上打印此签名?

对于感兴趣的人,此问题早先已作为 的副本关闭。

您可以使用 TypeApplications 扩展来做到这一点,它允许您明确指定要使用哪些类型来实例化类型参数:

λ :set -XTypeApplications                                 
λ :t (.) @Char @Int
(.) @Char @Int :: (Char -> Int) -> (a -> Char) -> a -> Int

请注意参数的顺序必须准确无误。

对于具有“常规”类型签名(如 foo :: a -> b)的函数,顺序由类型参数首次出现在签名中的顺序定义。

对于像 foo :: forall b a. a -> b 这样使用 ExplicitForall 的函数,顺序由 forall.

中的任何内容定义

如果您想根据将 (.) 应用到 digitToChar(而不是仅仅知道要填充哪些类型)来具体计算类型,我敢肯定您不能GHCi,但我强烈推荐 Haskell IDE 支持。

例如,这是我在 VSCode 中的样子(这里是 the extension):

Prelude:

的一角隐藏着这个巧妙的小功能
Prelude.asTypeOf :: a -> a -> a
asTypeOf x _ = x

它被记录为“强制其第一个参数与第二个参数具有相同的类型”。我们可以用它来强制 (.) 的第一个参数的类型:

-- (.) = \x -> (.) x = \x -> (.) $ x `asTypeOf` Data.Char.digitToInt
-- eta expansion followed by definition of asTypeOf
-- the RHS is just (.), but restricted to arguments with the same type as digitToInt
-- "what is the type of (.) when the first argument is (of the same type as) digitToInt?"
ghci> :t \x -> (.) $ x `asTypeOf` Data.Char.digitToInt
\x -> (.) $ x `asTypeOf` Data.Char.digitToInt
  :: (Char -> Int) -> (a -> Char) -> a -> Int

当然,这适用于您需要的任意数量的参数。

ghci> :t \x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
\x y -> (x `asTypeOf` Data.Char.digitToInt) . (y `asTypeOf` head)
  :: (Char -> Int) -> ([Char] -> Char) -> [Char] -> Int

您可以认为这是@K.A.Buhr在评论中想法的变体——使用签名比其实现更严格的函数来指导类型推断——除了我们不必定义任何东西我们自己,代价是无法在 lambda 下复制有问题的表达式。

我认为@HTNW 的回答可能涵盖了它,但为了完整起见,下面是 inContext 解决方案的详细工作原理。

函数的类型签名:

inContext :: a -> (a -> b) -> a

意味着,如果你有一个你想输入的东西,以及一个使用它的“上下文”(可以表示为一个将它作为参数的 lambda),用类型说:

thing :: a1
context :: a2 -> b

你可以强制统一 a1thing 的一般类型)和 a2(上下文的约束),只需构造表达式:

thing `inContext` context

通常,统一类型 thing :: a 会丢失,但 inContext 的类型签名意味着整个结果表达式的类型也将与所需类型统一 a, GHCi 会很高兴地告诉你那个表达式的类型。

所以表达式:

(.) `inContext` \hole -> hole digitToInt

最终被分配到 (.) 在指定上下文中的类型。你可以这样写,有点误导,如下:

(.) `inContext` \(.) -> (.) digitToInt

因为 (.)hole 一样是匿名 lambda 的参数名称。这可能会造成混淆,因为我们正在创建一个本地绑定,它隐藏了 (.) 的顶级定义,但它仍然命名相同的东西(具有改进的类型),并且这种对 lambda 的滥用允许我们编写原始表达式 (.) digitToInt 逐字记录,带有适当的样板。

如果您只是向 GHCi 询问其类型,那么 inContext 的定义方式实际上是无关紧要的,因此 inContext = undefined 会起作用。但是,只要看一下类型签名,就可以很容易地给出 inContext 一个有效的定义:

inContext :: a -> (a -> b) -> a
inContext a _ = a

事实证明,这只是 const 的定义,所以 inContext = const 也有效。

您可以使用 inContext 一次键入多个内容,它们可以是表达式而不是名称。为了适应前者,您可以使用元组;为了使后者起作用,您在 lambas 中使用了更合理的参数名称。

因此,例如:

λ> :t (fromJust, fmap length) `inContext` \(a,b) -> a . b
(fromJust, fmap length) `inContext` \(a,b) -> a . b
  :: Foldable t => (Maybe Int -> Int, Maybe (t a) -> Maybe Int)

告诉您在表达式 fromJust . fmap length 中,类型已专门化为:

fromJust :: Maybe Int -> Int
fmap length :: Foldable t => Maybe (t a) -> Maybe Int

这是 HTNW 回答的细微变化。

假设我们有任何涉及多态标识符的可能很大的表达式poly

 .... poly ....

我们想知道此时多态类型是如何实例化的。

这可以利用 GHC 的两个特性来完成:asTypeOf(如 HTNW 所述)和 typed holes,如下所示:

 .... (poly `asTypeOf` _) ....

读取 _ 漏洞后,GHC 将生成一个错误,报告应输入的术语类型以代替该漏洞。由于我们使用了 asTypeOf,这必须与我们在该上下文中需要的 poly 的特定实例的类型相同。

这是 GHCi 中的示例:

> ((.) `asTypeOf` _) Data.Char.digitToInt
<interactive>:11:17: error:
    * Found hole: _ :: (Char -> Int) -> (a -> Char) -> a -> Int

其他答案需要借助定义为人为限制类型的函数,例如 HTNW 的答案中的 asTypeOf 函数。这不是必需的,如以下交互所示:

Prelude> let asAppliedTo f x = const f (f x)

Prelude> :t head `asAppliedTo` "x"
head `asAppliedTo` "x" :: [Char] -> Char

Prelude> :t (.) `asAppliedTo` Data.Char.digitToInt
(.) `asAppliedTo` Data.Char.digitToInt
  :: (Char -> Int) -> (a -> Char) -> a -> Int

这利用了 缺乏 lambda 绑定中的多态性,这隐含在 asAppliedTo 的定义中。其主体中两次出现的 f 必须赋予相同的类型,即其结果的类型。这里使用的函数const也有其自然类型a -> b -> a:

const x y = x