【问题标题】:Haskell foldr reductionHaskell折叠器减少
【发布时间】:2016-05-01 14:14:45
【问题描述】:

我对 Haskell 还是很陌生,我正在尝试解决一个问题。我尝试了一些东西,但我很确定这是错误的。

问题:

foldr : f x NIL = x
foldr : f x (cons a list) = f a (foldr f x list)

让我们玩得开心,并结合多种不同语言的功能。

(1)借用课程笔记和Haskell,我们定义一个函数foldl,

(a)foldl f x NIL = x

(b)foldl f x (cons a list) = foldl f (f x a) list

(2) 借用 Python,我们可以将字符串视为字符的序列(即列表),

“bar” ≡ (‘b’ ‘a’ ‘r’) ≡ [‘b’, ‘a’, ‘r’]

(3) 借用 Lisp,我们可以使用 cons 单元格来表示或创建列表,

(‘b’ ‘a’ ‘r’) ≡ (cons ‘b’ (cons ‘a’ (cons ‘r’ nil)))

(4) 借用Fortran,我们可以定义一个字符串连接函数, //,

‘concate’//’nation’ ⇒‘concatenation’

(5) 借用Scheme,定义一个toggle函数,

f x y = f y x,
(define toggle (lambda (f x y)  (f y x)))

也就是说,toggle 函数切换了它的参数,在 Lambda Calculus 中是,

|_f. |_x. |_y. f y x

因此,例如,

(f ‘back’ ‘drop’) ⇒(f ‘drop’ back’)

在本题中,请追踪以下函数的执行/缩减:

foldl (toggle //) nil (“foo” “bar” “baz”)

以下是给我的提示:

    foldl (toggle //) nil (“foo” “bar” “baz”)

Step1:从 (1b) 开始,左侧,foldl f x (cons a list)

        with f = (toggle //), x = nil, and list = (“foo” “bar” “baz”)

Step2:使用(3)递归展开列表

Step3:使用(1b),右侧,foldl f(f x a)列表

        to recursively apply f, (toggle //), to the expanded list

Step4:使用 (5) 在列表表达式中应用切换函数。

Step5: 使用 (4), //, 减少表达式

我得到了什么:

foldl (toggle //) nil (“foo” “bar” “baz”)
foldl f x (cons a list) – via (1b)
foldl f x(a list)  – via (3)
foldl f (f x a) list – via (1b)
foldl (toggle //) (toggle // x a) list – via(5)
foldl x // // a list – via (4)
foldl nil a (“foo” “bar” “baz”)

我知道这不可能,但我觉得我就在附近。如果我能得到一些指导,我觉得我可以摆脱困境并成功理解。

【问题讨论】:

  • 在解决问题时坚持使用 Haskell 的语法(方程式,[...],...);这是最清楚的。然后当你完成后,将结果转换回你所期望的任何疯狂的符号组合。

标签: haskell lambda fold reduction


【解决方案1】:

开始
foldl (toggle //) nil (“foo” “bar” “baz”)
{ 1b }
= foldl (toggle //) ((toggle //) nil "foo") (“bar” “baz”)

...

评论/问题:

  • ("foo" "bar" "baz") 不是用于列表的 Haskell! (什么奇怪的"?)
  • (toggle //) 也不是运算符的 Haskell 语法(它是 (toggle (//))
  • 您应该使用什么策略?根据它,您必须继续使用 (toggle //) nil "foo")foldl

toggle 将是

(toggle //) nil "foo"
{ 5 }
= (//) "foo" nil
{ 4 }
"foo"

另一个还是一样的:

foldl (toggle //) ((toggle //) nil "foo") (“bar” “baz”)
{ 1b }
= foldl (toggle //) ((toggle //) ((toggle //) nil "foo") "bar") ("baz")

我相信你可以解决剩下的问题(实际上只是将正确的东西放在正确的位置)

【讨论】:

  • (toggle //) 是有效的语法——它就像 (1 +)——但它是一个类型错误,所以我认为你可能是正确的 (toggle (//)) 的意思。
  • 你是怎么得到的:foldl (toggle //) nil (“foo” “bar” “baz”) { 1b } = foldl (toggle //) ((toggle //)无 "foo") ("bar" "baz")?我不明白你是如何从 1b 得到的。
  • 如果我说(cons "foo" ("bar" "baz")) = ("foo" "bar" "baz")(模语法 - 正如我所说的不是 Haskell - 但 LISP 中的 cons 在 Haskell 中是 (:) 并且如果你说 a:b:c:[] = [a,b,c] = (a b c) = (cons a (b c))那么它应该保持
  • @Carsten 好的。我很困惑切换实际上是如何工作的。对于 foldl (toggle //) ((toggle //) ((toggle //) nil "foo") "bar") ("baz"),toggle 实际上做了什么?我认为它会将旁边元素的位置切换到右侧。一步之后会是: foldl (toggle //) (toggle //) ("foo" nil) "bar") ("baz")
  • 不要认为它有一个参数 - 一个函数 - 这里是 (//) - 它会接受这个并返回一个新函数,该函数将只使用给定的但 flip参数 - 所以如果你这样做 (toggle (//)) a b 它将与 (//) b a 相同,即 Haskell 中的 b // ab ++ a
【解决方案2】:

我要做的第一件事是用 Haskell 语法和名称替换所有外来语法和名称。

toggle              ⇒ flip
nil                 ⇒ []
(“foo” “bar” “baz”) ⇒ ["foo", "bar", "baz"]
(//)                ⇒ (++)

给予:

foldl (toggle (//)) nil (“foo” “bar” “baz”)
⇒ foldl (flip (++)) [] ["foo", "bar", "baz"]

注意:我将(toggle //) 更正为(toggle (//)),因为我认为后者是本意。前者是类型错误。

现在您只需扩展foldl 的定义。同样,我会用 Haskell 语法和名称替换外星人语法和名称。

foldl f x NIL = x
foldl f x (cons a list) = foldl f (f x a) list

变成:

foldl f a [] = a
foldl f a (x:xs) = foldl f (f a x) xs

现在您将foldl 应用于其参数。您可以做几件事。一件事是重写foldl,所以它是一个表达式。

foldl =
  \f -> \a -> \xxs ->
    case xxs of
      []   -> a
      x:xs -> foldl f (f a x) xs

或者在一行中:

foldl = \f -> \a -> \xxs -> case xxs of [] -> a; x:xs -> foldl f (f a x) xs

您还可以将列表["foo", "bar", "baz"] 脱糖为"foo":"bar":"baz":[],这使得模式匹配更加明显。

现在展开foldl 并申请:

foldl (flip (++)) [] ("foo":"bar":"baz":[])
⇒ (\f -> \a -> \xxs -> case xxs of [] -> a; x:xs -> foldl f (f a x) xs) (flip (++)) [] ("foo":"bar":"baz":[])
⇒ (\a -> \xxs -> case xxs of [] -> a; x:xs -> foldl (flip (++)) ((flip (++)) a x) xs) [] ("foo":"bar":"baz":[])
⇒ (\xxs -> case xxs of [] -> []; x:xs -> foldl (flip (++)) ((flip (++)) [] x) xs) ("foo":"bar":"baz":[])
⇒ case "foo":"bar":"baz":[] of [] -> []; x:xs -> foldl (flip (++)) ((flip (++)) [] x) xs

简化case表达式:

case ("foo":"bar":"baz":[]) of [] -> []; x:xs -> foldl (flip (++)) ((flip (++)) [] x) xs
⇒ foldl (flip (++)) ((flip (++)) [] "foo") ("bar":"baz":[])

你可以用同样的方式完成归一化。

一旦您了解 foldl 之类的函数定义实际上只是案例表达式的糖,您就可以走捷径。

注意:还有其他丰富的语法,比如guards和do-notation。当您想要评估它们时,了解底层表达式会有所帮助。

起点:

foldl (flip (++)) [] ("foo":"bar":"baz":[])

我们可以看看foldl这个定义:

foldl f a [] = a
foldl f a (x:xs) = foldl f (f a x) xs

然后根据匹配的情况立即选择a 定义或foldl f (f a x) xs 定义。在这种情况下是后者。选择正确的定义后,我们将所有参数替换为实参,并快速得出与之前相同的表达式:

foldl (flip (++)) ((flip (++)) [] "foo") ("bar":"baz":[])

您可以看到我的一般方法是简化语法。也就是说,操作更简单,而不是更容易读取或写入,这通常意味着减少到更少的原语。我们只使用case、抽象(即\x -> m)和应用程序(即f x,用参数代替参数)进行管理。如果如何使用更丰富的语法(或者在这个问题的情况下,发明的语法)的规则不清楚,这是一个很好的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-12-27
    • 2022-01-08
    • 2012-02-21
    • 2011-12-05
    • 1970-01-01
    • 2019-06-06
    • 2015-08-30
    • 2018-12-03
    相关资源
    最近更新 更多