Существует ли возможность множественных утверждений внутри тела условного оператора?
Я в первую очередь программист С++ (таким образом, OO/императив), и я нахожу довольно причудливым, что вы можете иметь только один оператор для оценки в условном выражении, например if-statement в Scheme, функциональном языке.
Например:
(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)
и т.д.
Само собой разумеется, что это не слишком практично. Не говоря уже о дублировании служебных данных кода.
Мне что-то не хватает или нет другого пути?
Ответы
Ответ 1
(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 должен быть процедурой.
Ответ 2
Одна вещь, которую вам может не хватать, заключается в том, что в Схеме нет такой вещи, как "выражение". Все это выражение, и вы можете считать, что утверждения также возвращают значение. Это относится к if
, который обычно используется для возврата значения (например, (if (tea-drinker?) 'tea 'coffee)
. В отличие от С++, большинство применений условных выражений не предназначены для изменения значений переменных или печати, что уменьшает необходимость наличия нескольких выражений в предложении if
.
Однако, как указал Росс и Раджеш, вы можете использовать cond
(рекомендуется) или использовать begin
в своих предложениях if
. Обратите внимание, что если у вас много побочных эффектов в условном выражении, вы можете не использовать Схему идиоматически.
Ответ 3
@RajeshBhat дал хороший пример использования begin с инструкцией if.
другое решение - это форма 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.
Ответ 4
Поскольку вы уже используете итеративный процесс во внутренней процедуре, почему бы не использовать это определение, используя named let
(define (fact n)
(let inner ((counter 1) (result 1))
(if (> counter n)
result
(inner (+ counter 1) (* result counter)))))
Поскольку состояние процесса может быть определено только с двумя переменными, оно не будет использовать столько памяти.
например (факт 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)))
Ответ 5
Если вы чувствуете ограниченность синтаксисом Scheme, вы всегда можете изменить синтаксис, указав макрос. Макрос похож на лямбда, за исключением того, что он генерирует код во время компиляции (например, шаблон С++), и его аргументы не обрабатываются до вызова макроса.
Вы можете легко сделать макрос, чтобы вы могли использовать синтаксис, который обычно означает процедуру-приложение, например (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
Не волнуйтесь, если вы не понимаете, как работают макроопределения. Если вы просто пробуете Scheme после освоения С++, вы, несомненно, испытываете много разочарований. У вас должно быть небольшое представление о силе и гибкости. Схема действительно есть.
Большая разница между макросами Scheme и шаблонами С++ заключается в том, что в макросе доступен весь язык Схемы. Макрос сообщает, используя Scheme, как преобразовать s-expr в код схемы, любым абсолютно любым способом, который вам нравится. Затем компилятор компилирует код схемы, выводимый макросом. Поскольку программы Scheme сами являются s-exprs, по существу нет ограничений (кроме лексического охвата и необходимости заключать все в круглые скобки).
И не позволяйте никому препятствовать вам использовать побочные эффекты, если хотите. Слава Схемы заключается в том, что вы можете делать все, что хотите.