Haskell:函数签名
此程序编译时没有问题:Haskell:函数签名,haskell,types,Haskell,Types,此程序编译时没有问题: bar :: MonadIO m => m String bar = undefined run2IO :: MonadIO m => m String -> m String run2IO foo = liftIO bar 当我将bar更改为foo(参数名称)时 我得到: 无法将类型“m”与“IO”匹配 “m”是一个刚性类型变量,由 run2IO::MonadIO m=>m String->m String的类
bar :: MonadIO m
=> m String
bar = undefined
run2IO :: MonadIO m
=> m String
-> m String
run2IO foo = liftIO bar
当我将bar
更改为foo
(参数名称)时
我得到:
无法将类型“m”与“IO”匹配
“m”是一个刚性类型变量,由
run2IO::MonadIO m=>m String->m String的类型签名
预期类型:IO字符串
实际类型:m字符串
为什么这两种情况不相同?在第一种情况下,您使用的是
bar
上的liftIO
。这实际上需要bar::IO字符串。现在,IO
恰好是MonadIO
上的一个实例,因此这是可行的–编译器只需丢弃bar
的多态性即可
在第二种情况下,编译器无法决定使用哪种monad作为foo
的类型:它是由环境固定的,即调用方可以决定它应该是什么MonadIO
实例。要再次获得选择IO
作为单子的自由,您需要以下签名:
{-# LANGUAGE Rank2Types, UnicodeSyntax #-}
run2IO' :: MonadIO m
=> (∀ m' . MonadIO m' => m' String)
-> m String
run2IO' foo = liftIO foo
。。。然而我认为你并不真的想要这样:你还不如写信
run2IO' :: MonadIO m => IO String -> m String
run2IO' foo = liftIO foo
或者简单地run2IO=liftIO
记住liftIO的类型:
liftIO :: MonadIO m => IO a -> m a
重要的是,第一个参数必须是一个具体的IO
值。这意味着当您有一个表达式liftIO x
,那么x
必须是IO a
类型
当Haskell函数被普遍量化(使用隐式或显式的forall
)时,这意味着函数调用方选择类型变量的替换对象。作为一个例子,考虑<代码> ID>代码>函数:它具有类型<代码> A- > A/<代码>,但是当您对表达式<代码> id“真/ <代码>进行评估时,则<代码> ID 采用类型<代码>布尔-> BOOL < /代码>,因为<代码> A<代码>被实例化为<代码> BOOL < /代码>类型。< /P>
现在,再考虑一下你的第一个例子:
run2IO :: MonadIO m => m Integer -> m Integer
run2IO foo = liftIO bar
foo
参数在这里完全不相关,因此真正重要的是liftIO-bar
表达式。由于liftIO
要求其第一个参数的类型为ioa
,因此bar
必须为ioa
类型。然而,bar
是多态的:它实际上具有类型MonadIO m=>m Integer
幸运的是,IO
有一个MonadIO
实例,因此使用IO
将bar
值实例化为IO Integer
,这是可以的,因为bar
是普遍量化的,所以它的实例化是通过使用来选择的
现在,考虑另一种情况,即使用<代码> LIFTIO FoO 。这看起来是一样的,但实际上根本不是:这次,
MonadIO m=>m Integer
值是函数的参数,而不是单独的值。量化是在整个函数上进行的,而不是单个值。为了更直观地理解这一点,再次考虑<代码> ID>代码>可能是有帮助的,但是这次,请考虑它的定义:
id :: a -> a
id x = x
在这种情况下,x
不能在其定义中实例化为Bool
,因为这意味着id
只能处理Bool
值,这显然是错误的。实际上,在id
的实现中,x
必须完全通用,不能实例化为特定类型,因为这将违反参数性保证
因此,在run2IO
函数中,foo
必须作为任意MonadIO
值完全通用,而不是特定的MonadIO
实例。liftIO
调用尝试使用特定的IO
实例,这是不允许的,因为调用方可能不提供IO
值
当然,您可能希望函数的参数以与bar
相同的方式进行量化;也就是说,您可能希望它的实例化由实现选择,而不是由调用方选择。在这种情况下,您可以使用RankNTypes
语言扩展来使用显式forall
指定不同的类型:
{-# LANGUAGE RankNTypes #-}
run3IO :: MonadIO m => (forall m1. MonadIO m1 => m1 Integer) -> m Integer
run3IO foo = liftIO foo
这将进行类型检查,但它不是一个非常有用的函数。我可以问一下,从哪里可以获得有关编译器行为的信息?我已经在Haskell中编写了大约6个月的代码,我想进一步了解在幕后发生了什么。呃,你所说的“关于编译器行为的信息”是什么意思?您当然可以通过查看了解GHC的工作。事实上,我从来没有读过太多的东西——我发现写很多代码,看看会发生什么更有用。最好不要把学习这样的行为看作是学习编译器,因为这种行为是由语言本身指定的。由类型签名提供的量化类型并不是一个罕见的触发点,它是一件值得深入研究的事情,而不是用“嗯,总有一天我会理解”之类的响应。它可能值得一提,也可能不值得一提primitive
中的PrimBase
类。
{-# LANGUAGE RankNTypes #-}
run3IO :: MonadIO m => (forall m1. MonadIO m1 => m1 Integer) -> m Integer
run3IO foo = liftIO foo