【问题标题】:Haskell: composing function with two floating arguments failsHaskell:用两个浮动参数组合函数失败
【发布时间】:2010-12-02 23:14:31
【问题描述】:

我正在尝试将(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 等,但没有发现任何与此特定情况相关的函数组合限制。有谁知道为什么会这样?

【问题讨论】:

    标签: haskell function-composition


    【解决方案1】:
       \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
    

    【讨论】:

    • 谢谢。简洁明了。我以前没见过 (test2 .) 表示法,但它一定意味着 f |-> test2.f.
    • @gdj:在 Haskell 中,可以部分应用二元运算符来形成 section
    • 是的,我知道;只是从未见过它与“。”一起使用。但这很有意义。谢谢!
    【解决方案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,Char -> Int)必须与test2(#1,Int)的参数类型统一。哦不!

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

    testBoth x = test2 . test1 x
    

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

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

    【讨论】:

    • 谢谢,这是一个直观的解释。当我省略类型类时,这一定意味着 testBoth 将获取 x 并将其传递给 test1,然后获取生成的函数 f : y |-> x 并将其传递给 test2,然后它将返回 f。这显然不是我想要的。
    【解决方案3】:

    您的问题与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 参数的函数变得无点。那就是使用“owl”运算符:

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

    以上两个函数定义是等价的。阅读more on “owl” operator

    参考文献

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

    另请阅读:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-30
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多