Простые генераторы
Этот код взят из статьи под названием "Ленивый против доходности". Речь идет о способе разъединения производителей и потребителей потоков данных. Я понимаю часть кода на Haskell, но O'Caml/F# ускользает от меня. Я не понимаю этот код по следующим причинам:
Какое поведение я могу ожидать от функции, которая принимает в качестве аргумента исключение и возвращает единицу?
Как потребитель проектирует в конкретное исключение? (что это значит?)
Что будет примером для потребителя?
module SimpleGenerators
type 'a gen = unit -> 'a
type producer = unit gen
type consumer = exn -> unit (* consumer will project into specific exception *)
type 'a transducer = 'a gen -> 'a gen
let yield_handler : (exn -> unit) ref =
ref (fun _ -> failwith "yield handler is not set")
let iterate (gen : producer) (consumer : consumer) : unit =
let oldh = !yield_handler in
let rec newh x =
try
yield_handler := oldh
consumer x
yield_handler := newh
with e -> yield_handler := newh; raise e
in
try
yield_handler := newh
let r = gen () in
yield_handler := oldh
r
with e -> yield_handler := oldh; raise e
2 ответа
Я не знаком с бумагой, поэтому другие, вероятно, будут более поучительными. Вот несколько быстрых ответов / предположений.
Функция типа exn -> unit
в основном обработчик исключений.
Исключения могут содержать данные. Таким образом, они очень похожи на полиморфные варианты - то есть вы можете добавить новое исключение, когда захотите, и оно может выступать в качестве конструктора данных.
Похоже, что потребитель будет искать конкретное исключение (я), которые предоставляют ему данные, которые он хочет. Другие это просто поднимет. Таким образом, он только смотрит на проекцию пространства возможных исключений (я полагаю).
Я думаю, что образец OCaml использует несколько конструкций и шаблонов проектирования, которые вы обычно не используете в F#, поэтому он довольно специфичен для OCaml. Как говорит Джеффри, программы OCaml часто используют исключения для потока управления (в то время как в F# они используются только для исключительных ситуаций).
Кроме того, в F# есть действительно мощный механизм выражений последовательностей, который можно довольно удобно использовать для отделения производителей данных от потребителей данных. Я не читал статью подробно, поэтому, возможно, у них есть что-то более сложное, но простой пример на F# может выглядеть так:
// Generator: Produces infinite sequence of numbers from 'start'
// and prints the numbers as they are being generated (to show I/O behaviour)
let rec numbers start = seq {
printfn "generating: %d" start
yield start
yield! numbers (start + 1) }
Простой потребитель может быть реализован с помощью for
цикл, но если мы хотим использовать поток, нам нужно сказать, сколько элементов потреблять, используя Seq.take
:
// Consumer: takes a sequence of numbers generated by the
// producer and consumes first 100 elements
let consumer nums =
for n in nums |> Seq.take 100 do
printfn "consuming: %d" n
Когда ты бежишь consumer (numbers 0)
код начинает печатать:
generating: 0
consuming: 0
generating: 1
consuming: 1
generating: 2
consuming: 2
Таким образом, вы можете видеть, что эффекты производителей и потребителей чередуются. Я думаю, что это довольно простой и мощный механизм, но, возможно, я упускаю смысл статьи, и у них есть что-то еще более интересное. Если это так, пожалуйста, дайте мне знать! Хотя я думаю, что идиоматическое решение F#, вероятно, будет выглядеть очень похоже на вышеизложенное.