Достойный способ вложенного определения в схеме
Я хочу определить константу foo
используя вспомогательную функцию, скажем, bar
, И я хочу спрятаться bar
внутри определения foo
Итак, я пришел с этим кодом:
(define foo
(define (bar n)
(+ n n))
(bar 1))
Однако это определение вызывает синтаксические ошибки во многих реализациях схемы (mit-схема, ракетка, guile и т. Д.).
У меня есть три обходных пути, но ни один из них не кажется удовлетворительным:
(define foo1
((lambda ()
(define (bar n)
(+ n n))
(bar 1))))
(define foo2
(let ((bar (lambda (n) (+ n n))))
(bar 1)))
(define (foo3)
(define (bar n)
(+ n n))
(bar 1))
foo1
использует лямбду для создания среды написания вспомогательных определений, а круглые скобки кажутся несколько запутанными.
foo2
использует выражение let, но я больше не могу использовать синтаксический сахар (define (f n) ...)
=> (define f (lambda (n) ...))
foo3
требует меньше модификаций по сравнению с исходным, но каждый раз, когда я хочу это значение, я должен вызвать (foo3)
и сделайте вычисление снова и снова.
Мои вопросы:
- Я думаю, что этот вид вложенного определения имеет смысл, но почему он считается ошибкой синтаксиса?
- Есть ли достойный способ написать определение
foo
?
4 ответа
Отвечая на ваши вопросы:
define
может использоваться только определенным образом, как указано в спецификации. То, что вы хотите сделать, не охватывается спецификацией, следовательно, ошибка. Как Вам известно,define
присваивает имя значению выражения, просто вы не можете напрямую создавать внутренние определения в его контексте.- Но есть и другие выражения, которые позволяют создавать новые привязки в этом контексте. по моему мнению
foo2
это лучший вариант, и это тоже идиоматично. И еслиbar
было рекурсивное определение, вы могли бы использоватьletrec
,
Но если вас немного беспокоит потеря синтаксического сахара (из-за того, как процедуры определены внутри let
выражение), затем попробуйте использовать local
Сработает в Racket:
(define foo
(local [(define (bar n) (+ n n))]
(bar 1)))
Ваш исходный код имеет синтаксическую ошибку, потому что синтаксис, необходимый для define
идентификатора
(define <identifier> <expression>)
но ваш синтаксис
(define <identifier> <definition> <expression>)
Вам нужен какой-то способ сгруппировать <definition>
и <expression>
, То, что вы ищете, это то, что позволяет лексические определения - в Схеме это синтаксическая форма с <body>
, Синтаксические формы для этого являются lambda
или любой let
(и варианты) или "программный" begin
,
Но это легко сделать в схеме без необходимости расширения Racket или дополнительных пустых лексических сред или <body>
синтаксическая форма. Просто используйте то, что вы считаете "неудовлетворительным"
(define foo
(let ((bar (lambda (x) (+ x x))))
(bar 1)))
или даже
(define foo
((lambda (x) (+ x x)) 1))
Слишком много сахара, даже синтаксический сахар, могут иметь неблагоприятные последствия для здоровья...
Если я правильно понимаю ваш вопрос, другой идиоматический способ сделать это в Racket - использовать модуль.
Этот модуль может быть определен с использованием отдельного файла:
;; foo.rkt
#lang racket
(define (bar n)
(+ n n))
(define foo (bar 1))
(provide foo)
;; use-foo.rkt
#lang racket
(require "foo.rkt")
foo
Или через module
Форма в одном файле:
#lang racket
(module 'foo-mod racket
(define (bar n)
(+ n n))
(define foo (bar 1))
(provide foo))
(require 'foo-mod)
foo
Это сжато по сравнению с вашими примерами? Конечно, нет. Но на практике этот тип инкапсуляции обычно работает при модульности модуля.
Например, частный помощник, такой как
bar
может быть полезно при определении нескольких других функций или констант.Зачастую форма файла удобнее: если помощнику нравится
bar
не является вложенным, но вместо этого на верхнем уровне модуля дляfoo.rkt
проще отладить в REPL.
PS Ракетка обеспечивает define-package
форма для совместимости с Chez Scheme, но она не идиоматична в Racket; вместо этого вы бы использовали подмодуль.
foo1
также эквивалентно следующему:
(define foo1
(let ()
(define (bar n)
(+ n n))
(bar 1)))
Это более приемлемо для вас?