Достойный способ вложенного определения в схеме

Я хочу определить константу 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) и сделайте вычисление снова и снова.

Мои вопросы:

  1. Я думаю, что этот вид вложенного определения имеет смысл, но почему он считается ошибкой синтаксиса?
  2. Есть ли достойный способ написать определение foo?

4 ответа

Решение

Отвечая на ваши вопросы:

  1. define может использоваться только определенным образом, как указано в спецификации. То, что вы хотите сделать, не охватывается спецификацией, следовательно, ошибка. Как Вам известно, define присваивает имя значению выражения, просто вы не можете напрямую создавать внутренние определения в его контексте.
  2. Но есть и другие выражения, которые позволяют создавать новые привязки в этом контексте. по моему мнению 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)))

Это более приемлемо для вас?

Другие вопросы по тегам