24

我主要是一个 C++(因此是一个 OO/命令式)程序员,我发现在条件语句中每次评估只能有一个语句,例如功能语言 Scheme 中的 if 语句,这很奇怪。

例如:

 (let ((arg1 0) (arg2 1))
   (if (> arg1 arg2)
       arg1
       arg2)))

错误的例子:

(let ((arg1 0) (arg2 1))
  (if (> arg1 arg2)
      (arg1 (display "cool"))
      (arg2 (display "not cool"))))

给我一个类型的错误“程序应用程序:预期的程序,给定:2;参数是:#void”

这可以通过将该条件语句放入已定义函数的主体内的不同语句中来解决,例如,条件语句的主体每次都有单独的语句,如下所示:

(if (condition) statement1a statement2a)
(if (condition) statement1b statement2b)

等等...

不用说,它不太实用。更不用说重复的代码开销了。

我在这里遗漏了什么还是真的没有其他办法?

4

5 回答 5

24
(let((arg1 0)(arg2 1))
  (if (> arg1 arg2) 
      (begin
        (display arg1)
        (newline)
        (display "cool"))
      (begin
        (display arg2)
        (newline)
        (display "not cool"))))

当您说 (arg1 (disply "cool")) 时,您是在暗示 arg1 应该是一个过程。

于 2012-06-29T14:21:42.737 回答
10

您可能缺少的一件事是,在 Scheme 中没有“声明”之类的东西。一切都是表达式,您可能认为语句的事物也返回一个值。这适用于if,它通常用于返回一个值(例如,(if (tea-drinker?) 'tea 'coffee)。与 C++ 不同,条件的大多数用途不会用于改变变量或打印值。这减少了在if子句中具有多个表达式的需要。

但是,正如 Ross 和 Rajesh 所指出的,您可以在子句中使用cond(推荐)或使用begins 。if请注意,如果您在条件中有许多副作用计算,那么您可能不会习惯性地使用 Scheme。

于 2012-06-29T15:27:33.777 回答
5

@RajeshBhat 给出了一个在 if 语句中使用 begin 的好例子。

另一种解决方案是cond表格

(let ([arg1 0] [arg2 1])
  (cond
    [(< arg1 0) (display "negative!")]
    [(> arg1 arg2) (display arg1) (newline) (display "cool")]
    [else (display arg2) (newline) (display "not cool")]))

表单中的每一行cond都有一个隐含的begin内容,如果您查看cond.

(链接是 Chez Scheme 文档,可能(阅读:可能)与您使用的实现不同,因为它是专有的,尽管 Petite Chez 是免费的(小版本中没有编译器))

http://scheme.com/tspl4/syntax.html#./syntax:s39

编辑:关于开始形式的重要说明,因此所有具有隐式开始的表达式。

以下代码

(+ 2 (begin 3 4 5))

计算结果为 7。这是因为begin表单的返回值是它的最后一个表达式。这只是开始使用时要记住的事情。但是,使用副作用和诸如显示之类的东西在 3 和 4 所在的位置可以正常工作。

于 2012-06-29T14:57:00.663 回答
1

既然您已经在“内部”过程中使用了迭代过程,为什么不使用命名的 let来使用这个定义

(define (fact n)
  (let inner ((counter 1) (result 1))
    (if (> counter n)
        result
        (inner (+ counter 1) (* result counter)))))

由于进程的状态可以仅用 2 个变量来确定,因此它不会使用那么多内存。

例如(事实 6)是这样计算的

(inner 1 1)
(inner 2 1)
(inner 3 2)
(inner 4 6)
(inner 5 24)
(inner 6 120)
(inner 7 720)
720

这是相同过程的 letrec 版本:

(define (fact n)
  (letrec ((inner
            (lambda (counter result)
              (if (> counter n)
                  result
                  (inner (+ counter 1) (* result counter))))))
    (inner 1 1)))
于 2012-06-30T13:03:23.457 回答
1

如果你觉得受到 Scheme 语法的限制,你总是可以通过定义一个宏来改变语法。宏类似于 lambda,但它在编译时生成代码(如 C++ 模板),并且在调用宏之前不会评估其参数。

您可以轻松地制作一个宏,让您使用通常表示过程应用程序的语法,例如(arg1 "cool"),表示“在括号内显示所有内容,并在每个项目后添加换行符”。(当然,这意味着只在宏内部。)像这样:

(define-syntax print-if
  (syntax-rules ()
    [(_ c (true-print ...) (false-print ...))
      (if c
          (display-with-newlines true-print ...)
          (display-with-newlines false-print ...))]))

(define-syntax display-with-newlines
  (syntax-rules ()
    [(_ e)
      (begin (display e) (newline))]
    [(_ e0 e* ...)
      (begin (display-with-newlines e0) (display-with-newlines e* ...)) ]))

(let ([arg1 0] [arg2 1])
  (print-if (> arg1 arg2)
            (arg1 "cool")
            (arg2 "not cool")))

输出:

1
not cool

如果您现在不了解宏定义的工作原理,请不要担心。如果您只是在掌握 C++ 之后才尝试使用 Scheme,那么您无疑会遇到很多挫折。您应该对 Scheme 真正拥有的那种力量和灵活性有一个初步的了解。

Scheme 宏和 C++ 模板之间的一个很大区别在于,在宏中,您可以使用整个 Scheme 语言。宏告诉使用 Scheme,如何以您喜欢的任何完全任意的方式将 s-expr 转换为 Scheme 代码。然后编译器编译宏输出的 Scheme 代码。由于 Scheme 程序本身就是 s-exprs,因此基本上没有任何限制(除了词法范围和需要将所有内容括在括号中)。

如果你愿意,不要让任何人阻止你使用副作用。Scheme 的荣耀在于你可以为所欲为

于 2012-07-03T15:25:08.843 回答