【问题标题】:How do I handle a variable number of arguments passed to a function in Racket?如何处理传递给 Racket 中函数的可变数量的参数?
【发布时间】:2013-08-13 14:00:41
【问题描述】:

我喜欢创建带有无限数量参数的函数,并且能够将它们作为列表处理。它在创建二叉树时对我很有用,我现在正在使用它来改进最近邻算法。然而,我的方法真的很糟糕:因为我想不出一种方法来遍历不正确的列表(这很可能是不正确的和退化的),我尝试使用各种列表函数将不正确的列表强制转换为列表形式。

这是我在确定地图节点之间差异的简单函数中的最佳尝试(有效,只是不确定为什么有效):

(define distance-between
  (lambda xs
    (let ([input-list (list* xs null)])
      (letrec 
          ([f (lambda (xs acc)
               (if (null? (cdr xs))
                   acc
                   (f (cdr xs) 
                      (+ (abs (- (map-node-x (car xs)) 
                                 (map-node-x (cadr xs))))
                         (abs (- (map-node-y (car xs)) 
                                 (map-node-y (cadr xs))))
                         acc))))])                   
       (f (car input-list) 0)))))

如您所见,这是一个丑陋的解决方案,并且涉及一些对我来说似乎很神奇的东西 - 为什么当我将不正确的列表包含在列表中时,它会被强制转换为列表形式*? (注意:这句话具有误导性,这不会发生)。

我宁愿有一个漂亮的解决方案而不是魔法。有人可以帮忙吗?

例如,典型的输入是:

(distance-between (map-node 1 2) (map-node 2 3) (map-node 3 4))

预期结果:

4

(map-node (a) 和 m-n (b) 之间的距离为 2,加上 map-node (b) 和 map-node (c) 之间的距离为 2)。

也可以简单地输入:

(distance-between (map-node 1 2) (map-node 2 2))

并得到答案:

1

如果我在原始输入上尝试此操作,而没有我的 (let ([input-list...])...) 语句,则会导致错误,因为 (? 实际上不确定为什么要回答这个问题) .

函数按预期工作。

【问题讨论】:

  • 在什么情况下您可能会处理不正确的列表?可变参数列表总是正确的。此外,您应该使用示例输入和输出来描述您的函数应该做什么。
  • 我不确定可变参数列表是什么(也许除了这个)。我的想法是,这些列表不适合堆栈溢出的其他一些答案,以及未处理的列表 xs 不被任何其他函数视为列表的事实 - 他们抛出错误说它不是列表。此外,它不会自动以 null 结尾,我认为所有 Lisps 都将其用作其列表的基础。我将描述我的函数现在做了什么,但它完全按照预期工作。
  • 基本上我的困惑源于这样一个事实,即当我尝试在...可变参数列表的原始输入上使用诸如 (length xs) 之类的列表函数时?它们会引发错误。
  • 在示例中,问题不在于函数,而在于您调用它的方式。应该是:(define dist (distance-between (map-node 1 2) (map-node 2 3) (map-node 3 4)))
  • 糟糕。对不起,那是一个错字。我通常不是这样称呼它的。但是,您完全正确,问题出在其他地方。

标签: list arguments scheme racket variadic-functions


【解决方案1】:

作为可变参数列表接收到的列表没有什么不妥(意思是:可变数量的参数)。例如:

(define test-list
  (lambda xs
    (length xs))) ; xs is a normal list, use it like any other list

(test-list 1 2 3 4)
=> 4

在上面的例子中,xs 参数是一个普通的、普通的、普通的列表,没有什么不妥之处。您可以像遍历任何其他列表一样遍历它。不用car了,已经是列表了!另外,请注意同样的函数可以写成这样:

(define (test-list . xs)
  (length xs))   ; xs is a normal list, use it like any other list

仅供参考:不正确的列表是以空列表结尾的列表。例如:'(1 2 3 . 4)。同样,这不是可变参数列表的外观。

【讨论】:

  • 我觉得这很有趣,因为这是我自己看到的,但是 (length xs) 之类的列表函数拒绝对我使用的输入起作用。
  • @JamesDuval 那么问题在于输入,而不是可变参数列表的工作方式
  • @JamesDuval 你应该发一个SSCCE 来证明这个问题。
【解决方案2】:

我也不明白你的可变参数列表怎么可能不合适。

但要回答您的原始问题(如何更优雅地迭代可能不正确的列表),这是使用match 的一种方法:

#lang racket

(define (properly-sum-improper-list xs)
  (let loop ([acc 0]
             [xs xs])
    (match xs
      [(list) acc]
      [(cons x more) (loop (+ acc x) more)]
      [x (+ acc x)]))) ;last item of improper list

(require rackunit)
(check-equal? (properly-sum-improper-list '(1 2 3 4))   10)
(check-equal? (properly-sum-improper-list '(1 2 3 . 4)) 10)

然而,需要这样做,可能表明您想要修复或更改其他内容。

【讨论】:

  • > 需要执行此操作可能表明您想要修复或更改其他内容。 ——绝对同意。我完全误解了问题出在哪里,一旦堆栈溢出这里的人让我直截了当,我就能够迅速解决它!
  • 当我几年前开始学习 Racket(我的第一个真正的 Lisp)时,“正确”与“不正确”列表是其中一种感觉就像是一种神秘的区别而没有区别。 IME 这是您有时想去的一个奇怪的角落,但更有可能偶然发现自己。
【解决方案3】:

你的清单没有错。当您的论点不是一对时,例如(lambda xs body ...)(define (fun . xs) body ...),您的所有论点都会被放入一个列表中。例如..(fun 1 2 3) 会变成 xs '(1 2 3)。执行(list* '(1 2 3) '()) 会使'((1 2 3) 立即撤消,您可以通过使用car 调用循环来立即撤消它,使其再次变为'(1 2 3)

除此之外,您的程序按预期工作。您可能会稍微清理一下您的过程,但是由于没有列表推导可以滑过折叠在两个下一个元素上的列表,因此它不会变得更小。下面是基本相同的代码,但抽象出执行工作的过程(如果存在您可以使用的 foldl 对)并使用 named let 作为迭代器循环(这是语法letrec+call 的糖)。

(define (distance-between e1 . lst)
  (define (add-diff-acc e1 e2 acc)
    (+ (abs (- (map-node-x e1) (map-node-x e2)))
       (abs (- (map-node-y e1) (map-node-y e2)))
       acc))

  (let iterate ((e1 e1) (lst lst) (acc 0))
    (if (pair? lst)
        (let ((e2 (car lst)))
          (iterate e2 (cdr lst) (add-diff-acc e1 e2 acc)))
        acc)))

编辑:关于语法糖,named letletrec

(let ((x 10) (y 19)) 
  body)

是匿名过程调用的语法糖

((lambda (x y) 
    body) 
  10 19)

named let 只是给该过程一个名称,虽然好像是 letrec,进行 recursive 绑定。你用你给的名字来调用它,参数将是你提供的,而不是 let 中的初始值。我已经习惯了,今天更喜欢它们。不过可能需要一些时间来适应。

我们编写的大部分代码都是一些较低级别内容的语法糖。宏是嵌套的,因此您的 letrec 表单 可以 最终减少 lambdas。没有语法糖的整个过程如下所示:

(define distance-between 
  (lambda (e1 . lst)
    ((lambda (add-diff-acc)
       ((lambda (iterate e1 lst acc) ; emulate Y to substitute `letrec`
          (iterate iterate e1 lst acc))
        (lambda (iterate e1 lst acc)
          (if (pair? lst)
              ((lambda (e2)
                 (iterate iterate e2 (cdr lst) (add-diff-acc e1 e2 acc)))
               (car lst))
              acc))
        e1 lst 0))
     (lambda (e1 e2 acc)
       (+ (abs (- (map-node-x e1) (map-node-x e2)))
          (abs (- (map-node-y e1) (map-node-y e2)))
          acc)))))

【讨论】:

  • 我很欣赏重构,但我不明白为什么 named let 是语法糖 - 即为什么它使代码的外观更甜美。对我来说,它使代码看起来更加晦涩难懂——没有letrec,任何读者都必须解析let 语句的特定语法才能意识到它实际上是letrec,确定吗?
  • @JamesDuval 对于经验丰富的 Schemer,命名为 letletrec 更具可读性。命名的let 的工作方式几乎与未命名的let 类似,不同之处在于它创建了一个标签,您可以使用它来跳回(或者,如果需要,递归)到let 的开头。显式 letrec 最好保留用于命名 let 无法处理的情况,例如相互递归函数。
  • @JamesDuval 我在回答中添加了一些内容。
  • @JamesDuval 您的 cmets 促使我写了一篇关于名为 let 的博客文章;希望你喜欢! dyscour.se/post/58639636506/named-let
  • 我愿意;这真是令人着迷。我喜欢当我认为我已经掌握了基础知识并且有人向我展示了它们到底有多深。我仍然没有尽可能多地使用/没有研究宏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 1970-01-01
  • 1970-01-01
  • 2015-09-23
  • 2017-09-28
  • 2019-01-17
  • 2011-05-24
相关资源
最近更新 更多