Haskell:使用两个浮动参数的组合函数失败

时间:2010-12-02 23:14:32

标签: haskell function-composition

我正在尝试使用类型(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而不是xytestBoth编译。

我搜索过StackOverflow,Haskell wikis,Google等,并没有发现任何与此特定情况相关的功能组成限制。有谁知道为什么会这样?

3 个答案:

答案 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的类型并转动曲柄得到:

  1. b ~ Inttest2的参数与(.)的第一个参数统一
  2. c ~ Inttest2与[{1}}的第一个参数的结果统一的结果)
  3. (.)a ~ Int参数1与test1的参数2统一)
  4. (.)b ~ Char -> Inttest1的参数2统一的结果)
  5. 但是等等!该类型变量'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

参考

一旦你理解了函数,类型,部分应用和currying,函数组合和美元运算符,Haskell编程变得更容易和直接。要理解这些概念,请阅读以下StackOverflow答案:

另请阅读: