12

У меня трудно понять, что происходит с evens-only*&co например The Little интриган на странице 145.Маленький Schemer сглаживает только * & со

Вот код:

(define evens-only*&co 
(lambda (l col) 
    (cond 
    ((null? l) 
    (col '() 1 0)) 
    ((atom? (car l)) 
    (cond 
     ((even? (car l)) 
     (evens-only*&co (cdr l) 
        (lambda (newl product sum) 
         (col (cons (car l) newl) 
          (opx (car l) product) 
          sum)))) 
     (else 
     (evens-only*&co (cdr l) 
        (lambda (newl product sum) 
         (col newl product (op+ (car l) sum))))))) 
    (else 
    (evens-only*&co (car l) 
        (lambda (newl product sum) 
        (evens-only*&co (cdr l) 
            (lambda (dnewl dproduct dsum) 
             (col (cons newl dnewl) 
              (opx product dproduct) 
              (op+ sum dsum)))))))))) 

Начальная col может быть :

(define evens-results 
(lambda (newl product sum) 
    (cons sum (cons product newl)))) 

то, что я не получаю, с l как '((1) 2 3), он идет сразу в финал else с (car l) как (1) и (cdr l) как (2 3). Хорошо, но мой разум пуст, пытаясь разобраться в dnewl, dproduct, dsum от newl, product, sum. Было бы также полезно, если бы кто-нибудь мог меня обучить тому, как настроить DrRacket или Chez Scheme или MIT-Scheme для запуска степпера.

Но, возможно, я слишком рано размахиваю. Является ли какой-нибудь новичок, впервые читающий это, в первый раз на самом деле должен понимать это дикое продолжение?

+0

Для настройки шагомера см. Http://stackoverflow.com/questions/10499514/y-combinator-discussion-in-the-little-schemer/10500358#10500358 – soegaard

ответ

16

Я нашел этот раздел в замешательстве и в первом чтении, и только начал его получать после того, как я прочитал в другом месте о продолжениях и стилях продолжения прохождения (вот что это такое).

С риском объяснить что-то, что у вас уже есть, один из способов взглянуть на него, который помог мне, - подумать о «сборщике» или «продолжении» как о замене нормального способа возврата функции. В нормальном стиле программирования вы вызываете функцию, получаете значение и делаете что-то с ним в вызывающем. Например, стандартная рекурсивная функция length включает в себя выражение (+ 1 (length (cdr list))) для непустого случая. Это означает, что после того, как (length (cdr list)) вернет значение, произойдет подсчет ожидаемого результата с любым значением, которое мы можем назвать (+ 1 [returned value]). В обычном программировании интерпретатор отслеживает эти ожидающие вычисления, которые имеют тенденцию «складываться», как вы можете видеть в первых парах глав книги. Например, при вычислении длины списка рекурсивно мы имеем гнездо «ожидающих вычислений» как много уровней в глубину, так как список длинный.

В продолжении прохождения стиля вместо вызова функции и использования возвращаемого вызывается функция вызова, мы сообщаем функции, что делать, когда она производит свое значение, предоставляя ей «продолжение» для вызова. (Это похоже на то, что вы делаете с обратными вызовами в асинхронном программировании Javascript, например: вместо записи result = someFunction(); вы пишете someFunction(function (result) { ... }), а весь код, который использует result, входит в функцию обратного вызова).

Вот length в продолжении прохождения стиля, просто для сравнения. Я вызвал параметр продолжения return, который должен предлагать, как он функционирует здесь, но помните, что это обычная переменная Scheme, как и любая другая. (Часто параметр продолжения называется k в этом стиле).

(define (length/k lis return) 
    (cond ((null? lis) (return 0)) 
     (else 
     (length/k (cdr lis) 
        (lambda (cdr-len) 
        (return (+ cdr-len 1))))))) 

Существует полезный совет для чтения этого кода в an article on continuations by Little Schemer co-author Dan Friedman. (См. Раздел II-5, начиная со страницы 8).Перефразируя, вот что оговорка else выше говорит:

представьте, что вы есть результат вызова length/k на (cdr lis) и называют его cdr-len, затем добавьте и передать результат этого добавления вашему продолжение (return) ,

Обратите внимание, что это почти то, что переводчик должен сделать в оценке (+ 1 (length (cdr lis))) в нормальной версии функции (кроме того, что он не должен дать название промежуточного результата (length (cdr lis)). Пропуская вокруг продолжениях или обратных вызовах, мы сделали явный поток управления (и имена промежуточных значений) явным, вместо того, чтобы интерпретатор отслеживал его.

Давайте применим этот метод к каждому пункту в evens-only*&co. факт, что эта функция производит три значения, а не один: вложенный список с нечетными номерами удален; произведение четных чисел; и сумма нечетных чисел. Вот первый пункт, где (car l), как известен, четное число:

(evens-only*&co (cdr l) 
       (lambda (newl product sum) 
        (col (cons (car l) newl) 
         (opx (car l) product) 
         sum))) 

Представьте себе, что у вас есть результаты удаления нечетных чисел, умножаются эвены, и добавления нечетных чисел из cdr списка, и назовите их newl, product и sum соответственно. cons глава списка на newl (так как это четное число, оно должно пойти в результате); умножить product на головку списка (с мы вычисляем произведение эвенов); оставить только sum; и передайте эти три значения в ожидаемое продолжение col.

Вот случай, когда голова списка является нечетным числом:

(evens-only*&co (cdr l) 
       (lambda (newl product sum) 
        (col newl product (op+ (car l) sum)))) 

Как и раньше, но проходят одни и те же значения newl и product продолжения (то есть «возвращение» их), вместе с суммой sum и главой списка, так как мы суммируем нечетные числа.

И вот последняя, ​​где (car l) вложенный список, и который немного усложнен двойная рекурсия:

(evens-only*&co (car l) 
       (lambda (newl product sum) 
        (evens-only*&co (cdr l) 
            (lambda (dnewl dproduct dsum) 
            (col (cons newl dnewl) 
             (opx product dproduct) 
             (op+ sum dsum)))))) 

Imagine у ​​вас есть результаты от удаления, суммирования и сложения чисел в (car l) и называть их newl, product и sum; то представьте, что у вас есть результаты от того же дела до (cdr l), и назовите их dnewl, dproduct и dsum.К вашему ожиданию продолжение, дайте значения, полученные cons ing newl и dnewl (так как мы составляем список списков); умножение вместе product и dproduct; и добавление sum и dsum.

Примечание: каждый раз, когда мы делаем рекурсивный вызов, мы строим новое продолжение для рекурсивного вызова, который «закрывает над» текущими значениями аргумента, l и возвратную продолжение - col, другими словами, , вы можете думать о цепочке продолжений, которую мы создаем во время рекурсии, как моделирование «стека вызовов» более условно написанной функции!

Надеюсь, что дает часть ответа на ваш вопрос. Если я немного переусердствовал, то только потому, что я думал, что после рекурсии, продолжения являются второй по-настоящему опрятной, расширяющей разум идеей в The Little Schemer и программирования в целом.

+0

Спасибо, Джон О. Я все еще нечеткий о том, что происходит в третьем случае, где это вложенный список, а не атом. Предположим, что входной список l равен ((2)), что означает, что в первый раз через него попадает случай 3 с: (2) для (автомобиль l) и() для (cdr l) и мои evens-результаты для col - который затем идет сначала вниз, повторяется. Возвращаясь только к evens-only * & co, это верно для первого cond, (null? L) и выбирает '(), 1, 0 для dnewl, dproduct, dsum. Но это первый раз, когда, предположительно, новый, продукт, сумма не была бы инициализирована или даже существовала. Но, очевидно, я вижу это неправильно. , , , – melwasul

+0

@melwasul '((2))' переводится в 'col (cons (cons 2())()) (* (* 2 1) 1) (+ 0 0)'. –

0

Я читал, как программировать программы (felleisen et.al.). Я просматриваю раздел, где они определяют локальные определения. Я написал код, который реализует вышеописанный evens-only & co, используя локальное определение. Вот что я написал:

(define (evens-only&co l) 
    (local ((define (processing-func sum prod evlst lst) 
      (cond ((null? lst) (cons sum (cons prod evlst))) 
        ((atom? (car lst)) 
        (cond ((even? (car lst)) (processing-func sum (* prod (car lst)) (append evlst (list (car lst))) (cdr lst))) 
         (else 
          (processing-func (+ sum (car lst)) prod evlst (cdr lst))))) 
        (else 
        (local ((define inner-lst (processing-func sum prod '() (car lst)))) 
        (processing-func (car inner-lst) (cadr inner-lst) (append evlst (list (cddr inner-lst))) (cdr lst))))))) 
    (processing-func 0 1 '() l))) 

Для тестирования, когда я вхожу (сглаживает только & со '((9 1 2 8) 3 10 ((9 9) 7 6) 2)), она возвращает' (38 1920 (2 8) 10 (() 6) 2) как ожидалось в маленьком интригане. Но мой код выходит из строя в одном условии: когда нет четных чисел вообще, произведение эвенов по-прежнему отображается как 1. Например (только evens-only & co '((9 1) 3 ((9 9) 7))) возвращает '(38 1() (())). Думаю, мне понадобится дополнительная функция, чтобы исправить это. @melwasul: Если вы не знакомы с местными, извините, что опубликуйте здесь. Я предлагаю вам также прочитать HTDP. Это отличная книга для начинающих. Но ребята, которые являются экспертами в схеме, могут поместить свои комментарии и в мой код. Правильно ли я понимаю местное определение?

+0

На самом деле, правильно, что продукт должен быть «1», если в списке нет четных чисел: произведение пустого списка чисел должно быть мультипликативным тождеством «1», как сумма пустого списка является аддитивным тождеством, '0'. Например, попробуйте оценить '(*)' в командной строке Scheme. –

+0

Вы правы. Спасибо за разъяснения. –

1

answer от Jon O. - это действительно отличное углубленное объяснение базовых концепций. Хотя для меня (и, надеюсь, для некоторых других людей) понимание таких понятий намного проще, когда они имеют визуальное представление.

Итак, я поставил подготовил два блок-схем (по аналогии с once I did for multirember&co, распутать то, что происходит во время вызова evens-only*&co

когда l является:

'((9 1 2 8) 3 10 ((9 9) 7 6) 2) 

и col является:

(define the-last-friend 
    (lambda (newl product sum) 
     (cons sum (cons product newl)) 
    ) 
) 

Одна блок-схема, отражающая , как переменные относятся к разности различны шаги рекурсии: variable relations Второй блок-схема, показывающая фактические значения , передается: actual values

Моя надежда состоит в том, что этот ответ будет приличным дополнением к Jon's explanation above.

0

В эквациональном псевдокоде (а KRC -кака нотации, написание f x y для вызова (f x y), где она однозначна), это

evens-only*&co l col 
    = col [] 1 0          , IF null? l 
    = evens-only*&co (cdr l) 
        (newl product sum => 
         col (cons (car l) newl) 
          (opx (car l) product) 
          sum)     , IF atom? (car l) && even? (car l) 
    = evens-only*&co (cdr l) 
        (newl product sum => 
         col newl product (op+ (car l) sum))  , IF atom? (car l) 
    = evens-only*&co (car l) 
        (anewl aproduct asum => 
         evens-only*&co (cdr l) 
             (dnewl dproduct dsum => 
              col (cons anewl dnewl) 
               (opx aproduct dproduct) 
               (op+ asum  dsum))) , OTHERWISE 

Это CPS кода, который собирает все эвен из входного списка вложенных (т. е. дерево) при сохранении древовидной структуры, а также находит произведение всех эвенов; как и для не-эвенов, он подводит их:

  • если l является пустым списком, три основных значения (идентичности) передаются в качестве аргументов COL;

  • , если (car l) является четным числом, то результаты обработки (cdr l) являются newl, product и sum, , а затем они передаются в качестве аргументов col в то время как первые два дополнены consing   ⁄   умножителя с (car l) (четное число);

  • , если (car l) представляет собой атом, который не является четным числом, то результаты обработки (cdr l) являются newl, product и sum, , а затем они передаются в качестве аргументов col с третьим дополненным путем суммирования с (car l) (атом нечетного числа);

  • , если (car l) список, результаты их обработки (car l) являются anewl, aproduct и asum, , а затем результаты их обработки (cdr l) являются dnewl, dproduct и dsum, , а затем три объединенные результаты передан в качестве аргументов col.

[], 1 и 0 базового случая являются элементами идентичности monoids списков, чисел по умножению, и цифры относительно сложения, соответственно. Это означает только особые значения, которые не меняют результат, когда они объединены в него.

В качестве иллюстрации для '((5) 2 3 4) (что близко к примеру, в вопросе), он создает расчет

evens-only*&co [[5], 2, 3, 4] col 
= 
col (cons []     ; original structure w only the evens kept in, 
      (cons 2    ; for the car and the cdr parts 
       (cons 4 []))) 
    (opx 1      ; multiply the products of evens in the car and 
      (opx 2 (opx 4 1))) ; in the cdr parts 
    (op+ (op+ 5 0)    ; sum, for the non-evens 
      (op+ 3 0))  

Подобно my other answer (к родственному вопросу), вот еще один способ пишу это, с скороговоркой сопоставления псевдокода (с охраной):

evens-only*&co = g where 
    g [a, ...xs...] col 
     | pair? a = g a (la pa sa => 
         g xs (ld pd sd => 
             col [la, ...ld...] (* pa pd) (+ sa sd))) 
     | even? a = g xs (l p s => col [ a, ...l... ] (* a p)  s ) 
     | otherwise = g xs (l p s => col   l   p (+ a s) ) 
    g []   col =     col []    1   0 

экономики (и разнообразие) это обозначение действительно делает это все гораздо яснее, EAS ier to просто см. вместо того, чтобы потеряться в слове салат длинных имен для функций и переменных, так как парны перегружены как синтаксические разделители для данных списка, группировки предложений (например, в выражениях cond), привязки имен (в выражениях lambda) и функциональные указатели вызовов все точно одинаковый. Такая же однородность обозначений S-выражений, которые способствуют легкости манипулирования машиной (то есть lisp's read и макросами) является тем, что наносит ущерб его читабельности .