Концепция later

Sergey ShishkinSergey Shishkin
4 min read

(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. Функция акцентировалась в общем списке управления потоком, но без её описания, поэтому авторская статья с её отдельным описанием просто переведена “как есть”.

0
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

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