Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Archive for the ‘f#’ Category

Что нового в F# 3.1

leave a comment »

Недавно в пререлиз VS2013 была залита версия F# 3.1 в которой, как ни странно, есть несколько полезняшек которые немного упрощают жизнь. В частности:

  • Наконец-то кейсы дискриминированных объединений можно именовать. То есть если мы раньше писали
    type Stuff =
      | Pair of float * float
    

    то теперь можно дать имена аргументам, т.е.:

    type Stuff =
      | Pair of first : float * second : float
    

    Не могу сказать что это выглядит красиво, но это мне реально нужно для MathSharp, где разные конструкции MathML как раз и представлены таким большим объединением, и хочется помнить какой тип к чему относится. Да, и конечно же теперь есть паттерн-матчинг на все это, т.е. можно писать

    match myPair with
    | Pair(fisrt = 0.0f) -> ⋮
    
  • F# явно пытается стать чем-то вроде MATLAB-а в плане работы с матрицами – в 3.0 они сделали так чтобы матрицы можно выпиливать кусочки например так foo.[2..5,*] что в MATLAB выглядело бы как foo(2:5,:), а в 3.1 можно еще и брать отдельные элементы, например
  • foo.[2,*] // 2й ряд
    bar.[*,5] // 5я колонка
    
  • Вывод типов для LINQ. Давно пора. Пока вы в уютном сишарпике писали foo.Where(x => x.Bar), в F# творилось плохое приходилось навешшивать аннотации типов на параметр. Кажись пофиксили, теперь можно просто писать foo.Where(fun x -> x.Bar). Э-эх, если бы они еще fun убрали для лаконичности, хотя это наверное не реально.
  • Раньше, если вы импортировали из C# методы расширения с дженериками (например монаду Maybe — хотя зачем?) то F# их не видел. Теперь в 3.1 все должно работать и, более того, можно самому делать подобные расширения. Правда F# как всегда отличился и добавил анти-ООПшного изврата в сей процесс путем не давания возможности ограничить generic-параметр (в стиле where foo : Bar, new(). Обещают пофиксить в будущем, а пока рекомендуют пользоваться [<Extension>].
  • В констрантных выражениях теперь можно писать вещи вроде
    [<Literal>]
    let foo = "bar" + "baz"
    

Это что касается языка. Помимо этого, есть какие-то попытки улучшить поддержку VS, но если честно — улучшение тултипов и выравнивания кода не даст мне возможность лучше идентифицировать ошибки или делать рефакторинги. Более того, все мои дискуссии с F# community сводятся к тому что все с пеной у рта бегут доказывать что «ReSharper не нужен»,ну и следовательно продолжать диалог сложно.

Что ж, скажем спасибо что язык хотя бы не стоит на месте. Хотя хотелось бы конечно большего. Вот ссылка на пост где помимо языковой части еще затронут инструментарий и либы – может будет интересно. ■

Реклама

Written by Dmitri

27 сентября 2013 at 11:33

Опубликовано в f#

Tagged with ,

Парсим HTML Zen на F#, FsLex и FsYacc

5 комментариев

Все наверное так или иначе слышали про HTML Zen и про то, как он спасает при написании HTML. Но мало кому приходит в голову написать свою собственную реализацию, когда есть готовая на Python’е. На самом деле, собственная реализация позволяет добавлять собственные конструкты, нужные только вам.

Давайте попробуем реализовать базовые конструкты HTML Zen. Для этого мы воспользуемся языком F#, библиотекой тестирования FSunit. Будем писать программу в стиле TDD. Я пожалуй назову ее FSharpZen, так интересней.

Исходный код можно найти тут: https://bitbucket.org/nesteruk/fsharpzen

Базовые случаи

Весь HTML Zen – это набор трансформаций. Например, на входе a, а на выходе <a href="|"></a>, где | обозначает финальное положение каретки. Следовательно, можно как минимум написать следующий тест:

[<TestFixture>]
type ``Basic transformation tests`` ()=
  [<Test>] member test.
    ``Empty input should yield empty output`` () =
      Zen.html "" |> should equal "|"

Это счастье совсем не компилируется, поэтому нам придется как минимум предоставить хоть какую-то реализацию, например:

module FSharpZen.Zen
let html input = ""

В результате, конечно, получим “красный” тест:

Expected string length 1 but was 0. Strings differ at index 0.
Expected: "|"
But was:  <string.Empty>
-----------^

Ну а дальше мы “жульничаем” и наш тест проходит. Не забываем сказать спасибо Решарперу, который поддерживает F#-тесты out-of-the-box.

Пустые тэги

Дефолтное поведение HTML Zen такое, что при написании, скажем, буквы z разворачивается шаблон <z>|</z>. С другой стороны, hr определенно превращается в <hr/>|, а img – в <img src="|"></img>.

[<Test>] member test.
  ``Simple entity should be in a closed tag`` () =
    Zen.html "c" |> should equal "<c>|</c>"
[<Test>] member test.
  ``Self-closing entity should yield a self-closing tag`` () =
    Zen.html "br" |> should equal "<br/>|"
[<Test>] member test.
  ``Entity should have appropriate attributes generated and placeholder set accordingly`` () =
    Zen.html "a" |> should equal "<a href=\"|\" title=\"\"></a>"

Теперь у нас есть 3 use-case’а: просто тэги, тэги которые не нуждаются в закрывающем тэге, и тэги которые имеют сложную начинку. Второй и третий случай – специальные, поэтому мы можем попробовать их выделить…

let singularEntities = ["hr"; "br"; "img"]
let neededAttributes = [
                         "a", ["href", "title"];
                         "img", ["src", "alt"]
                       ]

TDD по наивности своей рекоммендует “самое простое работающее решение” которое в нашем случае выглядит примерно вот так:

let html input =
  if input |> String.IsNullOrEmpty then "|"
  else
    let sb = StringBuilder()
    sb.Append ("<" + input) |> ignore
    for a in neededAttributes do
      match a with
      | i, attributes when i = input ->
        let firstAtt = List.head attributes
        sb.Append(" " + firstAtt + "=\"|\"") |> ignore
        attributes |> List.tail 
                   |> List.iter (fun f -> sb.Append(" " + f + "=\"\"") |> ignore)
      | _ -> ()
    // see if this is a self-closing tag
    let isSelfClosing = List.exists (fun f -> f = input) singularEntities
    let pipe = if sb.ToString().Contains("|") then "" else "|"
    sb.Append(if isSelfClosing then "/>" + pipe else ">" + pipe + "</" + input + ">") |> ignore
    sb.ToString()

До элегантности тут как до Китая. Вывода напрашивается два:

  • Нужны какие-то структуры для того чтобы отражать структуру тэгов, особенно когда будут вложенные, с аттрибутами, и т.д.
  • Нужно учиться лучше парсить строки. То, что мы делаем выше – “не по паттернам”.

FsLex и FsYacc

Лучший способ парсить – это взять что-то уже готовое (хотя до меня это иногда туго доходит). Поэтому для парсинга наших магических строк мы возьмем F# Power Pack, и скачаем через VS Extension Manager шаблон проекта под названием F# Parser Language Starter. Это позволит нам получить пример, в котором есть три важных файла, а именно:

  • Ast.fs — определение синтактического дерева. В нашем случае оно достаточно простое.
  • Lexer.fsl — определение “лексем”, т.е. различных кусков строки, которые формируют выражение HTML Zen.
  • Parser.fsy — собственно парсер, который определяет то, как лексемы становятся чем-то осмысленным.

В сгенерированном примере получается калькулятор1, нам же остается только поменять его чтобы начали поддерживаться наши собственные структуры.

AST

Итак, для нашего магического синтаксиса нам нужно определить тот набор элементов дерева, который мы фактически готовы подерживать. Это включает в себя как минимум следующие конструкции2:

  • Идентификаторы, такие как div, href и так далее. Они парсятся “как есть”.
  • Классы, т.е. при при написании a.b мы получаем <a href="|" class="b"></a>.
  • Идентификаторы, т.е. a#b ставовится <a id="b" href="|"></a>.
  • Множители, т.е. hr*2 дает нам <hr/><hr/>. Между тэгами, между прочим, фигурирует \n – это вынужденная мера для таких ситуаций как ol>li*2.
  • Вложенные тэги – например, ol+li становится <ol><li>|</li></ol>, с соответствующими переносами и отступами.

Теперь попробуем перевисти все эти идеи на структуры F#. У меня получилось примерно следующее:

type ZenExpression =
  | ZenExpression of Expression list
and Expression =
  | Expression of Identifier * Qualifier list
and Identifier =
  | Identifier of string
and Qualifier =
  | ClassQualifier of string
  | IdentityQualifier of string
  | CardinalityQualifier of int

Строго говоря, то, что вы видите выше – уродство. С этим очень сложно работать. К тому же, нет никаких ограничений на количество идентификатов или показателей размерности (а ведь элемент может иметь только один квалификатор каждого типа). Ну да ладно, эту проблему мы разрулим позже.

Лексер

Лексер у нас будет простенький. Во-первых, нужно определить регулярные выражения для всех полезных конструктов:

let digit = ['0'-'9']
let letter = ['a'-'z'] | ['A'-'Z']
let letterOrDigit = letter | digit
let whitespace = [' ' '\t' ]
let newline = ('\n' | '\r' '\n')

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

  • Строки (возможно с числами) для идентификаторов и квалификаторов.
  • Операторы для разделения квалификаторов (., #, + и *)
  • Числа для квалификатора размерности

Следовательно, получаем следующее определение:

rule tokenize = parse
| whitespace	   { tokenize lexbuf }
| newline        { tokenize lexbuf }
| letterOrDigit+ { STRING(lexeme lexbuf) }
| digit+         { INT32(Int32.Parse(lexeme lexbuf)) }
// Operators
| "+"            { PLUS }
| "#"            { HASH }
| "*"            { TIMES }
| "."            { DOT }
// EOF
| eof   { EOF }

Парсер

Определив лексемы, нам нужно научиться парсить их и превращать их комбинации в элементы AST. Для этого пойдет следующее определение:

start: exprs { ZenExpression($1) }
exprs:
  | exp PLUS exprs EOF { Expression($1) :: $3 }
  | exp EOF { [Expression($1)] }
exp:
  | STRING quals { (Identifier($1), $2) }
  | STRING { (Identifier($1), []) }
quals:
  | qual quals { $1 :: $2 }
  | qual { [$1] }
qual:
  | DOT STRING  { ClassQualifier($2) }
  | HASH STRING { IdentityQualifier($2) }
  | TIMES INT32 { CardinalityQualifier($2) }

Помимо того, что списки чего бы то ни было создаются рекурсивно, тут нет ничего необычного.

Принтер

Ну а теперь самое сложное. Помните я сказал, что та структура в которую мы парсим – убогая и избыточная? Так вот, сейчас вы увидите насколько неудобно такую штуку печатать.

Во-первых, код забрасывается кучей вспомогательных функций для поиска квалификаторов определенного типа. Например:

let rec private getId qualifierList =
  match qualifierList with
  | IdentityQualifier(id) :: t -> Some(id)
  | h :: t -> getId t
  | [] -> None

Потом, приходится (не знаю в какой уже раз) делать вменяемый билдер для кода:

type CodeBuilder() =
  let sb = StringBuilder()
  let mutable indent = 0;
  member this.GetIndent() =
    System.String.Empty.PadRight(indent * 2)
  member this.AppendWithIndent (text:string) = 
    sb.Append(this.GetIndent()).Append(text) |> ignore
  member this.Append (text:string) = 
    sb.Append(text) |> ignore
  member this.NewLine() = sb.AppendLine() |> ignore
  member this.Indent() = indent <- indent + 1
  member this.Unindent() = indent <- indent - 1
  override this.ToString() = sb.ToString()

Потом нужен поистине гигантский кусок кода для того чтобы распарсить все кейсы – конструкция весьма громоздкая:

let rec private print expressions (builder:CodeBuilder) =
  match expressions with
  | h :: t ->
    builder.AppendWithIndent "<"
    match h with
    | Expression(name, qual) ->
      builder.Append name
      // id it if has any
      match getId qual with
      | Some(id) -> builder.Append(" id=\"" + id + "\"")
      | _ -> ()
      // needed attributes
      let needed = getNeededAttributes name
      needed |> List.iter(fun n -> builder.Append(" " + n + "=\"|\""))
      // classes
      let classes = getClasses qual
      if classes <> List.empty then
        builder.Append(" class=\"")
        builder.Append(System.String.Join(" ", classes))
        builder.Append("\"")
      // if self-closing, then close it
      let selfClosing = singularEntities |> List.exists(fun f -> f = name)
      if selfClosing then builder.Append "/>|"
      else builder.Append ">"
      // if there's more stuff in there, add it
      if t <> List.empty then
        builder.Indent()
        builder.NewLine()
        print t builder
        builder.NewLine()
        builder.Unindent()
        
      if not selfClosing then
        builder.Append("|</" + name + ">")
  | _ -> 
    builder.Append "|"

А вот собственно как все запускается:

let html input = 
  let lexbuf = LexBuffer<char>.FromString(input)
  let parsed = Parser.start Lexer.tokenize lexbuf
  let builder = new CodeBuilder()
  match parsed with
  | ZenExpression(items) -> print items builder
  let pre = builder.ToString() |> Seq.toList
  let rec clean input metBar =
    match input with
    | '|' :: t when not metBar -> '|' :: clean t true
    | '|' :: t when metBar -> clean t true
    | h :: t -> h :: clean t metBar
    | [] -> []
  let lst = clean pre false
  System.String(lst |> List.toArray)

Излишние телодвижения у нас от того, что мы в принтере генерируем избыточное число кареток. Нужно удалить все кроме первой, а более элегантного способа я не знаю (хотя не исключаю что он существует).

Заключение

В очередной раз у меня сомнения. Количество телодвижений для того чтобы реализовать парсер примерно в 10 раз больше чем когда я все это писал “в лоб” на C#. FxLex и FsYacc обладают практически нулевой диагностичностью, никакого IntelliSense нет, такое впечатление что блуждаешь в потемках. Что касается принтера, опять же, структура на которую все было “замэплено” очень неудобна; намного быстрее было бы использовать ООП. Плюшки FP вроде паттерн-матчинга и прочего тут совсем не амортизировались.

Заметки

  1. Кстати, пример кривой – попробуйте например ввести 2-2 и у вас парсер моментально накроется – а все из-за попытки взять минус перед 2кой как префикс-оператор. :)
  2. Тут я немного отклонился от “кошерного” HTML Zen который, на мой взгляд, достаточно избыточен. В частности, я заменил > на + а “оригинальный” + попросту проигнорировал.

Written by Dmitri

3 марта 2011 at 15:22

Опубликовано в .NET, f#

Tagged with ,

Уравнения на F# проще чем на C#?

22 комментария

На недавней встрече, посвещенной языку F#, я показал как якобы «элегантно» можно описывать математические функции на F#. А сейчас сел и задумался – так ли это на самом деле? Давайте разберемся.

Квадратное уравнение

Начнем с примера, приведенного мною на встрече – решения квадратного уравнения. Казалось бы – что может быть проще?

Сразу можно сделать несколько сравнений C# и F# для этого примера:

  • Возвращаются 2 значения, а следовательно F# выигрывает за счет того что не надо писать Tuple.Create, а тому кто эти значения получит не надо использовать абсолютно ничего не значащие свойства Item1 и Item2
  • Математические binding’и у F# намного «ближе» чем у C#, а следовательно в этом случае (да и в общем), ваш код на F# не будет испачкан явным вызовом статических функций вроде Math.Sqrt. Меня всегда это бесило в C# т.к. сложные вычисления становится еще сложнее читать.
  • Ни C# ни F# не умеют работать с оператором ±. Но если в C# нам придется тупо вызывать вычисления дважды, в F# мы можем просто определить функцию конечного вычисления, а потом передать в нее операторы (+) и (-).

В результате получим следующее сравнение:

C#

public Tuple<double,double> SolveQuadraticEquation(double a, double b, double c)
{
  double discRoot = Math.Sqrt(b*b-4.0*a*c);
  double x1 = (-b + discRoot) / (2 * a);
  double x2 = (-b - discRoot) / (2 * a);
  return Tuple.Create(x1, x2);
}

F#

let solveQuadratic a b c =
  let disc = b * b - 4.0 * a *c
  let calc op = (op (-b) (sqrt disc)) / (2.0*a)
  (calc (+), calc(-))

Пока что разница небольшая – в одну строчку, если не считать фигурных скобок. А спорим я могу сократить эту разницу практически до нуля? Вот, пожалуйста:

C#

public Tuple<double, double> SolveQuadraticEquation(double a, double b, double c)
{
  double q = -0.5 * (b + Math.Sign(b) * Math.Sqrt(b * b - 4 * a * c));
  return Tuple.Create(q / a, c / q);
}

F#

let solveQuadraticEquation a b c =
  let q = -0.5 * (b + (sign b |> float) * sqrt(b * b - 4.0 * a * c))
  (q / a, c / q) //           ^^^^^^^^ WTF?!?

Пример на F# должен вызвать у вас возмущение – с какой это радости результат (sign b) должен быть приведен к типу float?1 А дело все в непродуманности API, согласно которому sign(x) – это всегда int (могли бы сделать тем же типом что и аргумент), а также крайне жесткой системе типов в которой float + int * float не воспринимается и никак не вычисляется.

Такое странное поведение F# унаследовано из OCaml и требуется для правильного вывода типов. Если вам действительно интересны причины – можно почитать тут. Мне же это лишь палки в колеса. Да, и я знаю что можно было бы написать sign(float b) или что-то в этом роде. Тут как бы без разницы, все равно «не очень».

Комплексные решения

Ну да ладно, а вот вопрос – что будет если дискриминанта (Δ) меньше нуля? С одной стороны, и C# и F# поддерживает комплексные типы данных (см. System.Numerics). Если сделать вид что мы все будем передавать как комплексные числа (так проще?), то получим вот такой вот код:

C#

public Tuple<Complex, Complex> SolveQuadraticEquation3(double a, double b, double c)
{
  double det = b * b - 4 * a * c;
  double absRoot = Math.Sqrt(Math.Abs(det));
  Complex root = det < 0 ? new Complex(0, absRoot) : new Complex(absRoot, 0);
  Complex q = -0.5 * (b + Math.Sign(b) * root);
  return Tuple.Create(q / a, c / q);
}

F#

let solveQuadratic3 a b c =
  let complex v = Complex(v, 0.0)
  let det = b * b - 4.0 * a * c
  let absRoot = sqrt(det |> abs)
  let root = if det < 0.0 then Complex(0.0, absRoot)
                          else Complex(absRoot, 0.0)
  let q = -(complex 0.5) * ((complex b) + (sign b |> float |> complex) * root)
  (q / complex(a), complex(c) / q)

Эээ, ну что я могу сказать? F# определенно превносит массу головной боли. Проблема тут как раз в том, что в C# нам доступны операторы автоконверсии вроде floatComplex, но в F# они попросту не работают! Поэтому вы можете либо определить их как функции (благо у них есть имя op_Implicit), либо же просто вызвать конструктор как делаю я.2

Заключение

То ли я чего-то недопонимаю, то ли F# действительно неудобен для работы с математикой? Ведь если нужно каждый int кастовать во float, а каждый float в Complex, и так далее, то это же сколько лишнего, никому не нужного и мешающего восприятию кода надо написать?

Критика welcome! А то может я что не так написал?

Заметки

  1. Напоминаю, что float в F# – это double в C#. А C#-ный float в F# называется float32.
  2. Тут еще хочется заметить что if который выбирает какой Complex создавать как бы не нужно — можно было вместо этого написать Complex.Sqrt(new Complex(b*b-4*a*c, 0.0)). Проблема только в том что в результате этого вычисления Real-значение будет чуточку отличаться от нуля. Например sqrt(Complex(-4.0, 0.0)) равен (1.22460635382238E-16, 2).

Written by Dmitri

5 февраля 2011 at 10:52

Опубликовано в .NET, C#, f#

Коротко про WebSharper и сайтлеты

leave a comment »

Этот пост – про WebSharper в целом и про сайтлеты в частности. Как я уже писал, WebSharper – это идея создания сайтов полностью на языке F#. У этой идеи есть несколько модельных реализаций, а точнее три – через Asp.Net, Asp.Net MVC и так называемые сайтлеты “sitelets”.

Концепция

Меня во всей этой концепции интересуют именно сайтлеты. В отличии от использования Asp.Net-конструктов (пусть и на F#), сайтлеты – наиболее “трушная” концепция, в которой сгенерированный код будет полностью отвязан от Asp.Net и, впоследствии, от IIS.

Теперь о том как это делается. На самом деле, основной концепцией является конверсия F# в JavaScript/CSS/HTML на этапе компиляции. Делается это за счет аттрибута под названием ReflectedDefinitionAttribute который, имея alias под названием JavaScriptAttribute, используется перед методами, которые нужно сконвертировать в JavaScript.

Что этот аттрибут делает? Он заставляет компилятор F# сохранить логические конструкты метода вместо того чтобы просто его компилировать. А тут включается постпроцессинг WebSharper’а, который берет это “отраженное определение” и, на его основе, формирует некий промежуточный слой JavaScript. Промежуточный слой нужен потому, что мэпить F# на JavaScript не так-то просто.

Простой код который вы напишете вот так

[<JavaScript>]
let rec Fact n =
  match n with
  | n when n < 2 -> 1
  | n -> n * Fact1 (n - 1)

может быть в последствии вызван со страницы напрямую – ведь все остальное, что есть на странице тоже определено через F#, конвертированный в JS и HTML.

Вызовы серверных методов

Мне очень понравилось что механизм вызова методов очень прозрачный. Метод помеченный как [<JavaScript>] вызывается как JS прямо у клиента, в то время как метод помеченный как [<Rpc>] хостится на сервере (как это будет сделано в полноценных сайтлетах – непонятно), и соответственно его вызов со страницы дергает сервер.

На данный момент механизм поведения Rpc-методов такой: если метод возвращает какое-то значение, то вызов является блокирующим. Если метод возвращает unit – его вызов считается асинхронным и тем самым, если вы повесили на него брейкпоинт (да, это можно делать), вы можете остановиться в нем не тогда, когда ожидаете.

Формирование контента

ВебШарпер не отказывается от идеи шаблонов! По крайней мере сайтлеты на текущем этапе предоставляют возможность писать шаблоны в HTML, который потом компилируется… в F#! Что намного более извращенно чем то, что делает Asp.Net MVC, например. Но мы сейчас не об этом.

Дело в том, что сам по себе WebSharper предоставляет возможность формировать HTML путем использования HTML-подобных конструктов в F#. Механизм, с помощью которого организовано создание HTML весьма забавно. В модуле IntelliFactory.Html.Tags есть определения для большого количества тэгов, например H1. Каждый тэг – это функция, которая сама по себе берет список, состоящий из нескольких “нодов”, которые могут формировать как аттрибуты данного тэга, так и его начинку. Например, чтобы сделать заголовок с текстом, мы пишем следующее:

H1 [Text "Title"]

В примере выше, метод Text определяет, что его аргумент – это действительно текст. Вместо него, мы бы могли например использовать Attr чтобы указать что это аттрибут.

Теперь насчет композиции. Композиция – это центральная особенность ВебШарпера, которая очень хорошо выделяет его по сравнению с тем же Asp.Net MVC. В данном случае, для вкладывания HTML-элементов друг в друга, мы можем использовать оператор -<. Например:

Div [Attr.Style "padding: 5px;"] -< [
  P [Text "Hello"]
]

События

Обработка событий в F# делается путем еще одного полезного оператора |>!. Этот оператор делает подписку на событие, и одновременно возвращает созданный элемент, что обязательно в условиях механики работы WebSharper. Вот небольшой пример:

let update () =
  // обработка события
 
let input = Input [Attr.Type "Text"]
Form [
  label "First Name: "
  input |>! OnKeyUp (fun _ _ -> update ())
]

Опять же, интересно тут то, что не важно, где располагается метод update() – на сервере или на клиенте.

Это вам не MVC!

Сейчас устраивать гонения на MVC модно, многие уходят на Rails (в т.ч. всем известный Рой Ошеров), поэтому я решил тоже подлить масла в огонь хотя, по-хорошему, ВебШарпер является частью Asp.Net инфраструктуры. Но это пока сайтлеты полностью не дописаны. Так вот, понятное дело, что в функциональной парадигме, создание сайтов выглядит несколько по-другому.

Для начала, давайте познакомимся с концепцией “формлетов”. Формлет – это возможность описания того, какие данные нужно получать в форме и как пройдет валидация. Например:

[<JavaScript>]
let BasicInfoForm () : Formlet<BasicInfo> =
  Formlet.Yield (fun name age -> { Name = name; Age = age })
  <*> Input "Name" "Please enter your name"
  <*> InputInt "Age" "Please enter a valid age"

Формлеты можно “обрамлять” (типичный паттерн Декоратор) в различные другие конструкты, функционально добавляя им кнопки Submit/Reset, настройки, и так далее. Формлеты можно инстанциировать у себя в коде, причем с ними можно делать вещи, которые в том же MVC делать очень сложно. Например, допустим у вас сайт где можно разместить резюме, и вы хотите чтобы человек мог внести данные о всех местах где он работал. Для этого, вы делаете формлет, который инкапсулирует все данные о работе (где, когда, итп.) а потом одной строчкой кода говорите “их может быть несколько”. И все! Композиционная структура сделает все сама за себя.

Так вот, о чем мы? После того как мы сделали несколько формлетов, их можно выстроить в цепочку, так что нажав Submit на одном, мы перейдем к другому. Эта “концепция” именуется flowlet’ом (или flowlet’ами). Выражается это следующим образом.

Во-первых, есть некий workflow под названием Flowlet.Do этот вокрфлоу умеет показывать формы в тот момент, когда они учавствуют по правую сторону let!, и сохранять их возвращенные значения (да-да, форма это функция, а ее return value – это то, что пользователь ввел). После этого, путем return! происходит “типа возврат” из этой цепочки, то есть мы можем, например, предоставить фунцию, которая получит результаты formlet’ов и выведет из них полезные значения на экран.

На самом деле, механика всего этого чуть-чуть сложнее. Во-первых, конечным результатом выражения Formlet.Do все равно должен быть Formlet, то есть создав элемент, вам придется его конвертировать. Но это не сложно! А во-вторых, весь этот computational expression должен быть передан в функцию Formlet.Flowlet для того чтобы оркестровать такое феерическое действие.

Formlet.Do {
  let! w = welcomeForm
  let! c = consentForm
  return! Formlet.OfElement (proc w c)
}
|> Formlet.Flowlet

Представление

На данный момент презентация сего фееричного действия организована в терминах Asp.Net’ного WebControl, от которого наследует ВебШарперный контрол, давая нам возможность засунуть в него целиком наш pagelet. Pagelet – это еще одна абстракция в стиле “нечто что может фигурировать на странице”. Логика мне не ясна, но на данный момент pagelet’ом являются текст, Element (это просто элемено HTML) или Flowlet.

Понятное дело, что формирование отдельного WebControl-а не означает создание странички целиком. И вот тут начинаются странности.

Дело в том, что для сайтлетов у нас генерируются 3 проекта, а именно:

  • Проект Asp.Net, который содержит некоторые ресурсы (например CSS), но в будущем скорее всего не будет нужен вообще. По крайней мере, сейчас для сайтлетов он служит заглушкой со ссылкой на “настоящий сайт”.
  • Проект сожержащий сайтлет.
  • Проект с шаблонами.

Проект с шаблонами – это возможность делать, фактически, master-странички со вставками, определеными в F#. Вы уже догадались почему нужно конвертировать HTML в F#? Поясню: в основном проекте, у нас появляется возможность строготипизированно определить, что мы помещаем в различные секции шаблона. Обычно туда помещяется либо наш F#-созданный HTML, либо… наш WebControl, конечно же!

За этим всем стоит разве что само определение сайта. Там собраны все шаблоны, и еще есть некий plumbing для того чтобы все работало.

Размышления

Для меня, идеальным кажется сайт, который существует целиком на клиенте, а с сервером общается только через REST или более продвинутую парадигму a la NetKernel. Поэтому в случае с ВебШарпером (надеюсь мне авторы простят русификацию названия), то чего хочу видеть я – это полную отвязку от Asp.Net и IIS. Я точно не знаю когда это будет, но коллеги подсказывают мне, что релиз WebSharper 2.0 будет отвязан от Asp.Net, в то время как полноценная поддержка сайтлетов с отвязкой от IIS будет в последующем релизе. Главное, что для перехода на Linux (а это основная цель) не надо будет ничего переписывать. По крайней мере я на это надеюсь.

Теперь что касается юзабельности сего фреймворка. Понятное дело что вкуривать сложную DSL и писать на F# – занятие не для слабонервных, но лично меня технические трудности не очень беспокоят. Композициональность, если так можно выразиться, фреймворка впечатляет, ровно как и его возможность быстро оборачивать такие JS библиотеки как JQueryUI, YUI или даже RxJS! Это означает что в F# можно получить унифицированную, строготипизированную модель, которая будет сама подсказывать как использовать тот или иной фреймворк и упрощать работу с ним.

Анонсы

Коротко о главном. Во-первых, Петербургская Группа Alt.Net планирует провести 3х-часовую встречу посвещенную F#. Максим Моисеев и я будем рассказывать про все, начиная с основных понятий и заканчивая спекуляциями на тему type provider’ов. Встреча пройдет 3го Февраля в офисах Exigen, и в ближайшее время мы вывесим анонс на сайтах Ineta и Spbalt.net.

Во-вторых, на выходных периодически происходят неформальные встречи участников сообщества. Встречи происходят в моем офисе (территориально 3 мин. от м. Автово), так что если вы живете в Питере и у вас есть желание убить выходной копаясь в F# (или еще какой-нть интересной фигне) с единомышленниками, напишите мне и мы обсудим. Писать лучше по скайпу — dmitri.nesteruk.

На этом все. Comments welcome!

Written by Dmitri

19 января 2011 at 23:53

Опубликовано в .NET, f#

Tagged with ,

Технологии на моей тарелке

15 комментариев

За последние несколько месяцев, мне под руки попался ряд технологий которые я, без стороннего влияния, наверняка никогда бы и не узнал. Причина этому во-первых в том, что я слишком сильно “залип” в инфраструктуре .Net и языке C# чтобы отчетливо видеть что-то за ее пределами. Тем самым, “отлипание” для меня – процесс болезненный. А целью этого поста является кратко осветить те технологии, с которыми я познакомился – мало ли, может кому-то еще интересно будет.

F#

Писать на F# демо-программки и делать на нем что-то существенное – это совершенно разные вещи. Когда начинаешь массово плодить код на F#, начинаешь понимать всю упадочность своего понимания мира. Например, я достаточно долго писал member this.Something with get() = ... вместо аналогичного варианта member this.Something = .... Ну и тяга к скобочкам и излишним аннотациям типов (даже там где и ежу понятно что это за тип будет) – это тоже наследие использования C# где все прописывается.

F# поражает скудностью поддержки в студии. На одной из моих машин вылетела поддержка IntelliSense (полностью!), на другой – F# проекты по F5 только компилируются но не запускается отладчик. Очевидно что-то тут не так. Я конечно предпринимаю усилия для того чтобы работать с F# было более гуманно, но пока что нужно делать основные вещи, такие как лексер и парсер. Адаптировать их из сорцов F#-а — дело не для слабонервных.

WebSharper

Я упомянул этот фреймворк в моем первом сольном подкасте, сказав что он позволяет делать сайты, полностью оторваные от Asp.Net и IIS. Не знаю, один ли я страдаю такой неприязнью к IIS, но на данный момент для меня нет антидота: тот релиз Вебшарпера который состоялся недавно еще не поддерживает вторую версию Sitelet’ов, в которой как раз и должна быть поддержка создания полностью независимых сайтов. Что-то мне подсказывает, правда, что Адам Границ и компания планируют вообще сделать чуть ли не свой веб-сервер.

Так или иначе, я продолжаю эксперименты с Web#, а как только появятся новые сайтлеты, я начну тестировать все это дело уже на Linux. Интересно, что хоть технология и F#, нельзя сказать что она как-то связана с .Net или Microsoft.

NetKernel

Во-первых, NK – это Java-стек, но меня это не пугает т.к. ничего страшного там нет. NK – это реализация ресурсно-ориентированной парадигмы, которая сейчас будоражит умы всех кто занимается коммуникационными технологиями, например та же комманда WCF. По сути дела, RoC в представлении NK – это REST’ообразная концепция, которая предоставляет как бы аусторсинг веб-архитектуры, позволяя разработчикам писать компоненты, которые работают через ресурсы, запросы на ресурсы, конверсию различных типов ресурсов, и так далее.

NK это, если хотите, эволюция REST, и является интересной технологией (более живой чем существующие .Net аналоги) которая понравится тем, кто доселе работал с такими вещами как WCF REST.

Семантический Веб

Про семантический веб я уже писал, поэтому ограничусь конкретикой, а именно теми библиотеками, которые применимы к данной задаче.

Во-первых, это DotNetRdf – собственно библиотека, которая позволяет работать с RDF и поддерживает различные форматы хранилищ. Кстати, недавно нашел в блоге попытку создания semantic triple store на MongoDB.

Еще один аспект моих исследований связан уже с конкретикой, а именно с онтологией Good Relations и ее нынешней поддержкой гуглом (их Rich Snippets). Изначально у меня была идея попробовать подобавляеть поддержку всего этого в R2P чтобы с этим было легче работать, но потом я узнал что модели Xml и Html в моем любимом Решарпере не имеют общего корня, что означает что, по сути дела, все пришлось бы делать дважды. Возможно когда-нибудь у меня дойдут до этого руки.

Так вот, в попытках найти возможность создать .Net объектную модель из OWL-файла, описывающего GR, я набрел на проект ROWLEX, в котором поставляется утилита именно для этого. Интересно, а можно ли как-то автоматически брать данные со страниц, записывать их в эту модель и потом заталкивать все в MongoDB без каких-либо лишних действий? Подозреваю что все несколько сложнее…

MindTouch

Я занимался исследованием MindTouch в двух направлениях – в попытке понять, можно ли использовать эту штуку для документирования API большого программного продукта, а также мне хотелось понять, можно ли использовать его вместо SharePoint для внутренних целей.

По первому вопросу у меня нет ответа. Бесплатная версия MT шикарна, и покрывает 99% того что мне лично нужно. Поэтому в личных целях я начал ее использовать. Что же касается построения на нем портала разработчиков, то тут все неоднозначно. Я не decision maker в данном вопросе, но лично меня никто не убедил отдавать за коммерческий вариант этой штуки большие деньги.

Я рекоммендую всем скачать MindTouch Core (бесплатную версию) и поиграть с ней. Это действительно стоит того! Это по сути дела Wiki/CMS которая хорошо подходит для документации. Чудес в этом мире не бывает, но штука красивая, и я ей доволен.

TidePowerd GPU.NET

Поскольку несколько лет назад я плотно занимался GPGPU и даже сам эксплуатировал pixel shader’ы через Direct3D, наличие .Net библиотеки мне импонирует – давно пора. Тем более что трансляция IL во что-то еще – процесс весьма понятный и реализуемый без особых проблем. Библиотека GPU.Net – это как раз оно и есть. Еще одна абстракция чтобы облегчить нам жизнь.

Признаюсь что я только лишь посмотрел вебинар – я не игрался еще с самой библиотекой т.к. не было времени. Все впереди :)Update: уже успел созвониться с директором Tidepowerd – разговор шел в основном в ключе поддержки конкретно F# в GPU.NET. Заинтересованность определенно есть, так что рано или поздно замутим совместный конференс с Саймом, Границем, и партнерами. Как что состоится – обязательно напишу.

Fusion IO

Fusion IO – компания, которая за 3 года получила 300 миллионов долларов инвестиций. Их “фишка” – высокоскоростные хранилища данных, и когда я говорю “хранилище”, подразумевается PCI-карта с SSD-устройствами на ней. Конечно, Fusion IO говорит что их устройства – это не SSD, но на самом деле их отличие в качественном ПО (в плане прошивки их ASIC), а есть ли у них что-то кардинально новое – не знаю. Надо получить образец (надеюсь дадут) и самому посмотреть.

Хотя если по мне, так более интересной фишкой является NVidia Tegra – нам уже 10 лет назад нужно было иметь нормальные SoC-решения. Те же Fusion IO могли бы засунуть на свои карты контроллер и несколько FPGA которые я бы мог “флэшить” и им цены бы не было! Вот интересно – Intel уже начал нервничать или еще нет? Ведь по мне так самое главное это избавиться от ситуации когда я не могу иметь масштабно-инвариантную систему, а должен на каждый процессор закупать еще и материнскую плату, память, жесткий диск, и так далее. Надеюсь скоро эти телодвижения станут пережитками прошлого.

Кстати о Tegra… двухъядерные процессоры в мобильных телефонах это круто или как?

Written by Dmitri

7 января 2011 at 19:44

Опубликовано в .NET, f#, GPU, Hardware

Tagged with , , , ,

Ревью нескольких .Net-ориентированных книг от Manning

31 комментарий

Это еще один пост с обзором книг, но в этот раз «с изюминкой» – будем обсуждать книги издательства Manning. По сравнению с Apress (см. предыдущий обзор), издательство Manning Publications печатают книги совсем другого уровня. У них, например, печатаются такие люди как Джон Скит или Айенде. Книги от Manning – это далеко не утилитарные произведения, что с одной стороны хорошо (технический уровень намного выше) а с другой стороны не очень (книги иногда сложно читать, и многие из них недоступны для новичков).

Заметьте, тут всего 4 книжки. Хочется больше, но как-то руки не доходят попросить у Manning несколько review-копий.


DSLs in BOO


Собственно сам язык Boo (некоторые почему-то пишут BOO) мне интересен только одной фичей – поддержкой метапрограммирования или, другими словами, возможностью расширять компилятор. Привлекательно это потому, что научиться конкретно метапрограммированию в Boo не так сложно, хотя и приходится отказаться от таких приятностей как синтаксис C#, Visual Studio (нужно использовать SharpDevelop) и соответственно IntelliSense. Как бы там ни было, популярный блоггер Айенде решил встать «впереди планеты всей» и написал книжку про то, как создавать доменно-специфичные языки (DSL-и) на этом питонообразном языке.

Если быть откровенным, то книжка у Айенде не получилась. Совсем. Главной причиной является то, что Айенде плохо пишет – у него явно нет навыков для написания больших монографий, и с первых страниц книги читатель может ощутить тот дискомфорт который автор испытывает, пытаясь передать мысль в когерентной форме. Теперь насчет собственно структуры контента. Во-первых, Айенде плохо продает идею DSLей – а ее, как мне кажется, нужно продавать. Например, показать пример BDD-фреймворка и сказать что это DSL – это как-то нечестно потому что я, например, могу привести контр-пример где мы используем, скажем, Cucumber (Ruby) чтобы написать то же самое, и для этого мне не нужен Boo. Возникает естественный вопрос: а что нам дает именно Boo, чем он лучше и полезнее?

Далее в книге идет описание самого языка, и интересно что на создание DSLей на этом языке выделено всего 2 главы из 13 – остальное это либо вводные части, либо примеры реализаций. Кстати насчет примеров – они все не просто короткие, они коротенькие и ультра-простые. Это я к тому, что в моей практике DSLи имеет смысл использовать тогда, когда за одной строчкой DSL скрывается 5-10 обычного C#. У Айенде же в примерах соотношение примерно 1:1. Зато количество инфраструктурных обсуждений в книге очень большое – тестирование, создание UI, да просто обсуждение того как сортировать скрипты – возникает вопрос о смещении ценностей. Ведь все-таки изначальная идея была продать Boo как язык для создания DSLей, а в результате она как бы вышла на второй план.

В заключение скажу что на книгу я возлагал много надежд, купив ее в форме early access для подготовки к небольшому семинару по метапрограммированию, но в результате разочаровался. Что ж, иногда и так бывает.

1 stars1 stars1 stars1 stars1 stars

ASP.NET MVC 2 in Action


Иначе как «вынос мозга» эту книжку не обозвать. MVC in Action – это книга по Asp.Net MVC для тех кто уже похоже все знает но хочет проверить свои знания и заодно получить дозу зубодробильной информации от настоящих гуру в этом деле. Написало ее целых 5 авторов (все MVP), и они за словом в карман не лезут – тут у них и MvcContrib и Rhino.Mocks и другие прелести.

На самом деле – первая часть книги это еще цветочки – ее хоть как-то можно читать новичкам. А вот начиная со 2й части уже становится значительно сложнее, т.к. без хорошего багажа знаний современный паттернов и практик во всем написанном вполне реально утонуть. Тут, кстати, фигурирут такие вещи как DDD и такие технологии как Spark. (Это кстате в каком-то смысле минус т.к. большинство людей сейчас скорее интересует Razor.) В трерьей части книги появляются такие вещи как AutoMapper и NHibernate (опять же, все преподнесено на хорошем, презентабельном уровне). Ну и в последней главе, уже в разбивку, показаны разные многофункциональные прелести. В одной из глав, правда, приведен пример того, как написать autocomplete text-box, что немножко выбивает из ритма.

Если резюмировать – хорошая, компактная но полная актуальной информации «от станка» книжка. Подойдет для тех кто уже начал писать на MVC и теперь хочет сделать что-то серьезное. Рекоммендую!

4 stars4 stars4 stars4 stars4 stars

C# in Depth

Несмотря на то, что у меня есть свои фавориты в плане книжек по C#, я счел своим долгом прочитать именно эту – ведь её автором является не кто-то там, а сам Джон «Чак Норрис» Скит. Для тех из вас кто живет на другой планете, мистер Скит является звездой портала StackOverflow и одним из ведущих экспертов по C# (а также по Java, на которой он пишет в Google, хотя подозреваю что его интерес к этому языку несколько меньше по весьма понятным причинам).

Итак, если коротко – то C# in Depth (я читал первую версию, сейчас есть вторая) – это просто конфетка. Для человека который хочет узнать что такое C# (например, если вы пришли к нему из мира Java или C++), эта книга – манна небесная. Написана она очень хорошо – в очередной раз подтверждает что мы британцы в принципе неплохо пишем :)

Книга, как вы догадались, является хрестоматией C# разработки, начиная от азов и кончая такими вещами как Linq. Читается она очень легко, без WTF-моментов. Скит пишет в очень неформальной манере, завлекая читателя в интересные тонкости языка. Я читал много книг по C#, но такой дозы позитива я до этого не находил нигде. Мне кажется, что эта книга формирует неплохой «двойной удар» с книжкой C# in a Nutshell (о книгах O’Reilly мы еще поговорим) – ведь первая объясняет что это за язык такой, а вторая описывает библиотеки классов.

Вообщем, впечатление осталось очень позитивное, поэтому C# in Depth получает твердую пятерку как за стиль так и за содержание.

5 stars5 stars5 stars5 stars5 stars

Real-World Functional Programming

После того как я опубликовал отзывы по книге Expert F#, коллеги подсказали что неплохо было бы мне было посмотреть и на эту книгу, благо не всем хочется пролистывать дорогой (одна из самых дорогих книг) талмуд на тему F#. Также, стимулировали к прочтению этой книги две вещи – то что книжка покрывает не только F# но и C#, а также (drumroll please) то что и к этой книге приложил руку сам Джон Скит (хотя основной автор – Томаш Петричек). Сорри если кого-то этот мем начал раздражать – я не нарочно.

Итак, R-WFP – это книжка про «бытовое» функциональное программирование без высоко поднятого носа сообществ языка Haskell, Lisp или каких-то еще – это, в очередной раз, прагматика и лично мне это очень нравится. (Собственно термин ‘Real-World’ в названии как бы намекает…) Первое что пришло мне в голову – они украли у меня идею функциональной анимации! Я недавно на эту тему статью писал, она даже на CP приз выиграла. Впрочем, очень приятно что авторы пишут про знакомые мне идеи. Также приятно что не остались за бортом и другие языки программирования – такие как Lisp и Erlang. Понятное дело что мы тут про .Net говорим, но наше сообщество порой страдает узким мировозрением по отношению к другим платформам.

Книга, точно так же как и C# in Depth, делает плавную прогрессию от азов (например, от рассказов про то что такое кортежи или списки – это ведь нужно для понимания F#) и прогрессируя до таких вещей как применение функций высокого порядка. Вообще что меня поразило, так это широта задач, которые покрывает эта книга – тут и анимация, и создание DSLей (я правда и сам писал про DSLи на F#) и реактивное программирование. Правда тут есть один весьма существенный (с моей точки зрения) минус – поскольку книга была опубликована в декабре 2009, в книге не слова на тему Reactive Extensions. Обидно! Ведь не знаю как вам, а лично мне очень хотелось бы иметь описание тех новых структур и опереторов, которые там фигурируют, а также четкое руководство к использованию. Хоть я и сам, в принципе, понимаю что и как.

Скажу честно, книга мне понравилась – в ней нашлось предостаточно моментов (особенно по F#, где мои знания на «четыре с минусом») которые меня удивили и заставили пересмотреть то как я пишу код. Другое дело что у меня очень мало F# «в продашкн», но это уже чисто мои проблемы.

4 stars4 stars4 stars4 stars4 stars

Written by Dmitri

27 августа 2010 at 14:07

Опубликовано в .NET, Books, C#, f#

Нужно ли полиглотное программирование в стеке .Net?

54 комментария

Из комментариев к коротенькому посту на Хабре про использование Python как базового языка для Asp.Net мне понравился вот этот комментарий от centur:

Ну собственно повторный вопрос — зачем?
Дмитрий Нестерук высказал вполне здравую мысль — зачем нужна вся эта куча языков, если есть так активно развиваемый C#.
В общем возможность мульти-языкастости среды это клево, но вот потенциальная поддержка этого — один из кругов ада =)

Мне кажется что пришло самое время досконально обсудить проблему того, что конкретно нам дают все эти языки, и насколько “полиглотное программирование” вообще имеет смысл.

F#

Давайте прокинем VB и начнем с языка №2 который уже является полноправным жителем нашей .Net экосистемы. Да, интересный, функциональный язык. А теперь внимание вопрос: в чем собственно его приемущества, какой конкретно от него business value?

Вот я написал несколько проектов либо полностью на F# либо частично. У меня была возможность попробовать такие “рекламируемые” фичи как async workflows и повсеместное отсутствие мутабельности. И вот к каким выводам я пришел:

  • Бизнес-программирование по большому счету мутабельное, тем самым язык который вставляет вам 100 палок в каждое колесо при попытке изменить переменную для него слабо подходит. Чего стоит например вот такое определение:
    let context : Ref<IVisitable option> = ref None
    
  • Async workflows – это всего лишь кастомная издевка над синтаксисом. Это хак. Такой же хак есть и в .Net с использованием AsyncEnumerator (PowerThreading). Ничего “гениального” в этом нет, работу это не упрощает. Вот вам прямое сравнение двух подходов – выводы делайте сами:

    F#

    let private DownloadPage(url:string) =
      async {
        try
          let r = WebRequest.Create(url)
          let! resp = r.GetResponseAsync() // let! позволяет дождаться результата
          use stream = resp.GetResponseStream()
          use reader = new StreamReader(stream)
          let html = reader.ReadToEnd()
          use fs = new FileStream(@"c:\temp\file.htm", FileMode.Create,
                                  FileAccess.Write, FileShare.None, 1024, true);
          let bytes = Encoding.UTF8.GetBytes(html);
          do! fs.AsyncWrite(bytes, 0, bytes.Length) // ждем пока все запишется
        with
          | :? WebException -> ()
      }
    // вызов ниже делает синхронный вызов метода, но поведение внутри метода - асинхронное
    Async.RunSynchronously(DownloadPage("http://habrahabr.ru"))
    

    C#

    public IEnumerator<int> DownloadPage(string url, AsyncEnumerator ae)
    {
      var wr = WebRequest.Create(url);
      wr.BeginGetResponse(ae.End(), null);
      yield return 1;
      var resp = wr.EndGetResponse(ae.DequeueAsyncResult());
      var stream = resp.GetResponseStream();
      var reader = new StreamReader(stream);
      string html = reader.ReadToEnd();
      using (var fs = new FileStream(@"c:\temp\file.htm", FileMode.Create,
                          FileAccess.Write, FileShare.None, 1024, true))
      {
        var bytes = Encoding.UTF8.GetBytes(html);
        fs.BeginWrite(bytes, 0, bytes.Length, ae.End(), null);
        yield return 1;
        fs.EndWrite(ae.DequeueAsyncResult());
      }
    }
    
  • Метапрограммирование в F# неполноценно. То, что там есть цитирование, еще ничего не значит, т.к. нам хочется не транслировать F# в какой-то там язык, мы хотим то цитирование и “сплайс” которыми мы можем помочь компилятору сгенерировать некие структуры в процессе компиляции. Настоящее статическое метапрограммирование есть пока что только в Boo, но оно будет в C#.
  • Создание DSLей – да, оно проще. Если интересно – вот моя статья. Только DSLи – это пока направление которое в основном, во-первых, для internal use, а во-вторых только для продвинутых фирм вроде моей. Точно не для общего пользования. Не верите? Let me google that for you.
  • Идея о том, что F# лучше чем C# для сложного алгоритмического программирования в финансовых и аналогичных индустриях не раскрыта. Да, его используют. Детальных case studies мало, и опять же, никто толком не может объяснить почему. И вообще, какая разница, во что транслировать формулы?

Есть еще нарекания в плане “общей вменяемости” – например набор всяких <@ @>, <@@ @@>, [], [| |], {}, (|x|_|), ->, <-, >>, :>, :?>, |> это же просто бред какой-то! Это “магические операторы”, иначе не назвать.

Вообщем, я действительно хочу использовать F#, но когда я реально начинаю его использовать, то каждая из фич которая изначально кажется selling point языка оказывается неповоротливой и непрактичной. Например, я надеялся что pattern matching поможет мне структурировать разбор строк вот в этом проекте. На самом же деле, код получился практически нечитабельным. Где тут maintainability? Правильно – ее нет в помине.

Boo

Я конечно понимаю что товарищ Айенде, будучи “впереди планеты всей” в плане работы с базами данных, решил поделиться своими замечаниями на тему того, как метапрограммирование на языке Boo может принести реальный business value. Проблема в том, что книжка у него отровенно не удалась, и тем самым аргумент как бы затух.

Идея Boo в том, что у вас есть якобы более лаконичный синтаксис (а-ля Python) плюс возможности расширения компилятора которые делают возможным мета-изыски которых мы затаив дыхание ждем в C#. Ну так что, давайте обсудим эти два “бенефита” на предмет их полезности.

Вообще, идея того что Питон чем-то “лаконичней” чем C# или какой-то там другой язык – это попытка продать абстрактное “нечто”, не подкрепляя это толком никакими аргументами. Возьмем например произвольный кусок кода из проекта:

class ManaIndicatorsAttribute(AbstractAstAttribute):   
  public override def Apply(node as Node):
    c = node as ClassDefinition
    for i in range(ManaSumAttribute.LandTypes.Count):
      basic = ManaSumAttribute.LandTypes[i] as string
      hybridLands as List = []
      for j in range(HybridManaAttribute.HybridLandTypes.Count):
        hybrid = HybridManaAttribute.HybridLandTypes[j] as string
        if (hybrid.Contains(basic)):
          hybridLands.Add(hybrid)
      rbasic = ReferenceExpression(basic.ToLower())
      b = Block();
      b1 = [| return true if $rbasic > 0 |]
      b.Statements.Add(b1)
      for k in range(hybridLands.Count):
        rhybrid = ReferenceExpression((hybridLands[k] as string).ToLower())
        b2 = [| return true if $rhybrid > 0 |]
        b.Statements.Add(b2)
      r = [|
        $("Is" + basic):
          get:
            $b;
      |]
      c.Members.Add(r)

Ну-с, и где тут ‘лаконичность’? На практике получается жесткий, слабочитаемый код, в котором не спасает вывод типов, в котором записи вроде return x if y добавляют только дополнительную степень неоднозначиности, а вы бы видели IL который сгенерирован – у меня при виде того как код на Boo разворачивается в код на C# с помощью Рефлектора глаза на лоб лезут – такое впечатление что этому языку просто плевать на стэк. Поэтому простое суммирование пяти переменных переведенное в C# вернется вам не как a + b + c + d + e а скорее как ((((a + b) + c) + d) + e).

Насчет вещей, аналогичных LINQ. Да, действительно, до Linq в Питоне был козырь в плане разбора списков и прочих, т.е. можно было написать list[1,-1] или например подобное:

List(y for y in (x**2 for x in range(10)) if y % 3 != 0)

Теперь это неактуально т.к. в том же C# можно сделать то же самое, причем с поддержкой IntelliSense и без необходимости запоминать всякие range() (какая разница с Enumerable.Range()? меньше букв?) и прочие названия. Приемущество Linq в том, что это унифицированный API, который выглядит одинаково вне зависимости от того, используете ли вы Linq to Objects, Linq to Events (Rx) или Linq to CepStream (StreamInsight). Тут в основном одни и те же ключевые слова, плюс IntelliSense если вы что-то забыли.

Генераторы… ах, генераторы, идея-то хорошая, но вот скажите мне, что делает следующий кусочек кода:

def TestGenerator():
  i = 1
  yield i
  for x in range(10):
    i *= 2
    yield i

Утипизация? Тем кому она нужна уже имеют ее в C# с помощью таких библиотек как LinFu. Регулярные выражения через /? Ну что ж, может это и удобней, но лично мне все равно, т.к. я использую приложение, которое решает за меня все эти проблемы (хотя соглашусь, что конверсия в C#-строку выглядит коряво). Что остается?

Остается пожалуй только метапрограммирование. Это, на данный момент, единственный козырь в языке который, как мы с коллегами считаем, умрет сразу же после выхода C#5. И если вы считаете что метапрограммивание нужно сегодня и нужно срочно использовать Boo, вот вам несколько контр-аргументов:

  • Язык и инструментарий не развиваются. BooLangStudio мертва. Документация на сайтах частично устарела.
  • Использовать две IDE достаточно напряжно. Я имею ввиду, что вам придется компилировать Boo через SharpDevelop, а потом импортировать сборки в ваши C# проекты. Никакого configuration management рядом не стояло.
  • Компилятор сырой, и у меня были мета-методы которые вообще не сходились, т.е. программа компилировалась в невалидный IL. Никаких гарантий безопасности компилятор не дает.
  • Про IntelliSense или аналогичные вещи можно забыть – Boo вы будете программировать вслепую.

Вообщем, для тех кого это действительно интересует, у меня есть вводная статья про метапрограммирование на Boo. Только вот стоит ли напрягаться? Ведь C#5 не за горами, и есть большая вероятность, что с его выходом можно будет пометить Boo как [Obsolete].

IronPython

Вся критика по языку Boo может быть обращена к IronPython ибо синтаксис похожий. Единственный плюс – что IronPython как бы более “зрелый” и даже имеет поддержку в Visual Studio (IronPythonTools). Ну и да, “динамичность” этого языка несет в себе, якобы, некоторые приемущества которые всем уже известны.

Только есть одно но – тема CLR vs DLR (не знаю можно ли так писать) – явно не раскрыта. Например, почему создатели языка Go намеренно сделали его статическим? Правильно – скорость. А что делать с языком который не дает ощутимых бенефитов в плане синтаксиса, и который по скорости не тянет (чего стоит скорость декораторов, например – да легче помучаться и на C# написать, или подвязать тот же PostSharp).

IronRuby

Если питонообразные языки человек с опытом C# еще как-то может понять, то Ruby – это чистая магия, т.е. язык который нужно долго и прагматично изучать с нуля. А стоит ли это того? Alt.Net сообщество уже прожужжало нам все уши на тему того, что Ruby лучше для тестирования (см. например Cucumber), и я с этим не спорю – действительно, чем ближе мы в BDD приближаемся к обычному английскому, тем больше к нам аппелирует сама концепция BDD. Только вот есть одно но – BDD пока штука не доказанная, она как и приемочное тестирование в стиле Fitnesse является интересной абстракцией к которой индустрия присматривается, но не делает ее стандартом для разработки.

Чем еще хорош Ruby? Да, он неплохо поддерживает динамическое метапрограммирование, есть даже книжка на эту тему. Но занимаясь именно динамическим, а не статическим, метапрограммированием, следует понимать что это не совсем одно и то же – динамическое программирование это серьезное заигрывание с концепциями а-ля ExpandoObject, то есть манипуляция типов на этапе исполнения – нечто, что после безумной головной боли можно реализовать и на Mono.Cecil. В то же время, статическое метапрограммирование – это возможность расширять не тип, а компилятор. Тем самым, сравнивать эти два подхода не очень уместно, и то что Роб Конери хочет “метапрограммирование сегодня и сейчас” – это лично его представление о том, что это такое, и мне кажется он смешивает краски.

Следует заметить, что у IronRuby в отличии от IronPython нет поддержки в Visual Studio. Поэтому придется использовать или SharpDevelop или (сюрприз!!!) IntelliJ IDEA/RubyMine. При этом вы получите IntelliSense для ваших собственных конструкций, но его у вас не будет для различных .Net-библиотек.

Заключение

Прежде всего – да – существуют и другие языки в стеке .Net, например IronScheme. Но чем дальше мы отходим от “канона” тем дальше мы и от обычного бизнес-программирования, а следовательно каким бы элегантным не был тот или иной язык, можно с уверенностью говорить что без участия комьюнити, uptake такого языка будет равен нулю. Примером может служить язык Nemerle, которого, несмотря на наличие интересных (но ни в коей мере не революционных) идей, ожидает та же участь что и Boo.

Проблемы со сторонними языками можно охарактеризовать примерно так:

  • Бизнес не примет их без поддержки Microsoft
  • Количество людей, хорошо знающих эти языки мало́, а значит те сотрудники что у вас их знают рискуют стать незаменимыми
  • Клиент вряд ли согласиться на проект на языке, особенно если саппорт он хочет получить не у вас, а где-то еще
  • Инструментарий пока не очень готов – это касается даже таких языков как F#
  • Реально не хватает success stories, которые четко показывают бенефиты

В заключение этой статьи я призываю всех .Net-разработчиков порадоваться, что у нас есть такой гибкий язык как C#, который удовлетворяет все 100% (да-да, именно 100%) наших потребностей. С другой стороны, никто не мешает экспериментировать – главное чтобы ваши эксперименты потом попадали в production, а то иначе это все академизм и теоретизирование. Удачи!

Update 1 Как подсказал коллега @butaji, DLR полезен тем что позволяет легко “скриптовать” ваши .Net-приложения, то есть фактически получать возможность скриптинга ваших .Net-конструктов без использования COM Automation или еще более кустарных извратов. Это конечно огромный плюс в тех ситуациях когда это действительно нужно, особенно если учесть альтернативы (создание всяких проприетарных DSL и т.п.).

Update 2 Судя по всему, с DLR-языками, в частности с IronRuby, все достаточно плохо.

Update 3
Судя по всему, огромным приемуществом DLR являются ситуации, когда нам нужно постоянно подтачивать нашу программу уже после компиляции. Коллега @butaji уже это упомянул, но мне пока это не попадалось как вопиющий юз-кейс. А теперь я даже обзавелся компонентом который делает синтактическую подсветку. Буду встраивать; посмотрим что получится.

Written by Dmitri

2 августа 2010 at 16:55

Опубликовано в .NET, C#, f#