6

我正在尝试将 type(Floating a) => a -> a -> a的函数与 type 的函数组合(Floating a) => a -> a以获得 type 的函数(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而不是xand y,则testBoth编译。

我搜索了 StackOverflow、Haskell wikis、Google 等,但没有发现任何与此特定情况相关的函数组合限制。有谁知道为什么会这样?

4

3 回答 3

17
   \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
于 2010-12-02T23:34:35.160 回答
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与 的第一个参数的结果统一的结果(.)
  3. a ~ Inttest1论点 1 与论点 2 统一(.)
  4. b ~ Char -> Inttest1与 的论点 2 统一的结果(.)

可是等等!该类型变量 'b' (#4, ) 必须与(#1, )Char -> Int的参数类型统一。不好了!test2Int

你应该怎么做?一个正确的解决方案是:

testBoth x = test2 . test1 x

还有其他方法,但我认为这是最易读的。

编辑:那么试图告诉你的错误是什么?它是说统一Floating a => a -> a需要Floating b => b一个instance Floating (a -> a)... 虽然这是真的,但您真的不希望 GHC 尝试将函数视为浮点数。

于 2010-12-02T23:39:48.520 回答
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 -> bto实例化[a] -> ([a] -> [a])。也就是说,在:

(b -> c) -> (a -> b) -> a -> c
  • 类型变量a将替换为[a]
  • 类型变量b将替换为[a] -> [a].

如果 type offoldr (:)a -> b,则类型 of(. foldr (:))将是:

(b -> c) -> a -> c`

(foldr (:)用作(.)) 的右操作符。

但是因为 type of foldr (:)is [a] -> ([a] -> [a]),所以 type of(. 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是函数的参数ixs也是函数的参数(i x)

有一个技巧可以使具有2 个参数的函数无点。那就是使用“owl”运算符:

f a x xs = g . h a .  i x xs
f a      = g . h a .: i
  where (.:) = (.).(.)

上述两个函数定义是等价的。阅读更多关于“owl”运算符的信息。

参考

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

另请阅读:

于 2015-09-02T15:18:39.707 回答