Haskell: composing function with two floating arguments fails

好久不见. 提交于 2019-12-17 20:15:57

问题


I am trying to compose a function of type (Floating a) => a -> a -> a with a function of type (Floating a) => a -> a to obtain a function of type (Floating a) => a -> a -> a. I have the following code:

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)

However, when I compile it in GHCI, I get the following error:

/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.

Note that the commented-out version of testBoth compiles. The strange thing is that if I remove the (Floating a) constraints from all type signatures or if I change test1 to just take x instead of x and y, testBoth compiles.

I've searched StackOverflow, Haskell wikis, Google, etc. and not found anything about a restriction on function composition relevant to this particular situation. Does anyone know why this is happening?


回答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

These two things are not like each other.

   test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y



回答2:


You're problem doesn't have anything to do with Floating, though the typeclass does make your error harder to understand. Take the below code as an example:

test1 :: Int -> Char -> Int
test1 = undefined

test2 :: Int -> Int
test2 x = undefined

testBoth = test2 . test1

What is the type of testBoth? Well, we take the type of (.) :: (b -> c) -> (a -> b) -> a -> c and turn the crank to get:

  1. b ~ Int (the argument of test2 unified with the first argument of (.))
  2. c ~ Int (the result of test2 unified with the result of the first argument of (.))
  3. a ~ Int (test1 argument 1 unified with argument 2 of (.))
  4. b ~ Char -> Int (result of test1 unified with argument 2 of (.))

but wait! that type variable, 'b' (#4, Char -> Int), has to unify with the argument type of test2 (#1, Int). Oh No!

How should you do this? A correct solution is:

testBoth x = test2 . test1 x

There are other ways, but I consider this the most readable.

Edit: So what was the error trying to tell you? It was saying that unifying Floating a => a -> a with Floating b => b requires an instance Floating (a -> a) ... while that's true, you really didn't want GHC to try and treat a function as a floating point number.




回答3:


Your problem has nothing to do with Floating, but with the fact that you want to compose a function with two arguments and a function with one argument in a way that doesn't typecheck. I'll give you an example in terms of a composed function reverse . foldr (:) [].

reverse . foldr (:) [] has the type [a] -> [a] and works as expected: it returns a reversed list (foldr (:) [] is essentially id for lists).

However reverse . foldr (:) doesn't type check. Why?

When types match for function composition

Let's review some types:

reverse      :: [a] -> [a]
foldr (:)    :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.)          :: (b -> c) -> (a -> b) -> a -> c

reverse . foldr (:) [] typechecks, because (.) instantiates to:

(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]

In other words, in type annotation for (.):

  • a becomes [a]
  • b becomes [a]
  • c becomes [a]

So reverse . foldr (:) [] has the type [a] -> [a].

When types don't match for function composition

reverse . foldr (:) doesn't type check though, because:

foldr (:) :: [a] -> [a] -> [a]

Being the right operant of (.), it would instantiate its type from a -> b to [a] -> ([a] -> [a]). That is, in:

(b -> c) -> (a -> b) -> a -> c
  • Type variable a would be replaced with [a]
  • Type variable b would be replaced with [a] -> [a].

If type of foldr (:) was a -> b, the type of (. foldr (:)) would be:

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

(foldr (:) is applied as a right operant to (.)).

But because type of foldr (:) is [a] -> ([a] -> [a]), the type of (. foldr (:)) is:

(([a] -> [a]) -> c) -> [a] -> c

reverse . foldr (:) doesn't type check, because reverse has the type [a] -> [a], not ([a] -> [a]) -> c!

Owl operator

When people first learn function composition in Haskell, they learn that when you have the last argument of function at the right-most of the function body, you can drop it both from arguments and from the body, replacing or parentheses (or dollar-signs) with dots. In other words, the below 4 function definitions are equivalent:

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

So people get an intuition that says “I just remove the right-most local variable from the body and from the arguments”, but this intuition is faulty, because once you removed xs,

f a x = g . h a . i x
f a   = g . h a . i

are not equivalent! You should understand when function composition typechecks and when it doesn't. If the above 2 were equivalent, then it would mean that the below 2 are also equivalent:

f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs

which makes no sense, because x is not a function with xs as a parameter. x is a parameter to function i, and xs is a parameter to function (i x).

There is a trick to make a function with 2 parameters point-free. And that is to use an “owl” operator:

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

The above two function definitions are equivalent. Read more on “owl” operator.

References

Haskell programming becomes much easier and straightforward, once you understand functions, types, partial application and currying, function composition and dollar-operator. To nail these concepts, read the following StackOverflow answers:

  • On types and function composition
  • On higher-order functions, currying, and function composition
  • On Haskell type system
  • On point-free style
  • On const
  • On const, flip and types
  • On curry and uncurry

Read also:

  • Haskell: difference between . (dot) and $ (dollar sign)
  • Haskell function composition (.) and function application ($) idioms: correct use


来源:https://stackoverflow.com/questions/4340992/haskell-composing-function-with-two-floating-arguments-fails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!