Поддержка таких выражений – это просто непередаваемая по значимости вещь для императивных монад типа Async для асинхронных вычислений. Более того, не нужно поддерживать на уровне языка продолжения – для них можно использовать те же вычислительные выражения (но нужна поддержка TCO на уровне исполняющей среды). Продолжения не совсем императивны, но все же блоки try-with и try-finally обрабатывать в таком языке как F# надо. И здесь нет известной проблемы Common Lisp.
Кстати, о Лиспе. Считаю, что вычислительные выражения и макросы мешают друг другу. Эти выражения предполагают, что есть ограниченное множество конструкций языка, которые могут быть “офункционалены” (превращены в функции). Макросы это нарушают. Когда я добавлял поддержку вычислительных выражений в Немерле, то оставлял макросы как есть. Не знаю, можно ли придумать здесь что-то лучше? (интересно, а как макросы и продолжения сосуществуют в Схеме?)
Мне теперь стало очень не хватать вычислительных выражений в Скале и Окамле. Скаловский for-comprehension смотрится как-то слабо, особенно для императивных монад, когда вычисления нужно разбавить обычным кодом. Что касается Окамла, то для него есть одно расширение, но очень хотелось бы, чтобы решение было стандартизировано и “из-коробки”. Надеюсь, что когда-нибудь добавят.
а можно маленький пример использования? если есть возможность, то на лиспе. нет - на чём угодно.
ОтветитьУдалитьТолько F# :)
ОтветитьУдалитьВот, пример с асинхронными вычислениями. Периодически посылает сообщение SaveModels. Кусок с do! - это асинхронное подвычисление (асинхронное усыпление системного потока, т.е. без фактического простаивания).
<------
let saver = async {
if server.SavingPeriod > 0 then
while true do
do! Async.Sleep server.SavingPeriod
agent.Post SaveModels
}
do! Async.StartChild saver |> Async.Ignore
------>
А это имитационная [законченная] модель станка. Вычисление Hold удерживает текущий поток исполнения имитационного процесса в течение заданного времени.
<------
type Machine (e, id) =
inherit Process (e)
override x.Activate = dynamicscont {
while true do
let! startUpTime = DynamicsCont.lift time
let upTime = expovariate upRate
do! x.Hold (upTime)
let! finishUpTime = DynamicsCont.lift time
totalUpTime <- totalUpTime +
(finishUpTime - startUpTime)
let repairTime = expovariate repairRate
do! x.Hold (repairTime)
}
------>
В обоих случаях код async {...} и dynamicscont {...} со всеми while, let! и do! преобразуется компилятором в композиции функций. let! - это монадический bind. do! - специализированная версия let! для типа unit.
Еще до кучи. Считывается граф из XML с помощью стандартного парсера XmlReader. Как и раньше, let! - это монадический bind. Опять же, все вычислительное выражение xmlreader {...} преобразуется в композицию функций.
ОтветитьУдалить<------
static let read =
XmlReader.elem "graph" <| xmlreader {
let! id = XmlReader.attr "guid"
let! (nodes, rels) = readContents
let graph = Graph (id)
graph.Nodes.AddGraph (nodes, rels)
return graph
}
------>
> Скаловский for-comprehension смотрится как-то слабо, особенно для императивных монад, когда вычисления нужно разбавить обычным кодом.
ОтветитьУдалитьДа. Нужно бы подумать, можно ли написать соответствующий плагин для компилятора.
Я правильно понял что это вариация на тему хаскельной do-нотации?
ОтветитьУдалитьДа, очень близко к нотации do. В одном ряду с for-comprehension из Скалы и Linq из C#.
ОтветитьУдалить@alexey-rom
ОтветитьУдалитьЯ и надеюсь на такой плагин :) Уж очень удобны эти выражения, например, как генераторы ленивой последовательности (моноид seq).