我正在尝试理解
的结果(*) . (+)
在Haskell。我知道合成算子只是数学函数的标准组合 - 所以
(f . g) = f (g x)
可是:
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
我很难理解这种类型的签名。我希望能够做到这样的事情:
((*) . (+)) 1 2 :: Num a => a -> a
= (* (+ 1 2))
(*)是什么意思。 (+)的签名?我尝试用类似的方式玩它(只是匹配它的签名):
((*) . (+)) 1 (\x -> x + 1) 1
但是无法编译。我正在尝试在编写这些步骤时完成逻辑步骤,但我还没有完全理解它是如何得到这个结果的(结果是)。
答案 0 :(得分:67)
我理解你的感受。我发现功能组成起初也很难掌握。什么帮助我解决这个问题是类型签名。考虑:
(*) :: Num x => x -> x -> x
(+) :: Num y => y -> y -> y
(.) :: (b -> c) -> (a -> b) -> a -> c
现在,当您编写(*) . (+)
时,它实际上与(.) (*) (+)
相同(即(*)
是(.)
的第一个参数,(+)
是第二个参数到(.)
):
(.) :: (b -> c) -> (a -> b) -> a -> c
|______| |______|
| |
(*) (+)
因此(*)
的类型签名(即Num x => x -> x -> x
)与b -> c
统一:
(*) :: Num x => x -> x -> x -- remember that `x -> x -> x`
| |____| -- is implicitly `x -> (x -> x)`
| |
b -> c
(.) (*) :: (a -> b) -> a -> c
| |
| |‾‾‾‾|
Num x => x x -> x
(.) (*) :: Num x => (a -> x) -> a -> x -> x
因此(+)
的类型签名(即Num y => y -> y -> y
)与Num x => a -> x
统一:
(+) :: Num y => y -> y -> y -- remember that `y -> y -> y`
| |____| -- is implicitly `y -> (y -> y)`
| |
Num x => a -> x
(.) (*) (+) :: Num x => a -> x -> x
| | |
| |‾‾‾‾| |‾‾‾‾|
Num y => y y -> y y -> y
(.) (*) (+) :: (Num (y -> y), Num y) => y -> (y -> y) -> y -> y
我希望澄清Num (y -> y)
和Num y
的来源。你留下了(Num (y -> y), Num y) => y -> (y -> y) -> y -> y
类型的非常奇怪的函数。
令它如此奇怪的是,它希望y
和y -> y
都是Num
的实例。可以理解,y
应该是Num
的实例,但y -> y
如何?使y -> y
成为Num
的实例似乎不合逻辑。这不是正确的。
但是,当你看一下函数组合实际上是什么时,这是有道理的:
( f . g ) = \z -> f ( g z)
((*) . (+)) = \z -> (*) ((+) z)
所以你有一个函数\z -> (*) ((+) z)
。因此,z
必须明确是Num
的实例,因为(+)
已应用于此\z -> (*) ((+) z)
。因此,Num t => t -> ...
的类型为...
,其中(*) ((+) z)
是((+) z)
的类型,我们将在稍后找到。
因此,Num t => t -> t
属于(*)
类型,因为它需要多一个数字。但是,在将其应用于其他数字之前,会对其应用(*)
。
因此((+) z)
期望Num
成为t -> t
的实例,这就是为什么Num
应该是...
的实例的原因。因此,(t -> t) -> t -> t
被Num (t -> t)
替换,并添加了约束(Num (t -> t), Num t) => t -> (t -> t) -> t -> t
,从而产生了(*)
类型。
您真正希望合并(+)
和(.:)
的方式是使用(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
f .: g = \x y -> f (g x y)
:
(*) .: (+)
因此\x y -> (*) ((+) x y)
与(+)
相同。现在给((+) x y)
两个参数确保Num t => t
确实只是Num t => t -> t
而不是((*) .: (+)) 2 3 5
。
因此(*) ((+) 2 3) 5
为(*) 5 5
25
即f .: g
,我相信这就是你想要的。
请注意,(f .) . g
也可以写为(.:)
,(.:) = (.) . (.)
也可以定义为{{1}}。你可以在这里阅读更多相关信息:
What does (f .) . g mean in Haskell?
希望有所帮助。
答案 1 :(得分:9)
(*)
和(+)
都有类型签名Num a => a -> a -> a
现在,如果你把它们组成,你会得到一些时髦的东西。
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
那是因为(*)
和(+)
期待两个'参数'。
(+)可以获得一个函数。 .
运算符需要该函数(您看到的a -> a
)。
这是(*) . (+)
x f y
(*) . (+) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
(*) . (+)
将x f y
映射到((x +) * f) y
,其中f
是a
到a
的函数,它也是一个数字。
(*)
期望函数的原因是使类型匹配而它需要两个参数,但该函数必须是一个数字,因为(*)
仅适用于数字。
真的,这个功能完全没有意义。
答案 2 :(得分:7)
首先是一些扩展:
{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}
正如其他答案所示,您的功能是
weird :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
weird x g = (x +) * g
但是这个函数确实有非奇怪的语义。
有difference lists的概念。因此,存在差异整数的概念。我已经看到它们仅在依赖类型的设置中使用(例如here,但这不是唯一的情况)。该定义的相关部分是
instance Enum DiffInt where
toEnum n = (n +)
fromEnum n = n 0
instance Num DiffInt where
n + m = n . m
n * m = foldr (+) id $ replicate (fromEnum n) m
这在Haskell中没有多大意义,但对于依赖类型可能很有用。
现在我们可以写
test :: DiffInt
test = toEnum 3 * toEnum 4
或者
test :: DiffInt
test = weird 3 (toEnum 4)
在两种情况下fromEnum test == 12
。
修改强>
可以避免使用TypeSynonymInstances
扩展程序:
{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}
weird :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
weird x g = (x +) * g
instance (Enum a, Num a) => Enum (a -> a) where
toEnum n = (toEnum n +)
fromEnum n = fromEnum $ n (toEnum 0)
instance (Enum a, Num a) => Num (a -> a) where
n + m = n . m
n * m = foldr (+) id $ replicate (fromEnum n) m
type DiffInt = Int -> Int
和以前一样,我们可以写
test' :: DiffInt
test' = weird 3 (toEnum 4)
但现在我们也可以写
-- difference ints over difference ints
type DiffDiffInt = DiffInt -> DiffInt
test'' :: DiffDiffInt
test'' = weird (toEnum 3) (toEnum (toEnum 4))
和
main = print $ fromEnum $ fromEnum test'
打印12
。
EDIT2 添加了更好的链接。
答案 3 :(得分:2)
让:
m = (*)
a = (+)
然后
(m.a) x = (m (a x)) = m (a x)
现在m
期望Num a
作为参数,另一方面(a x)
,(x +)
是(a -> a)
的一元函数(+)
,定义为{{ 1}}。我想发生的事情是GHC试图统一这两种类型,这样,如果你有一个既是数字又是一元函数的类型,m
可以取一个数和一元函数并返回一元函数,因为它们被认为是同一类型。
正如@Syd所指出的,这种统一对于任何正常的数字类型都没有意义,例如整数和浮点数。
答案 4 :(得分:2)
这里有很好的答案,但让我快点指出你出错的几个步骤。
首先,功能组合的正确定义是
(f . g) x = f (g x)
您在LHS上省略了x
。接下来,您应该记住,在Haskell中h x y
与(h x) y
相同。所以,与你的预期相反,
((*) . (+)) 1 2 = (((*) . (+)) 1) 2 = ((*) ((+) 1)) 2 = ((+) 1) * 2,
现在你明白为什么失败了。此外,
((*) . (+)) 1 (\x -> x + 1) 1
不起作用,因为不满足约束Num (Int -> Int)
。