【问题标题】:How does the flip function work?翻转功能如何工作?
【发布时间】:2013-01-02 01:22:41
【问题描述】:

Haskell 新手在这里。我正在通过 Learn you a haskell,并遇到了翻转函数的定义。

flip' :: (a -> b -> c) -> (b -> a -> c)  
flip' f = g  
    where g x y = f y x

我不明白的是,x 和 y 是从哪里来的?我的意思是,签名告诉我flip' 是一个接受一个函数(带有两个参数)并返回一个函数(同样带有两个参数)的函数。

如果我理解这一点,当我编写一个类似的函数时

foo :: (a -> b) -> a -> b
foo f x = f x   -- applies the function f on x

但是,在这种情况下,我将显式传递参数 [ 即 x ],因此我可以在函数体中访问它。那么flip'函数怎么可以访问参数x和y呢?

【问题讨论】:

  • 你知道(a -> b) -> a -> b(a -> b) -> (a -> b)是等价的吗?
  • 等价因为 -> 运算符关联到右边?类似于我如何将a -> (b -> c) 写为a -> b -> c
  • 没错。我认为看到foo f x = f xfoo f = f 相同(给定相同的类型签名)会有所帮助。无论如何,你提到的那些参数xy是绑定在g的定义中的,这就是它们的来源。 g的定义也可以写成g = (\x y -> f y x)。这意味着flip' 也可以定义为flip' f = (\x y -> f y x),相当于flip' f x y = f y x。这在某种程度上与(->) 的右关联性有关。

标签: haskell


【解决方案1】:

Prelude,位于 hackage.haskell.org 的 base 包中,包含在每个 Haskell 文件中的隐式导入中,其中找到了 flip 函数。在右侧您可以点击“来源”并查看source code for flip

flip         :: (a -> b -> c) -> b -> a -> c
flip f x y   =  f y x

where 子句允许本地定义,x=10y="bla"。您还可以使用与顶层相同的语法在本地定义函数。 add x y = x + y

在下面的等效公式中,我进行了替换 g = f y x

flip         :: (a -> b -> c) -> b -> a -> c
flip f x y   =  g
  where
    g = f y x

现在 g 没有参数。但是,如果我们将 g 定义为 g a b = f b a 那么我们会有:

flip         :: (a -> b -> c) -> b -> a -> c
flip f x y   =  g x y
  where
    g a b = f b a

不,我们可以做一点代数对消(如果你把它想象成数学课上的代数,你会很安全)。专注于:

flip f x y   =  g x y

取消每边的y:

flip f x   =  g x

现在取消 x:

flip f   =  g

现在把它放回完整的表达式中:

flip     :: (a -> b -> c) -> b -> a -> c
flip f   =  g
  where
    g a b = f b a

作为最后的修饰步骤,我们可以将 a 替换为 xb 替换为 y 以将函数恢复为参数名称:

flip     :: (a -> b -> c) -> b -> a -> c
flip f   =  g
  where
    g x y = f y x

正如您所见,翻转的定义有点绕,我们在前奏中开始的内容很简单,是我更喜欢的定义。希望这有助于解释 where 的工作原理以及如何对 Haskell 代码进行一些代数操作。

【讨论】:

  • 我的最后一次编辑是这样我没有在我的 g 定义中隐藏任何名称,希望这会让那些不习惯在 Haskell 中如何工作的人更清楚。
  • 一个小评论:当你做'取消'时,我相信flip 的类型也应该改变。喜欢:flip f x = g x 应该有 flip :: (a -> b -> c) -> b -> (a -> c) 类型。因此flip f = g 应该具有flip :: (a -> b -> c) -> (b -> a -> c) 类型。
  • @Lingxiao - 检查这一点的一种方法是将两者都放入 ghci 并查看 ghc 告诉你什么,它应该给你相同的签名模标签。另一种方法是使用(->) 的右关联属性。含义f :: b -> a -> c 等价于f :: b -> (a -> c) 等价于f::(b -> (a -> c) 等价于f::(a -> b -> c)。这种类型的转换可用于显示您的第一个类型 sig。 flip :: (a -> b -> c) -> b -> (a -> c) 相当于您的第二种类型的签名。 flip :: (a -> b -> c) -> (b -> a -> c).
  • On the right side you can click "source" and see the source code for flip. 在终端中点击?我在 Hoogle 上查了一下,但定义似乎是错误的。 @Lingxiao,如果翻转接受一个函数和两个参数并反转参数,它不应该是flip :: (a -> b- > c) -> (a -> c -> b)吗?
【解决方案2】:

flip' 无法访问 xy。它接收一个参数f,并计算为表达式g。看不到xy

但是,g 本身就是一个函数,由flip'where 子句中的辅助方程定义。

您可以像阅读 flip' 这样的顶级方程式一样阅读 g x y = f y x。所以g 是两个参数的函数,xy。可以访问xy 的是g,而不是flip'。在应用 g 之前,这些参数的值不存在,直到 flip' 返回函数 g(如果有的话)之后。

flip'where 子句中定义g 的特别之处在于它可以访问flip' 的参数,这就是g 的定义方式f.

所以当flip' 被调用时,它会收到一个函数f。对于flip' 的每个特定调用,它都会构造一个新函数gg 在被调用时会收到两个参数,xy,但这还没有发生。 flip' 然后只返回 g 作为结果。

【讨论】:

  • 好的,我认为事情变得不那么模糊了。我看到它的方式 - g 是一种委托(这是正确的术语吗?),当 flip' 被调用时将对其进行评估。
  • @ersran9 不是。 gflip' 的返回值。调用flip'的人得到返回值;他们何时(以及是否以及多少次)调用它取决于他们。与任何返回值一样,之后对其进行的处理与flip' 无关。
【解决方案3】:

让我们找出g的类型。

我们知道翻转类型:(a -> b -> c) -> (b -> a -> c)

因此我们可以推导出f类型:(a -> b -> c)

我们对g有这个定义

g x y = f y x

从右侧我们推断出y :: ax :: b

因此g :: b -> a -> c

请注意,可以在没有“where”子句的情况下重写定义。

flip' f = g where g x y = f y x
-> flip' f a b = g a b where g a b = f b a
-> flip' f a b = f b a

【讨论】:

  • 这几乎没有回答这个问题。问题是关于绑定和使用 x 和 y。
  • 我认为类型分析解释了大部分定义。不过,我希望能更好地解决绑定问题。
【解决方案4】:

简单地说,您还可以在 where 块中定义函数。所以变量xy只是g的形式参数,这就是为什么你可以在g x y = f y x中访问它:g x y定义了形式参数xy,以及f y xg 所做的定义。最后,该定义从flip f = g 返回。

【讨论】:

    【解决方案5】:

    一个简单的例子来理解和说明,在你的 ghci 上做:

    Prelude> sub x y = x - y
    Prelude> sub 3 1
    2
    Prelude> flip sub 3 1
    -2
    

    【讨论】:

    • 我不认为这个问题误解了flip 的用途,而是它的工作原理。
    猜你喜欢
    • 1970-01-01
    • 2014-07-13
    • 1970-01-01
    • 2012-12-07
    • 1970-01-01
    • 2011-06-27
    • 2017-03-30
    • 1970-01-01
    • 2021-08-07
    相关资源
    最近更新 更多