目前我正在学习Haskell。我们必须确定给定函数的最一般类型,但我还没有得到它。解释器如何确定函数的最一般类型,尤其是lambda表达式?什么是手动确定最常规类型的安全方法?
tx2 = (\x y z -> y.z.z)
tx2::a->(a->b)->(a->a)->b -- my guess
tx2 :: a -> (b -> c) -> (b -> b) -> b -> c -- interpreter solution
如果第一个变量(a)应用于表达式z,则z必须将a作为输入参数,但它在(b-> b)中使用b。 y消耗b并生成c,因此最终结果必须为c。但为什么b(作为中间结果?)包含在类型中?如果是这样,为什么它不是 - > (b - > c) - > (b - > b) - > B-> b - > c?
tm2 = (\i -> [sum,product]!!i)
tm2:: Int->[(Integer->Integer->Integer)]->(Integer->Integer->Integer) -- my guess
\i -> [sum,product] !! i :: Num a => Int -> [a] -> a -- interpreter with direct input
tm2 :: Int -> [Integer] -> Integer -- interpreter with :info tm2
因此,如果tm2在脚本中编码,解释器有关于类型的更详细信息,对吧?所以第二行中的类型是表达式的结果。为什么第2行只接受整数,而不是Float例如?
tp2 = (\x -> \y -> (x.y.x))
tp2::(a->b)->((a->b)->a)->a -- my guess
tp2 :: (a -> b) -> (b -> a) -> a -> b -- interpreter solution
为什么我必须在类型中包含中间结果?为什么没有使用(a-> b) - > a表示下面的tf2?
tf2 = (\x -> \y -> (x (y x), x, y))
tf2::(a->b)->((a->b)->a)->(a,a->b,(a->b)->a) -- solution
tg2 = (\x y z a -> y(z(z(a))));
tg2::a->(b->c)->(b->b)->b->c -- solution
这里我们不需要任何中间结果?我们写下参数的类型,然后写下结果的类型?
答案 0 :(得分:8)
tx2 = (\x y z -> y.z.z)
tx2
有三个参数(为了便于讨论,我忽略了currying),忽略了第一个并且组成第二个和第三个两次。所以第一个参数可以有任何类型,第二个和第三个必须有函数类型,比如说
y :: ay -> ry
z :: az -> rz
现在,z
的第一个应用程序的结果成为z
的第二个应用程序的参数,因此z
,rz
的结果类型必须是参数类型为z
,az
,我们称之为b
,所以
z :: b -> b
然后z
应用程序的结果成为y
的参数,因此y
的参数类型必须与z
的结果类型相同,但y
的结果类型完全不受表达式约束,因此
y :: b -> c
和y . z . z :: b -> c
,因此
tx2 :: a -> (b -> c) -> (b -> b) -> (b -> c)
然后是下一个,
tm2 = (\i -> [sum,product]!!i)
现在,sum
和product
是来自Prelude
的函数,两者的类型都为Num a => [a] -> a
,因此
Prelude> :t [sum,product]
[sum,product] :: Num a => [[a] -> a]
自(!!) :: [e] -> Int -> e
以来,给定xs
类型的列表[e]
,表达式\i -> xs !! i
的类型为Int -> e
。因此推断的tm2 = \i -> [sum,product] !! i
类型是
tm2 :: Num a => Int -> ([a] -> a)
但是,tm2
受到没有类型签名的简单模式绑定的约束,因此monomorphism restriction启动,tm2
的类型必须是单态的。通过默认规则,通过使用Num a
实例化类型变量a
来解决Integer
约束(除非明确的默认声明另有说明),所以你得到
Prelude> let tm2 = \i -> [sum,product] !! i
Prelude> :t tm2
tm2 :: Int -> [Integer] -> Integer
除非您禁用单态限制(:set -XNoMonomorphismRestriction
)。
tp2 = (\x -> \y -> (x.y.x))
第一次应用x
的结果成为y
的参数,因此x
的结果类型必须与y
的参数类型相同。然后y
的应用结果成为x
的第二个应用的参数,因此y
的结果类型必须是'x`的参数类型,总共
tp2 :: (a -> b) -> (b -> a) -> (a -> b)
然后在
tf2 = (\x -> \y -> (x (y x), x, y))
唯一有趣的部分是结果的第一个组成部分x (y x)
,因此x
应用于y
的应用结果,因此x
必须具有功能类型
x :: a -> b
和y
必须包含结果类型a
。但是y
已应用于x
,因此其参数类型必须是x
的类型,
y :: (a -> b) -> a
和
tf2 :: (a -> b) -> ((a -> b) -> a) -> (b, a -> b, (a -> b) -> a)
最后
tg2 = (\x y z a -> y(z(z(a))))
顺便说一句,它与tx2
完全相同,只是通过在lambda中再提供一个参数来进行扩展。因此,类型的推导也是一样的。
答案 1 :(得分:1)
我认为你对Haskell中函数类型的许多怀疑会在wikipedia上阅读一些关于“currying”的内容得到答案。
你编写的函数的推断类型就是这样,因为Haskell使用curried函数,即只有一个输入参数的函数可以返回(取决于定义)其他函数。
因此,当您编写类似mySum x y = x + y
的函数时,其类型为a -> a -> a
,通过明确->
:a -> (a -> a)
的正确关联性,可以更轻松地阅读。看到?您获得的额外返回类型不是您调用它们的“中间结果”。
您不需要两个参数并返回结果a
。您使用一个参数x
,在表达式x + y
中修复其值,然后返回带参数的函数a -> a
(此次为y
)并将其相加到y
您之前已修复过。
逐点回答你的问题会有点长,所以我会通过,但这是为了检查你的功能类型你必须做的理由。
对于tm2
上的第二个问题,我不确定解释器推断类型之间的区别。