Концепция later

(later 'var . prg) -> var
Выполняет prg
в конвейерном дочернем процессе. Возвращаемое prg
значение позже будет доступно в var
. Обратите внимание, что later использует pr и rd для передачи результата, поэтому prg не должен записывать какие-либо данные в стандартный вывод в качестве побочного эффекта.
: (prog1 # Parallel background calculation of square numbers
(mapcan '((N) (later (cons) (* N N))) (1 2 3 4))
(wait NIL (full @)) )
-> (1 4 9 16)
Этот вызов 'prog1' выполняет два выражения, возвращая результат первого, но после выполнения второго. Первое выражение вызывает 'mapcan' в списке (1 2 3 4), применяя функцию
((N) (later (cons) (* N N)))
к каждому из чисел. Эта функция в свою очередь возвращает результат 'later'.
later принимает var
(т. е. символ или ячейку списка) и возвращает эту var
. В качестве побочного эффекта он берет программу (prg
, которая является списком выражений), запускает ее в дочернем процессе и сохраняет результат этой prg
в var
в какой-то более поздний момент, когда дочерний процесс завершится.
Для простого примера мы можем выполнить в REPL
: (later 'A (+ 1 2 3 4 5))
-> A
Мы видим, что 'later' возвращает переменную 'A'. Но если мы посмотрим на момент после этого на значение 'A'
: А
-> 15
мы видим, что он получил результат вычисления. Однако один вызов 'later' не очень полезен, поскольку вместо этого мы могли бы напрямую выполнить выражение.
Но если одно и то же выражение должно быть выполнено параллельно (типичный случай использования — запуск параллельного запроса к базе данных на удаленных машинах), то мы передаем ячейки списка вместо одной переменной в 'later'.
Это делается с помощью 'mapcan' и 'cons' выше. Помните, что (cons) — это всего лишь сокращение от (cons NIL NIL) , и что 'mapcan' объединяет возвращаемые результаты. Таким образом, приведенный выше вызов без 'later' дает:
: (mapcan '((N) (cons NIL NIL)) (1 2 3 4))
-> (NIL NIL NIL NIL)
Однако, если у нас есть
(mapcan '((N) (later (cons) (* N N))) (1 2 3 4))
Затем в качестве побочного эффекта каждый вызов 'later' получает свою частную, недавно 'cons'ed' ячейку списка - т.е. 'var' - которую он должным образом возвращает в 'mapcan' для объединения в полный список. В то же время каждый 'later' запоминает свою частную ячейку и сохраняет там свой результат из дочернего процесса. Затем 'wait' ждет, пока ВСЕ ячейки не будут заполнены (т.е. не станут NIL).
Как узнать, какая из предыдущих функций обновила @ , а какая нет?
Исключительно функции (flow), перечисленные там, будут изменять @ . Есть также хорошая статья Торстена: The many uses of @ in PicoLisp. Если быть точным: значение @ является результатом 'mapcan', т. е. списком новых 'cons'ed ячеек:
(prog1
(mapcan '((N) (later (cons) (* N N))) (1 2 3 4))
(wait NIL (full @)) )
Однако не 'mapcan' изменяет @ , а 'prog1'. 'prog1' является одной из вышеприведенных "Функций с управляющими выражениями".
Таким образом, в примере 'full' получает список (NIL NIL NIL NIL). Он просматривает его многократно, пока все 'NIL' не будут заменены числовыми результатами. Это работает, потому что 'later' работает с 'var', т. е. он запоминает ячейку и записывает в нее результат из дочернего канала.
Возможно, два более явных варианта вышеприведенного примера сделают все более понятным, не относительно @ , а взаимодействия переменных в 'later' и 'wait'. Следующие три выражения возвращают тот же результат (1 4 9 16) :
(prog1
(mapcan '((N) (later (cons) (* N N))) (1 2 3 4))
(wait NIL (full @)) )
(use (A B C D)
(later 'A (* 1 1)) # We hope to have a value later in 'A'
(later 'B (* 2 2)) # We hope to have a value later in 'B'
(later 'C (* 3 3)) # We hope to have a value later in 'C'
(later 'D (* 4 4)) # We hope to have a value later in 'D'
(wait NIL (and A B C D))
(list A B C D) )
(let L (list NIL NIL NIL NIL)
(later L (* 1 1)) # We hope to have a value later in the first cell
(later (cdr L) (* 2 2)) # We hope to have a value later in the second cell
(later (cddr L) (* 3 3)) # We hope to have a value later in the third cell
(later (cdddr L) (* 4 4)) # We hope to have a value later in the fourth cell
(wait
NIL
(and # An explicit variant of (full L)
(car L)
(cadr L)
(caddr L)
(cadddr L) ) )
L )
https://picolisp.com/wiki/?later
P.S. Функция акцентировалась в общем списке управления потоком, но без её описания, поэтому авторская статья с её отдельным описанием просто переведена “как есть”.
Subscribe to my newsletter
Read articles from Sergey Shishkin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sergey Shishkin
Sergey Shishkin
Всегда чему-то учусь!