Объясните пример продолжения на стр.137 «Маленького мошенника». - PullRequest
14 голосов
/ 10 августа 2011

Код, о котором идет речь, таков:

(define multirember&co
  (lambda (a lat col)
    (cond
     ((null? lat)
      (col (quote ()) (quote ())))
     ((eq? (car lat) a)
      (multirember&co a
                      (cdr lat)
                      (lambda (newlat seen)
                        (col newlat
                             (cons (car lat) seen)))))
     (else
      (multirember&co a
                      (cdr lat)
                      (lambda (newlat seen)
                        (col (cons (car lat) newlat)
                             seen))))))

Я смотрел на это весь день, но, похоже, не могу этого понять.Когда вы повторяете функцию, вы переопределяете col, но в примерах они, похоже, используют исходное определение.Почему это не изменится.Как вы можете повторить это, не передавая параметры newlat и seen.

Трудно объяснить мой вопрос, потому что я, кажется, просто пропускаю кусок.Если, возможно, кто-то сможет дать более подробное описание, чем книга, я смогу понять, как это работает.

Ответы [ 7 ]

21 голосов
/ 10 августа 2011

Давайте рассмотрим пример; возможно это поможет :-) Для простоты я просто собираюсь использовать list в качестве сборщика / продолжения, которое просто вернет список с аргументами для продолжения.

(multirember&co 'foo '(foo bar) list)

В начале

a = 'foo
lat = '(foo bar)
col = list

На первой итерации условие (eq? (car lat) a) соответствует, поскольку lat не является пустым, а первый элемент lat равен 'foo. Это устанавливает следующую рекурсию к multirember&co таким образом:

a = 'foo
lat = '(bar)
col = (lambda (newlat seen)
        (list newlat (cons 'foo seen))

На следующей итерации else совпадает: поскольку lat не является пустым, а первый элемент lat равен 'bar (а не 'foo). Таким образом, для следующей рекурсии мы имеем:

a = 'foo
lat = '()
col = (lambda (newlat seen)
        ((lambda (newlat seen)
           (list newlat (cons 'foo seen)))
         (cons 'bar newlat)
         seen))

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

col = (lambda (newlat1 seen1)
        ((lambda (newlat2 seen2)
           (list newlat2 (cons 'foo seen2)))
         (cons 'bar newlat1)
         seen1))

Наконец, предложение (null? lat) соответствует, поскольку lat теперь пусто. Итак, мы называем

(col '() '())

, который расширяется до:

((lambda (newlat1 seen1)
   ((lambda (newlat2 seen2)
      (list newlat2 (cons 'foo seen2)))
    (cons 'bar newlat1)
    seen1))
 '() '())

которое (при замене newlat1 = '() и seen1 = '()) становится

((lambda (newlat2 seen2)
   (list newlat2 (cons 'foo seen2)))
 (cons 'bar '())
 '())

или (оценивая (cons 'bar '()))

((lambda (newlat2 seen2)
   (list newlat2 (cons 'foo seen2)))
 '(bar)
 '())

Теперь, подставив значения newlat2 = '(bar) и seen2 = '(), получим

(list '(bar) (cons 'foo '()))

или, другими словами,

(list '(bar) '(foo))

чтобы получить наш окончательный результат

'((bar) (foo))
7 голосов
/ 07 сентября 2011

Я нашел замечательный ответ здесь: http://www.michaelharrison.ws/weblog/?p=34

Я тоже переживал это.Ключ должен понять лексическую область видимости (для меня, а-ля Javascript) и внутренние функции, передаваемые multirember & co в ветвях eq, а не eq.Поймите это, и вы поймете всю процедуру.

3 голосов
/ 19 мая 2012

То, что ссылка выше (http://www.michaelharrison.ws/weblog/?p=34) хорошо объясняет), заключается в том, как это целое упражнение состоит в том, чтобы избежать императивного программирования (C, Java), чтобы объявить две переменные "holder" или "collector" (или списки, векторы) явно в памяти чтобы поймать ваши ответы при переборе списка.Используя продолжение схемы на языке FP, вы не «проталкиваете» результаты теста, проходя через (земляничный тунец и рыба-меч) в любые отдельно созданные «корзины»; объединение двух списков при отправке соответствующих сопутствующих функций - одна для eq? true, другая для eq? false - через рекурсоры ... наконец, заканчивается третьей функцией col, которая в первом примере TLS - " a-friend ", который спрашивает, является ли список, созданный для хранения всех совпадений, пустым (null?). TLS затем просит вас" запустить "multirember & co снова с новым" последним "col, который просто запрашивает список, содержащий все" not tuna " "атомов, сколько он содержит (" последний друг "). Таким образом, есть две" первоклассные "функции, являющиеся раньше он работал с задачей сбора, то есть строил два отдельных списка, а затем в конце раскручивания рекурсии исходный col («друг») задавал последний вопрос. Возможно, имя «multirember & co» - не самое лучшее имя, потому что оно действительно не перестраивает список минус атом, который будет удален; скорее, он создает два отдельных списка - которые никогда не отображаются - затем применяет последний col (a-friend или last-friend). , , который отображает либо #t или #f, либо длину списка "не тунца".

Вот некоторые результаты:

> (multirember&co 'tuna '(and tuna) a-friend)
#f
> (multirember&co 'tuna '(and not) a-friend)
#t

Вот столбец, который возвращает список несоответствий:

(define list-not  (lambda (x y) x))

и его использование:

> (multirember&co 'tuna '(and not) list-not)
(and not)
2 голосов
/ 21 сентября 2017

Я долго боролся, чтобы понять, что происходит внутри multirember&co.Проблема в том, что в тот момент, когда я подумал, что получил его - следующее задание / пример доказал, что у меня его нет.

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

Итак, я собрал две блок-схемы:Во-первых, просто показаны отношения между различными этапами рекурсии :Visual walkthrough showing relations


И еще один, отражающий фактические значения :Visual walkthrough with valuesТеперь, когда я чувствую, что снова теряю «нить аргумента», я просто обращаюсь к этим блок-схемам, и они возвращают меня в нужное русло.

Еще одна вещь, которую я понял после просмотра «целого изображения» с помощью блок-схемы, заключается в том, что функция a-friend просто проверяет, содержит ли seen какие-либо значения или нет (хотя и возвращает его в другом случае).наоборот #f, когда есть значения в seen и #t, когда seen пусто, что может сбивать с толку.

PS: я сделал аналогичные блок-схемы для evens-only*&co, который появится позже в книге.

1 голос
/ 21 января 2018

Давайте используем эквациональный псевдокод с некоторыми круглыми скобками, опущенными для ясности (поэтому мы пишем f x y для вызова (f x y), где это однозначно):

multirember&Co a lat col
    = col [] []                                , IF lat == [] 

    = multirember&Co a (cdr lat)
         ( newlat seen => 
             col newlat
                 (cons (car lat) seen) )       , IF (car lat) == a

    = multirember&Co a (cdr lat)
         ( newlat seen => 
             col (cons (car lat) newlat)
                 seen )                        , OTHERWISE

Разве это не самоочевидно, что это делает? :) Еще нет? :) Переписав снова с воображаемым псевдокодом для сопоставления с образцом (с защитой), мы получим

multirember&Co  =  g   where
    g a [b, ...lat] col | b == a  =  g a lat ( n s =>  col     n     [b, ...s] )
                        | else    =  g a lat ( n s =>  col [b, ...n]     s     )
    g a []          col           =                    col     []        []

Семантика сопоставления с образцом должна быть достаточно очевидной: [b, ...lat] соответствует [1,2,3], где b = 1 и lat = [2,3]. Таким образом, это всего лишь три уравнения:

  • Когда второй аргумент является пустым списком, функция «сборщик» col сразу получает два пустых списка в качестве двух аргументов;

  • Когда элемент второго аргумента (который является списком) head совпадает с первым аргументом, результат такой же, как и для рекурсии с хвостом списка с измененным коллектором, который - после получит два аргумента, n и s, - добавят к текущему элементу head (который является a) в список s, и эти два списка будут переданы в функцию сбора этого вызова col;

  • В противном случае элемент заголовка будет добавлен к списку n, после того как n и s будут получены построенным коллектором, и оба будут далее использованы в функции токосъемника.

Другими словами, мы имеем дело с двумя результатами, возвращающимися из рекурсивного вызова, с добавлением головы ко второму, если голова была a, или к первому, если это не так.

Таким образом, вызов

    (g 1 [ 2, 1, 3, 1, 4, 5 ] col)

- это то же самое, что (вызовет) вызов

    (col [ 2, ...[3, ...[4, ...[5, ...[]]]]]
         [    1, ...[1,            ...[]]  ])

т.е. * * тысяча пятьдесят-семь

    (col [ 2,     3,     4,     5          ]
         [    1,     1                     ])

Еще один способ взглянуть на это состоит в том, что следующее является другой, эквивалентной формулировкой:

multirember&Co a lat col  =  g a lat id id   where
    id      x  =  x              ; identity function  
    (f ∘ g) x  =  f (g x)        ; function composition
    g a [b, ...lat] c d 
               | b == a  =  g a lat  c     (d ∘ (x => cons b x))  ;    (d ∘ {cons b})
               | else    =  g a lat (c ∘ (x => cons b x))   d     ; (c ∘ {cons b})
    g a []          c d  =  col     (c [])                (d [])

и, следовательно,

multirember&Co 1 [ 2, 1, 3, 1, 4, 5 ] col 
=
col (((((id ∘ {cons 2}) ∘ {cons 3}) ∘ {cons 4}) ∘ {cons 5}) [])   ; { } is for
    ( ( (id ∘       {cons 1}) ∘ {cons 1}                  ) [])   ;  partial application
=
col     (id   (cons 2     (cons 3     (cons 4    (cons 5   [])))))
        (id         (cons 1     (cons 1                    []) ) )  

что самоочевидно одно и то же.

В еще одном псевдокоде (со списком) это показывает, что оно

multirember&Co a lat col  
   = col [ b for b in lat if (b /= a) ] 
         [ b for b in lat if (b == a) ] 
   = ( ((n,s) => col n s) ∘ {partition {/= a}} ) lat

за исключением того, что только один обход списка lat выполняется (в исходном коде), эффективно, создавая вложенную цепочку лямбда-функций, имитирующую исходную структуру списка; какая цепочка затем оценивается для создания двух результатов, передавая их самой верхней функции коллектора col.

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

1 голос
/ 23 августа 2014

Код не строит решение, как это обычно бывает, но он строит код, который вычисляет решение, точно так же, как если бы вы строили дерево, используя операции низкого уровня, такие как cons, +, - и т. д. вместо аккумуляторов или фильтров высокого уровня.

Вот почему трудно сказать, является ли процесс итеративным или рекурсивным, потому что, по определению итерационных процессов, они используют конечный объем памяти для локального состояния. Однако этот тип процесса использует много памяти, но он выделяется в среде, а не в локальных параметрах.

Во-первых, я продублирую код здесь, чтобы увидеть переписку без особой прокрутки:

(define multirember&co
  (lambda (a lat col)
    (cond
     ((null? lat)
      (col (quote ()) (quote ())))
     ((eq? (car lat) a)
      (multirember&co a
                      (cdr lat)
                      (lambda (newlat seen)
                        (col newlat
                             (cons (car lat) seen)))))
     (else
      (multirember&co a
                      (cdr lat)
                      (lambda (newlat seen)
                        (col (cons (car lat) newlat)
                             seen)))))))

Давайте попробуем разбить проблему, чтобы увидеть, что на самом деле происходит.

  • Дело 1:

(multirember&co 'a
                '()
                (lambda (x y) (list x y)))

is the same as    

(let ((col (lambda (x y) (list x y))))
  (col '() '()))

Это тривиальный случай, он никогда не зацикливается.

Теперь интересные случаи:

  • Дело 2:

(multirember&co 'a
                '(x)
                (lambda (x y) (list x y)))

is the same as    

(let ((col
       (let ((col (lambda (x y) (list x y)))
             (lat '(x))
             (a 'a))
         (lambda (newlat seen)
           (col (cons (car lat) newlat)
                seen)))))
  (col '() '()))

В этом случае процесс выдает этот код как результат и, наконец, оценивает его. Обратите внимание, что локально это все еще хвостовая рекурсия, но в глобальном масштабе это рекурсивный процесс, и он требует памяти не путем выделения какой-либо структуры данных, а из-за того, что оценщик выделяет только кадры среды. Каждый цикл углубляет среду, добавляя 1 новый кадр.

  • Дело 3

(multirember&co 'a
                '(a)
                (lambda (x y) (list x y)))

is the same as    

(let ((col
       (let ((col (lambda (x y) (list x y)))
             (lat '(a))
             (a 'a))
         (lambda (newlat seen)
           (col newlat
                (cons (car lat) seen))))))
  (col '() '()))

Это создает код, но в другой ветви, который накапливает результат в другой переменной.


Все остальные случаи являются комбинациями 1 из этих 3 случаев, и становится ясно, как действует каждый из 1, добавляя новый слой.

0 голосов
/ 14 июня 2014

Я надеюсь, что это пошаговое руководство поможет

. Как предположил Крис, я переименовал newlat / seen в n / s и добавил индекс.Книга дает ужасные названия функциям (друг-новый-новоявленный), поэтому я просто сохранил L (для лямбды) и определение.

multirember&co 'tuna '(strawberries tuna and swordfish) a-friend)
  multirember&co 'tuna '(tuna and swordfish) (L(n1 s1)(a-friend (cons 'strawberries n1) s1))
    multirember&co 'tuna '(and swordfish) (L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))
      multirember&co 'tuna '(swordfish) (L(n3 s3)((L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2)) (cons 'and n3) s3))
        multirember&co 'tuna '() (L(n4 s4)((L(n3 s3)((L(n2 s2)((L(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2)) (cons 'and n3) s3)) (cons 'swordfish n4) s4))

((lambda(n4 s4)((lambda(n3 s3)((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) (cons 'and n3) s3)) (cons 'swordfish n4) s4)) '() '())
               ((lambda(n3 s3)((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) (cons 'and n3) s3)) '(swordfish) '())
                              ((lambda(n2 s2)((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) n2 (cons 'tuna s2))) '(and swordfish) '())
                                             ((lambda(n1 s1)(a-friend (cons 'strawberries n1) s1)) '(and swordfish) '(tuna))
                                                            (a-friend '(strawberries and swordfish) '(tuna))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...