Практическое программирование схемы

Это были несколько месяцев, с тех пор как я коснулся Схемы и решил реализовать доход с командной строки partitioner использование Схемы.

Мое начальное внедрение использовало простую рекурсию по продолжению, но я полагал, что продолжение будет более соответствовать этому типу программы. Я ценил бы его, если кто-либо (более квалифицированный со Схемой, чем I) мог бы смотреть на это и предложить улучшения. Я - это несколько (display... строки являются идеальной возможностью использовать макрос также (я просто еще не добрался до макросов).

(define (ab-income)
  (call/cc
   (lambda (cc)
     (let
         ((out (display "Income: "))
          (income (string->number (read-line))))
       (cond
         ((<= income 600)
          (display (format "Please enter an amount greater than $600.00~n~n"))
          (cc (ab-income)))
         (else
          (let
              ((bills    (* (/ 30 100) income))
               (taxes    (* (/ 20 100) income))
               (savings  (* (/ 10 100) income))
               (checking (* (/ 40 100) income)))
            (display (format "~nDeduct for bills:---------------------- $~a~n" (real->decimal-string bills 2)))
            (display (format "Deduct for taxes:---------------------- $~a~n" (real->decimal-string taxes 2)))
            (display (format "Deduct for savings:-------------------- $~a~n" (real->decimal-string savings 2)))
            (display (format "Remainder for checking:---------------- $~a~n" (real->decimal-string checking 2))))))))))

Вызов (ab-income) просит вход и если чему-нибудь ниже 600 предоставляют его (от моего понимания) возвраты (ab-income) в current-continuation. Моя первая реализация (поскольку я сказал ранее), используемая простая-jane рекурсия. Это не было плохо вообще ни один, но я изобразил каждый ответный визит к (ab-income) если значение было ниже 600, продолжал разворачивать функцию.

(исправьте меня, если то предчувствие является неправильным!)

5
задан Eli Barzilay 8 June 2010 в 02:12
поделиться

1 ответ

Во-первых, вам не нужно продолжение. Согласно стандарту, Scheme всегда будет выполнять оптимизацию хвостового вызова . Хвостовой вызов - это вызов функции, который находится в последней позиции функции; после выполнения этого вызова больше ничего не произойдет. В этой ситуации нам не нужно сохранять текущую запись активации; как только функция, которую мы вызываем, вернется, мы просто вставим ее. Следовательно, хвостовой вызов повторно использует текущую запись активации. В качестве примера рассмотрим следующее:

(define (some-function x y)
  (preprocess x)
  (combine (modified x) y))
(some-function alpha beta)

Когда мы вызываем некоторую функцию , мы выделяем место для ее записи активации в стеке: локальные переменные, параметры и т. Д. Затем мы вызываем (preprocess x ) . Поскольку нам нужно вернуться к some-function и продолжить обработку, мы должны сохранить запись активации some-function , и поэтому мы помещаем новую запись активации для препроцесс . Как только это вернется, мы извлекаем кадр стека препроцессора и продолжаем работу. Затем нам нужно оценить модифицированный ; должно произойти то же самое, и когда модифицированный возвращается, его результат передается в комбайн .Можно было бы подумать, что нам нужно создать новую запись активации, запустить comb , а затем вернуть это в some-function - но some-function не делает » с этим результатом ничего не нужно делать, а возвращать его! Таким образом, мы перезаписываем текущую запись активации, но адрес возврата оставляем в покое; когда comb возвращается, тогда он вернет свое значение именно то, что его ожидало. Здесь (объединить (модифицированный x) y) - это хвостовой вызов, и его оценка не требует дополнительной записи активации.

Вот как вы можете реализовать циклы в схеме, например:

(define (my-while cond body)
  (when (cond)
    (body)
    (my-while cond body)))

(let ((i 0))
  (my-while (lambda () (< i 10))
            (lambda () (display i) (newline) (set! i (+ i 1)))))

Без оптимизации хвостовых вызовов это было бы неэффективно и потенциально могло бы привести к переполнению из-за длительного цикла, создающего множество вызовов к my- а . Однако благодаря оптимизации хвостового вызова рекурсивный вызов my-while cond body является скачком и не выделяет памяти, что делает его таким же эффективным, как итерация.

Во-вторых, здесь не нужны никакие макросы. Хотя вы можете абстрагироваться от блока display , вы можете сделать это с помощью простой функции. Макросы позволяют вам на каком-то уровне изменить синтаксис языка - добавить свой собственный вид define , реализовать некоторую конструкцию типа case, которая не оценивает все ее ветви и т. Д. Конечно, это все по-прежнему s-выражения, но семантика больше не просто «оценивает аргументы и вызывает функцию». Однако здесь семантика вызова функций - это все, что вам нужно.

С учетом сказанного, я думаю, что вот как я бы реализовал ваш код:

(require (lib "string.ss"))

(define (print-report width . nvs)
  (if (null? nvs)
    (void)
    (let ((name  (car  nvs))
          (value (cadr nvs)))
      (display (format "~a:~a $~a~n"
                       name
                       (make-string (- width (string-length name) 2) #\-)
                       (real->decimal-string value 2)))
      (apply print-report width (cddr nvs)))))

(define (ab-income)
  (display "Income: ")
  (let ((income (string->number (read-line))))
    (if (or (not income) (<= income 600)) 
      (begin (display "Please enter an amount greater than $600.00\n\n")
             (ab-income))
      (begin (newline)
             (print-report 40 "Deduct for bills"       (* 3/10 income)
                              "Deduct for taxes"       (* 2/10 income)
                              "Deduct for savings"     (* 1/10 income)
                              "Remainder for checking" (* 4/10 income))))))

Во-первых, по крайней мере, в моей версии mzscheme мне нужна была (require (lib "string .ss ")) строка для импорта вещественная-> десятичная строка . Затем я выделил блок display , о котором вы говорили. Мы видим, что каждая строка хочет напечатать деньги в том же формате в 40-м столбце, напечатав имя тега и строку тире перед ним. Следовательно, я написал печатный отчет . Первый аргумент - это начальная ширина; в данном случае 40 . Остальные аргументы представляют собой пары «поле-значение». Длина каждого поля (плюс два для двоеточия и пробела) вычитается из ширины, и мы генерируем строку, состоящую из этого количества тире. Мы используем формат для размещения полей в правильном порядке и display для печати строки. Функция выполняет рекурсию по всем парам (с использованием хвостовой рекурсии, поэтому мы не взорвем стек).

В основной функции я переместил (отображение «Доход:») до let ; вы игнорируете его результат, так зачем назначать его переменной? Затем я дополнил условие if , чтобы проверить, ложно ли input , что происходит, когда string-> number не может проанализировать ввод. Наконец, я удалил ваши локальные переменные, поскольку все, что вы делаете, это их распечатываете, и использовал синтаксис дробей Scheme вместо деления. (И, конечно же, я использую print-report вместо display s и формат s.)

Думаю, это все; Если у вас есть другие вопросы о том, что я сделал, не стесняйтесь спрашивать.

17
ответ дан 18 December 2019 в 09:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: