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

Я хочу определить константу foo с помощью вспомогательной функции, скажем, bar. И я хочу скрыть bar внутри определения foo, поэтому я пришел с этим кодом:

(define foo
  (define (bar n)
    (+ n n))
  (bar 1))

Однако это определение вызывает синтаксические ошибки во многих реализациях схем (мит-схема, рэкет, хитрость и т. д.).

У меня есть три обходных пути, но ни один из них не кажется удовлетворительным:

(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?

person Javran    schedule 03.01.2014    source источник
comment
И foo1, и foo2 эквивалентны (let использует lambda под капотом). С другой стороны, foo3 определяет новую функцию, а не постоянное значение, как другие параметры, так что это не эквивалентно.   -  person Óscar López    schedule 03.01.2014
comment
стандартный и правильный способ переписать вложенные define (которые являются рекурсивными) - это letrec: (define foo (letrec ((bar (lambda(n) (+ n n)))) (bar 1))). что эквивалентно (более популярной) конструкции named let, (define foo (let bar ((n 1)) (+ n n))) (хотя bar здесь вообще не вызывается рекурсивно).   -  person Will Ness    schedule 11.08.2015
comment
(define foo1 ((lambda () (define (bar n) (+ n n)) (bar 1)))), здесь слишком много скобок, удалите одну пару скобок вокруг лямбды; вот так (define foo1 (лямбда () (define (bar n) (+ n n)) (bar 1))). Оно работает.   -  person Alfgaar    schedule 29.12.2017


Ответы (4)


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

  1. define можно использовать только определенным образом в соответствии с спецификация. То, что вы хотите сделать, не охвачено спецификацией, отсюда и ошибка. Как вы знаете, define присваивает имя значению выражения, просто вы не можете напрямую создавать внутренние определения в его контексте.
  2. Но есть и другие выражения, позволяющие создавать новые привязки в этом контексте. ИМХО foo2 здесь лучший вариант, к тому же идиоматический. И если бы bar было рекурсивным определением, вы могли бы использовать letrec.

Но если вас беспокоит потеря синтаксического сахара (из-за того, как процедуры определены внутри выражения let), попробуйте использовать local, это будет работать в Racket:

(define foo
  (local [(define (bar n) (+ n n))]
    (bar 1)))
person Óscar López    schedule 03.01.2014

Если я правильно понимаю ваш вопрос, еще один идиоматический способ сделать это в 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.

p.s. Racket предоставляет форму define-package для совместимости с Chez Scheme, но она не идиоматична в Racket; вместо этого вы должны использовать подмодуль.

person Greg Hendershott    schedule 03.01.2014

В исходном коде есть синтаксическая ошибка, поскольку требуемый синтаксис для 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))

Слишком много сахара, даже синтаксического, может иметь неблагоприятные последствия для здоровья...

person GoZoner    schedule 03.01.2014
comment
Синтаксический сахар вызывает рак точки с запятой. Алан Дж. Перлис, эпиграмма 3. cs.yale.edu/quotes.html - person ben rudgers; 04.01.2014

foo1 также эквивалентен следующему:

(define foo1
  (let ()
    (define (bar n)
      (+ n n))
    (bar 1)))

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

person Chris Jester-Young    schedule 03.01.2014