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

发布于 2024-10-05 13:21:19 字数 1309 浏览 1 评论 0原文

我正在尝试编写一个 (Floating a) => 类型的函数一个->一个-> a 具有 (Floating a) => 类型的函数一个-> a 获取类型为 (Floating 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 wiki、Google 等,但没有找到任何与此特定情况相关的函数组合限制。有谁知道为什么会发生这种情况?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

多彩岁月 2024-10-12 13:21:20
   \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
   \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
万人眼中万个我 2024-10-12 13:21:20

您的问题与 Floating 没有任何关系,尽管类型类确实使您的错误更难以理解。以下面的代码为例:

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

test2 :: Int -> Int
test2 x = undefined

testBoth = test2 . test1

testBoth 的类型是什么?好吧,我们采用 (.) :: (b -> c) -> 的类型(a→b)→一个-> c 转动曲柄得到:

  1. b ~ Inttest2的参数与(.)的第一个参数统一)
  2. c ~ Inttest2 的结果与 (.) 第一个参数的结果统一)
  3. a ~ Int< /code> (test1 参数 1 与 (.) 的参数 2 统一)
  4. b ~ Char -> Inttest1 的结果与 (.) 的参数 2 统一)

但是等等!该类型变量 'b' (#4, Char -> Int) 必须与 test2 (#1, Int< /代码>)。哦不!

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

testBoth x = test2 . test1 x

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

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

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.

旧夏天 2024-10-12 13:21:20

您的问题与 Floating 无关,而是与您想要以不进行类型检查的方式组合一个具有两个参数的函数和一个具有一个参数的函数这一事实有关。我将为您提供一个组合函数 reverse 的示例。文件夹(:)[]

反向。 foldr (:) [] 的类型为 [a] -> [a] 并按预期工作:它返回一个反向列表(foldr (:) [] 本质上是 id 用于列表)。

然而,相反。 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< /code> 变为 [a]
  • b 变为 [a]
  • c 变为 [a]

所以<代码>相反。 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 (:)) 的右操作符。 ))。

但因为 foldr (:) 的类型是 [a] -> ([a] -> [a])(.foldr (:)) 的类型为:

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

reverse 。 foldr (:) 不进行类型检查,因为 reverse 的类型为 [a] -> [a],而不是 ([a] -> [a]) -> c!

Owl 运算符

当人们第一次学习 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 是函数(ix) 的参数。

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

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

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

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

另请阅读:

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:

Read also:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文