вторник, 23 марта 2010 г.

Обратные связи

Язык F# в наследство от OCaml получил интересную возможность задавать рекуррентные отношения декларативно, как есть. Например, в моих сообщениях постоянно используется одна и та же система дифф-уров. Теперь ее можно переписать на F# следующим образом:

  let rec a = integF (lazy (- ka*a)) 100.0
  and b = integF (lazy (ka*a - kb*b)) 0.0
  and c = integF (lazy (kb*b)) 0.0
  and ka = 1.0
  and kb = 1.0

Это – работающий пример, где функция integF возвращает интеграл (как некое вычисление в монаде моделирования) по заданной производной и начальному значению. Здесь мы видим, что (1) переменные можно задавать произвольно без указания зависимости, (2) обратные связи задаются через явную ленивость. Фактически есть еще третий очень важный пункт: (3) компилятор сам следит за разрешимостью системы (в отличие от того же Haskell). Мне особенно нравится этот пункт, поскольку он делает подобный метод пригодным для широкого практического применения. Например, у нас имеется eDSL, а неискушенные пользователи задают свои системы. Компилятор сам все проверит и укажет на ошибку в случае необходимости. Неоценимое свойство.

Правда, в случае одной более сложной системы компилятор F# (v1.9.9.9) почему-то неправильно вывел переменные. Соответствующий bug report был отослан. Я думаю, что это – временное явление.

Рекуррентные отношения можно задавать и более хитрым способом, используя генераторы массивов:

  let smoothNI (x: Lazy<Dynamics<float>>) (t: Lazy<Dynamics<float>>) n (i: Dynamics<float>) =
   let rec s = [|
    for k = 0 to n-1 do
     if k = 0 then
      yield integ (lazy ((x.Value - s.[k]) / (t.Value / (float n)))) i
     else
      yield integ (lazy ((s.[k-1] - s.[k]) / (t.Value / (float n)))) i |]
   in s.[n-1]

Здесь возвращается функция (значение в монаде моделирования), которая называется экспоненциальной порядка n сглаживающей x по времени t с начальным значением i.

Мне нравится.

3 комментария:

  1. > компилятор сам следит за разрешимостью системы (в отличие от того же Haskell).

    Это какое-то ОЧЕНЬ сомнительное утверждение. Нельзя ли развернуть?

    ОтветитьУдалить
  2. Я имею ввиду, что компилятор F# проверяет (как оказалось, не всегда) на отсутствие циклических связей при определении переменных. Если есть цикл, то будет ошибка времени компиляции. Насколько знаю, в Haskell такого нет. Другая сторона медали - обратные связи в F# нужно явно указывать через ключевое слово lazy.

    Например, такое не пройдет в F#:

    let rec a = b + 1
    and b = a

    Хотя более упрощенная версия все же откомпилируется, хотя и не должна (!!):

    let rec a = b
    and b = a

    Видимо, еще один баг. Сырая реализация. Но во всяком случае, все мои другие попытки задать циклы компилятор F# четко пресекал.

    Кстати, похоже, что в ОCaml такой фичи с рекурсивным определением переменных вовсе нет.

    ОтветитьУдалить
  3. На этой неделе сразу два письма отослал им. Получил ответ от Дона Сайма. Как я понял, последний случай из предыдущего комментария является полиморфным. Он обрабатывается иначе. Но если добавить аннотацию типа, то вылезет желаемая ошибка компиляции. Проверка на разрешимость будет работать. Более того, думаю, что в реальных условиях моей задачи она будет работать практически всегда, потому что в таких уравнениях не должны возникать полиморфные типы.

    // OK: it won't be compiled!
    let rec a: int = b
    and b: int = a

    Конечно, зависимости через лямбды не проверяются, но иначе и нельзя было бы добавить обратную связь для интегралов!

    Надо сказать, что я не знаю других языков программирования общего назначения, где была бы такая крутая штука с проверкой разрешимости задаваемых рекурсивно систем переменных! В специализированных такое, конечно, есть. Например, в моем MapSim.

    ОтветитьУдалить