如何获取特定类型 class 实例的多态函数类型?

How can i get the type of a polymorphic function for a specific type class instance?

例如,在 GHCi 中键入 :t ap 会得到结果

ap :: Monad m => m (a -> b) -> m a -> m b

如果我已经知道我将要使用的 Monad 实例是 ((->) r),我如何查询该特定实例的 ap 类型?

这只是一个 hack,但您可以随时执行以下操作:

:t ap . (id :: ((->) r a) -> ((->) r a))

:t \x y -> (id :: ...) (ap x y)

有意思

Prelude Control.Monad> type Reader = (->) r
Prelude Control.Monad> :t ap . (id :: Reader r a -> Reader r a)
ap . (id :: Reader r a -> Reader r a)
  :: Reader r (a -> b) -> (r -> a) -> r -> b

不同于

Prelude Control.Monad> :t \x y -> (id :: Reader r a -> Reader r a) (ap x y)
\x y -> (id :: Reader r a -> Reader r a) (ap x y)
  :: (r -> a1 -> a) -> (r -> a1) -> Reader r a

在 ghc 识别为同义词的地方 Reader r a

作为 GHC 8.0 中引入的 Lazersmoke said as a comment you can use the TypeApplications 扩展。

在 GHCi 中:

λ > :set -XTypeApplications
λ > import Control.Monad
λ > :t ap @((->) _)
ap @((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b

您可以使用 visible type application 功能来指定参数类型。您可以以更有创意的方式查看函数:Haskell 中的函数不仅可以应用于某些类型的值,还可以应用于该值的类型。但是要传递类型,您应该以某种方式指定(在前面加上@)您正在传递类型(因为类型不是Haskell[=73中的第一个class对象=] 还没有)。

这是它的工作原理:

λ: :set -XTypeApplications
λ: :t ap @((->) Int)
ap @((->) Int) :: (Int -> a -> b) -> (Int -> a) -> Int -> b

这种方法的唯一限制是您不能在 ghci 中使用类型变量,您应该使用特定类型(Int 而不是 r),但这不是大不了。

高级部分

好吧,实际上你可以,但这很棘手:

λ: :set -XExplicitForAll 
λ: :set -XPartialTypeSignatures 
λ: :set -XScopedTypeVariables 
λ: :{
λ| foo :: forall r . _
λ| foo = ap @((->) r)
λ| :}
<interactive>:28:19: warning: [-Wpartial-type-signatures]
    • Found type wildcard ‘_’
        standing for ‘(r -> a -> b) -> (r -> a) -> r -> b’
λ: :t foo
foo :: (r -> a -> b) -> (r -> a) -> r -> b

UPD: 您实际上可以使用占位符而不是类型变量(参见 )。但是,如果您想指定确切的名称,请使用上面的方法。

λ: :t ap @((->) _)
ap @((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b

/高级部分

关于这种方法还有一点要说:如果你的函数有多个类型参数并且你想指定一个,你应该做更多的事情。类型像 bar :: Int -> String -> Double 这样的函数中的简单参数一样从左到右一个一个地传递。如果你想修复 bar 的第一个参数,你应该写 bar 5 如果你想修复第二个,那么,你可以写类似 \n -> bar n "baz" 的东西,但这不适用于类型的应用程序。你需要知道两件事:

  1. 类型顺序。
  2. 如何指定所需的类型。

考虑下一个功能:

λ: :t lift
lift :: (Monad m, MonadTrans t) => m a -> t m a

我们希望能够指定 mt 类型的变量。因为 Haskell 没有命名类型变量(还)你 不能:t lift {t=MaybeT}:t lift {m=IO}很遗憾。所以回到两件事。

要查看类型的顺序,您应该使用一些编译器选项。类型参数的顺序由 forall 指定,您可以手动执行。否则,编译器将以某种方式对类型参数进行排序。普通人看不到 lift 函数的类型顺序,但如果您知道一些高级魔法,您可以:

λ: :set -fprint-explicit-foralls
λ: :t lift
lift
  :: forall {t :: (* -> *) -> * -> *} {a} {m :: * -> *}.
     (Monad m, MonadTrans t) =>
     m a -> t m a

然后你应该使用@_来跳过一些类型:

λ: :t lift @MaybeT
lift @MaybeT
  :: forall {a} {m :: * -> *}. Monad m => m a -> MaybeT m a
λ: :t lift @_ @IO
lift @_ @IO
  :: forall {t :: (* -> *) -> * -> *} {a}.
     MonadTrans t =>
     IO a -> t IO a
λ: :t lift @_ @_ @Int
lift @_ @_ @Int
  :: forall {t :: (* -> *) -> * -> *} {t1 :: * -> *}.
     (Monad t1, MonadTrans t) =>
     t1 Int -> t t1 Int

嗯,这对我来说真的是个谜,为什么 mforall 中显示为 third 参数,但应该作为 传递second 但我仍然没有意识到所有的魔法。