我正在尝试使用类型(Floating a) => a -> a -> a
的函数组合类型为(Floating a) => a -> a
的函数,以获取类型为(Floating a) => a -> a -> a
的函数。我有以下代码:
test1 :: (Floating a) => a -> a -> a
test1 x y = x
test2 :: (Floating a) => a -> a
test2 x = x
testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)
但是,当我在GHCI中编译它时,我收到以下错误:
/path/test.hs:8:11:
Could not deduce (Floating (a -> a)) from the context (Floating a)
arising from a use of `test2'
at /path/test.hs:8:11-15
Possible fix:
add (Floating (a -> a)) to the context of
the type signature for `testBoth'
or add an instance declaration for (Floating (a -> a))
In the first argument of `(.)', namely `test2'
In the expression: test2 . test1
In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.
请注意,testBoth
的已注释版本会编译。奇怪的是,如果我从所有类型签名中删除(Floating a)
约束,或者如果我将test1
更改为仅x
而不是x
和y
, testBoth
编译。
我搜索过StackOverflow,Haskell wikis,Google等,并没有发现任何与此特定情况相关的功能组成限制。有谁知道为什么会这样?
答案 0 :(得分:15)
\x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1
这两件事情彼此并不相同。
test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
答案 1 :(得分:2)
你的问题与Floating
没有任何关系,虽然类型类确实让你的错误更难理解。以下面的代码为例:
test1 :: Int -> Char -> Int
test1 = undefined
test2 :: Int -> Int
test2 x = undefined
testBoth = test2 . test1
testBoth的类型是什么?好吧,我们采用(.) :: (b -> c) -> (a -> b) -> a -> c
的类型并转动曲柄得到:
b ~ Int
(test2
的参数与(.)
的第一个参数统一c ~ Int
(test2
与[{1}}的第一个参数的结果统一的结果)(.)
(a ~ Int
参数1与test1
的参数2统一)(.)
(b ~ Char -> Int
与test1
的参数2统一的结果)但是等等!该类型变量'b'(#4,(.)
)必须与参数类型Char -> Int
(#1,test2
)统一。哦不!
你应该怎么做?一个正确的解决方案是:
Int
还有其他方法,但我认为这是最具可读性的。
编辑:那么试图告诉你的错误是什么?有人说将testBoth x = test2 . test1 x
与Floating a => a -> a
统一需要Floating b => b
...而这是真的,你真的不希望GHC尝试将函数视为浮点数。< / p>
答案 2 :(得分:2)
你的问题与Floating
无关,但事实上你要用一个没有类型检查的方式组成一个带有两个参数的函数和一个带有一个参数的函数。我将以组合函数reverse . foldr (:) []
为例给你一个例子。
reverse . foldr (:) []
的类型为[a] -> [a]
并按预期工作:它会返回一个反向列表(foldr (:) []
对于列表基本上是id
。
但reverse . foldr (:)
不进行类型检查。为什么呢?
让我们回顾一些类型:
reverse :: [a] -> [a]
foldr (:) :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
reverse . foldr (:) []
类型检查,因为(.)
实例化为:
(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
换句话说,在(.)
的类型注释中:
a
变为[a]
b
变为[a]
c
变为[a]
因此reverse . foldr (:) []
的类型为[a] -> [a]
。
reverse . foldr (:)
不会打字,因为:
foldr (:) :: [a] -> [a] -> [a]
作为(.)
的正确操作,它会将其类型从a -> b
实例化为[a] -> ([a] -> [a])
。也就是说,在:
(b -> c) -> (a -> b) -> a -> c
a
将替换为[a]
b
将替换为[a] -> [a]
。如果foldr (:)
的类型为a -> b
,则(. foldr (:))
的类型为:
(b -> c) -> a -> c`
(foldr (:)
适用于(.)
}的右操作。
但由于foldr (:)
的类型为[a] -> ([a] -> [a])
,(. foldr (:))
的类型为:
(([a] -> [a]) -> c) -> [a] -> c
reverse . foldr (:)
没有输入格式,因为reverse
的类型为[a] -> [a]
,而不是([a] -> [a]) -> c
!
当人们首先在Haskell中学习函数组合时,他们会知道当你在函数体的最右边有函数的最后一个参数时,你可以从参数和正文中删除它,替换或括起来(或者美元符号)带点。换句话说,以下4个函数定义是等效的:
f a x xs = g ( h a ( i x xs))
f a x xs = g $ h a $ i x xs
f a x xs = g . h a . i x $ xs
f a x = g . h a . i x
所以人们会直觉说“我只是从身体和参数中删除最右边的局部变量”,但这种直觉是错误的,因为一旦你删除了xs
,
f a x = g . h a . i x
f a = g . h a . i
不等同!您应该了解功能构成类型检查何时以及何时没有。如果上述2是等价的,那么这意味着以下2也是等价的:
f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs
这没有意义,因为x
不是以xs
作为参数的函数。 x
是功能i
的参数,xs
是功能(i x)
的参数。
有一个技巧可以使 2 参数无点地生成一个函数。那就是使用“猫头鹰”操作符:
f a x xs = g . h a . i x xs
f a = g . h a .: i
where (.:) = (.).(.)
以上两个函数定义是等价的。阅读more on “owl” operator。
const
const
, flip
and types curry
and uncurry
另请阅读: