[ANN] Crutch: недометапрограммирование в C# 4.0
От: Klapaucius  
Дата: 07.07.10 07:11
Оценка: 38 (5) +1
Viewer Discretion Is Advised.

В C# 4.0 у expression tree появились новые узлы, соотвествующие стейтментам, а не только выражениям, как раньше. Это раскрывает новые возможности. Но путь к этим возможностям тернист. Дело в том, что "цитирование" для них не работает. (далее примеры отсюда)
Т.е написать вот так все еще нельзя:
Expression<Func<int, List<int>>> getPrimes = to =>
{
    var res = new List<int>();
    for (int n = 2; n <= to; n++)
    {
        bool found = false;
        for (int d = 2; d <= Math.Sqrt(n); d++)
        {
            if (n % d == 0)
            {
                found = true;
                break;
            }
        }
        if (!found)
            res.Add(n);
    }
    return res;
};

Обходной путь — составлять такие деревья вручную, что будет выглядеть так:
  Скрытый текст
var to = Expression.Parameter(typeof(int), "to");
var res = Expression.Variable(typeof(List<int>), "res");
var n = Expression.Variable(typeof(int), "n");
var found = Expression.Variable(typeof(bool), "found");
var d = Expression.Variable(typeof(int), "d");
var breakOuter = Expression.Label();
var breakInner = Expression.Label();
var getPrimes = 
    Expression.Lambda<Func<int, List<int>>>(
        // {
        Expression.Block(
            // List<int> res;
            new [] { res },
            // res = new List<int>();
            Expression.Assign(
                res,
                Expression.New(typeof(List<int>))
            ),
            // {
            Expression.Block(
                // int n;
                new [] { n },
                // n = 2;
                Expression.Assign(
                    n,
                    Expression.Constant(2)
                ),
                // while (true)
                Expression.Loop(
                    // {
                    Expression.Block(
                        // if
                        Expression.IfThen(
                            // (!
                            Expression.Not(
                                // (n <= to)
                                Expression.LessThanOrEqual(
                                    n,
                                    to
                                )
                            // )
                            ),
                            // break;
                            Expression.Break(breakOuter)
                        ),
                        // {
                        Expression.Block(
                            // bool found;
                            new[] { found },
                            // found = false;
                            Expression.Assign(
                                found,
                                Expression.Constant(false)
                            ),
                            // {
                            Expression.Block(
                                // int d;
                                new [] { d },
                                // d = 2;
                                Expression.Assign(
                                    d,
                                    Expression.Constant(2)
                                ),
                                // while (true)
                                Expression.Loop(
                                    // {
                                    Expression.Block(
                                        // if
                                        Expression.IfThen(
                                            // (!
                                            Expression.Not(
                                                // d <= Math.Sqrt(n)
                                                Expression.LessThanOrEqual(
                                                    d,
                                                    Expression.Convert(
                                                        Expression.Call(
                                                            null,
                                                            typeof(Math).GetMethod("Sqrt"),
                                                            Expression.Convert(
                                                                n,
                                                                typeof(double)
                                                            )
                                                        ),
                                                        typeof(int)
                                                    )
                                                )
                                            // )
                                            ),
                                            // break;
                                            Expression.Break(breakInner)
                                        ),
                                        // {
                                        Expression.Block(
                                            // if (n % d == 0)
                                            Expression.IfThen(
                                                Expression.Equal(
                                                    Expression.Modulo(
                                                        n,
                                                        d
                                                    ),
                                                    Expression.Constant(0)
                                                ),
                                                // {
                                                Expression.Block(
                                                    // found = true;
                                                    Expression.Assign(
                                                        found,
                                                        Expression.Constant(true)
                                                    ),
                                                    // break;
                                                    Expression.Break(breakInner)
                                                // }
                                                )
                                            )
                                        // }
                                        ),
                                        // d++;
                                        Expression.PostIncrementAssign(d)
                                    // }
                                    ),
                                    breakInner
                                )
                            ),
                            // if
                            Expression.IfThen(
                                // (!found)
                                Expression.Not(found),
                                //    res.Add(n);
                                Expression.Call(
                                    res,
                                    typeof(List<int>).GetMethod("Add"),
                                    n
                                )
                            )
                        ),
                        // n++;
                        Expression.PostIncrementAssign(n)
                    // }
                    ),
                    breakOuter
                )
            ),
            res
        ),
        to
    // }
    )

Очевидно, что писать такие вещи — не самое приятное занятие, которое можно себе найти. Поэтому, я сделал для себя встроенный язык их построения, который пока называется Crutch — название рабочее, возможно, что позже я придумаю другое, похуже. Вернее, пока это только пруф-оф-концепт. Вот пример:
//Создаем ф-ю. Захватываем переменную to и конструируем список
var getPrimes = M.Fun(M.Let(() => to, () => new List<int>()).In( //область видимости - выражение, возвращающее значение.
        (max, res) => //именуем их для этой области видимости.
        M.For(2.Const(), max, n => //цикл создает для своего скоупа переменную n, меняющуюся от 2.Const() до max.
                M.Let(() => false).In( //создаем и переменную found.
                //Apply подставляет выражения в лямбду. В данном случае n вместо x. В общем, Apply - он и есть Apply.
                found => M.Let(n.Apply(x => (int)Math.Sqrt(x))).In(
                max2 => M.Block( //блок - выражение типа Expr<Void> - ничего не возвращает.
                        M.For(2.Const(), max2, (d, breakLoop) => //этот цикл подает с свою область видимости ф-ю breakLoop.
                                M.If(n.Apply((a, b) => a % b == 0, d)) //условие If Then - ничего не возвращает.
                                .Then(found.Assign(true), breakLoop())), //присваиваем значение, выходим из цикла. 
                        M.If(found.Apply(p => !p))
                        .Then(res.Apply((l,e) => l.Add(e),n))))))
        .Result(res))); //блок, который ничего не возвращает можно сделать возвращающим значение с помощью Result.

  во что это разворачивается
.Lambda #Lambda1<System.Func`1[System.Collections.Generic.List`1[System.Int32]]>() {
    .Block(System.Int32 $var1) {
        $var1 = .Constant<ConsoleApplication1.Program+<>c__DisplayClass15>(ConsoleApplication1.Program+<>c__DisplayClass15).to;
        .Block(System.Collections.Generic.List`1[System.Int32] $var2) {
            $var2 = .New System.Collections.Generic.List`1[System.Int32]();
            .Block() {
                .Block(System.Int32 $var3) {
                    $var3 = 2;
                    .Loop  {
                        .Block() {
                            .If ($var3 > $var1) {
                                .Break #Label1 { }
                            } .Else {
                                .Default(System.Void)
                            };
                            .Block(System.Boolean $var4) {
                                $var4 = False;
                                .Block(System.Int32 $var5) {
                                    $var5 = .Invoke (.Lambda #Lambda2<System.Func`2[System.Int32,System.Int32]>)($var3);
                                    .Block() {
                                        .Block(System.Int32 $var6) {
                                            $var6 = 2;
                                            .Loop  {
                                                .Block() {
                                                    .If ($var6 > $var5) {
                                                        .Break #Label2 { }
                                                    } .Else {
                                                        .Default(System.Void)
                                                    };
                                                    .If (
                                                        .Invoke (.Lambda #Lambda3<System.Func`3[System.Int32,System.Int32,System.Boolean]>)(
                                                            $var3,
                                                            $var6)
                                                    ) {
                                                        .Block() {
                                                            $var4 = True;
                                                            .Break #Label2 { }
                                                        }
                                                    } .Else {
                                                        .Default(System.Void)
                                                    };
                                                    $var6++
                                                }
                                            }
                                            .LabelTarget #Label2:
                                        };
                                        .If (
                                            .Invoke (.Lambda #Lambda4<System.Func`2[System.Boolean,System.Boolean]>)($var4)
                                        ) {
                                            .Invoke (.Lambda #Lambda5<System.Action`2[System.Collections.Generic.List`1[System.Int32],System.Int32]>)(
                                                $var2,
                                                $var3)
                                        } .Else {
                                            .Default(System.Void)
                                        }
                                    }
                                }
                            };
                            $var3++
                        }
                    }
                    .LabelTarget #Label1:
                };
                $var2
            }
        }
    }
}

.Lambda #Lambda2<System.Func`2[System.Int32,System.Int32]>(System.Int32 $x) {
    (System.Int32).Call System.Math.Sqrt((System.Double)$x)
}

.Lambda #Lambda3<System.Func`3[System.Int32,System.Int32,System.Boolean]>(
    System.Int32 $a,
    System.Int32 $b) {
    $a % $b == 0
}

.Lambda #Lambda4<System.Func`2[System.Boolean,System.Boolean]>(System.Boolean $p) {
    !$p
}

.Lambda #Lambda5<System.Action`2[System.Collections.Generic.List`1[System.Int32],System.Int32]>(
    System.Collections.Generic.List`1[System.Int32] $l,
    System.Int32 $e) {
    .Call $l.Add($e)
}

Страшно, как атомная война, но есть некоторые явные преимущества перед использованием ручного построения.
  • Краткость (относительная).
  • Проверки компилятора.
  • Автодополнение.
  • Вывод типов.
    Так что, если кому интересно — я причешу код и выложу. До более-менее работающего состояния я все равно планирую это доводить. В следующих сериях, помимо конструирования деревьев будет и разбор.
    Подозреваю, кстати, что подобное кто-то уже делал, но я не нашел.
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
  • 'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Visor2004  
    Дата: 07.07.10 11:01
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:

    K>Viewer Discretion Is Advised.


    А зачем это все?
    Помните!!! ваш говнокод кому-то предстоит разгребать.
    Re: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: achmed Удмуртия https://www.linkedin.com/in/nail-achmedzhanov-9907188/
    Дата: 07.07.10 11:17
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:


    K>
    K>//Создаем ф-ю. Захватываем переменную to и конструируем список
    K>var getPrimes = M.Fun(M.Let(() => to, () => new List<int>()).In( //область видимости - выражение, возвращающее значение.
    K>        (max, res) => //именуем их для этой области видимости.
    K>        M.For(2.Const(), max, n => //цикл создает для своего скоупа переменную n, меняющуюся от 2.Const() до max.
    K>                M.Let(() => false).In( //создаем и переменную found.
    K>                //Apply подставляет выражения в лямбду. В данном случае n вместо x. В общем, Apply - он и есть Apply.
    K>                found => M.Let(n.Apply(x => (int)Math.Sqrt(x))).In(
    K>                max2 => M.Block( //блок - выражение типа Expr<Void> - ничего не возвращает.
    K>                        M.For(2.Const(), max2, (d, breakLoop) => //этот цикл подает с свою область видимости ф-ю breakLoop.
    K>                                M.If(n.Apply((a, b) => a % b == 0, d)) //условие If Then - ничего не возвращает.
    K>                                .Then(found.Assign(true), breakLoop())), //присваиваем значение, выходим из цикла. 
    K>                        M.If(found.Apply(p => !p))
    K>                        .Then(res.Apply((l,e) => l.Add(e),n))))))
    K>        .Result(res))); //блок, который ничего не возвращает можно сделать возвращающим значение с помощью Result.
    K>


    Это конечно лучше чем конструировать Expression вручную, но уж лучше на мой взгляд использовать F#.
    Можно поинтересоваться Вы для каких целей используете метапрограммирование?
    Re[2]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Аноним  
    Дата: 08.07.10 09:36
    Оценка:
    Здравствуйте, Visor2004, Вы писали:

    V>А зачем это все?


    Поддерживаю вопрос — для чего все это?
    Re: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 09.07.10 07:07
    Оценка:
    K>Так что, если кому интересно — я причешу код и выложу. До более-менее работающего состояния я все равно планирую это доводить. В следующих сериях, помимо конструирования деревьев будет и разбор.

    А вот и код.
    FusionArray — это пример использования. Склеивание цепочки мапов в один цикл.
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re[2]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 09.07.10 07:14
    Оценка:
    Здравствуйте, Visor2004, Вы писали:

    V>А зачем это все?


    Ну а зачем вообще метапрограммирование? Для борьбы с boilerplate code в частности и сокращения объема кода вообще. И для оптимизаций. В данном случае и оптимизаций с учетом информации, получаемой во время выполнения.
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re[2]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 09.07.10 07:23
    Оценка:
    Здравствуйте, achmed, Вы писали:

    A>Это конечно лучше чем конструировать Expression вручную, но уж лучше на мой взгляд использовать F#.


    F#, конечно, во многих случаях лучше, но не настолько, чтобы я стал его проталкивать и популяризировать на работе. И не настолько интересен, чтобы играться с ним в свободное время.
    Самые серьезные, на мой взгляд, проблемы у него вообще с С# общие.
    Кроме того, я ML недолюбливаю, хотя по этим моим Let In и Fun выше такого и не скажешь. Они там не от хорошей жизни (как и в ML, впрочем).
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re[3]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Closer  
    Дата: 09.07.10 07:48
    Оценка: +1
    Здравствуйте, Klapaucius, Вы писали:

    K>Ну а зачем вообще метапрограммирование? Для борьбы с boilerplate code в частности и сокращения объема кода вообще. И для оптимизаций. В данном случае и оптимизаций с учетом информации, получаемой во время выполнения.


    Можете привести несколько (3-х вполне будет достаточно) более конкретных примеров из жизни в которых ваше решение поможет сократить кол-во кода и будет оптимальнее чем аналогичное решение но реализованное без Expression?
    Мы были здесь. Но пора идти дальше. (с) Дуглас Коупленд, Рабы "Микрософт"
    Re[4]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: achmed Удмуртия https://www.linkedin.com/in/nail-achmedzhanov-9907188/
    Дата: 09.07.10 10:41
    Оценка:
    Здравствуйте, Closer, Вы писали:


    C>Можете привести несколько (3-х вполне будет достаточно) более конкретных примеров из жизни в которых ваше решение поможет сократить кол-во кода и будет оптимальнее чем аналогичное решение но реализованное без Expression?


    Выше была ссылка на fusion array, наколько я понял аналог этого
    Автор: deniok
    Дата: 27.10.08
    .
    Нужно для того чтобы писать оптимальный код на C# в функциональном стиле
    Re[3]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: achmed Удмуртия https://www.linkedin.com/in/nail-achmedzhanov-9907188/
    Дата: 09.07.10 18:25
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:

    K>F#, конечно, во многих случаях лучше, но не настолько, чтобы я стал его проталкивать и популяризировать на работе. И не настолько интересен, чтобы играться с ним в свободное время.

    K>Самые серьезные, на мой взгляд, проблемы у него вообще с С# общие.
    интересно какие?
    K>Кроме того, я ML недолюбливаю, хотя по этим моим Let In и Fun выше такого и не скажешь. Они там не от хорошей жизни (как и в ML, впрочем).
    чем не угодили let in в ML ?
    Re[4]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 12.07.10 10:16
    Оценка: 3 (1)
    Здравствуйте, achmed, Вы писали:

    K>>Самые серьезные, на мой взгляд, проблемы у него вообще с С# общие.

    A>интересно какие?

    Хуже всего — слабая система типов. О некоторых вещах я уже писал. Об отсутствии higher-kinded polymorphism
    Автор: Klapaucius
    Дата: 15.06.10
    , например.

    K>>Кроме того, я ML недолюбливаю, хотя по этим моим Let In и Fun выше такого и не скажешь. Они там не от хорошей жизни (как и в ML, впрочем).

    A>чем не угодили let in в ML ?

    Я в принципе, ничего не имею против let in как выражения и допускаю, что для него есть своя область применения, если, конечно, меня не заставляют пользоваться им постоянно, вместо более вменяемой альтернативы where.
    Дело в том, что объявление перед использованием заставляет при чтении кода продираться через массу второстепенных деталей, которые вообще можно было бы и не читать, прежде чем удается добраться до главного.
    За деревьями не видно леса:
    let лес = 
        let лиственноеДерево = "береза" in
        let хвойноеДерево = "сосна" in
        лиственноеДерево + хвойноеДерево;;

    А тут такой проблемы нет:
    лес = лиственноеДерево ++ хвойноеДерево where
        лиственноеДерево = "береза"
        хвойноеДерево = "сосна"

    В довершение ко всему, let в ML еще и изувечен строгостью языка и оброс по этой причине совершенно ненужным синтаксическим мусором:
    let rec foo = bar
    and     bar = foo

    Зачем все эти let rec and? And еще и связывает объявления в одном месте файла, не давая записать что-то между ними.
    Вот как надо:
    foo = bar
    bar = foo
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re[5]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Lazy Cjow Rhrr Россия lj://_lcr_
    Дата: 12.07.10 11:01
    Оценка:
    Klapaucius,

    K>Вот как надо:

    K>
    K>foo = bar
    K>bar = foo
    K>

    Ну дык, Хаскел-то лениииивый.
    quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
    Re[6]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 12.07.10 11:40
    Оценка:
    Здравствуйте, Lazy Cjow Rhrr, Вы писали:

    LCR>Ну дык, Хаскел-то лениииивый.


    Ну так именно это я и имел в виду, говоря о том, что в ML это не от хорошей жизни. Мусорный синтаксис — прямое последсвие строгости языка.
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re[5]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Mr.Cat  
    Дата: 12.07.10 11:57
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:
    K>Зачем все эти let rec
    Если я правильно помню, то let rec — это первый костыль(тм) типизированного лямбда-исчисления (в котором не бывает знакомых по нетипизированному Y-комбинаторов), который, видимо, в ML решили подчеркнуть. Ленивость тут ни при чем. Не?
    Re[6]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 12.07.10 13:12
    Оценка:
    Здравствуйте, Mr.Cat, Вы писали:

    MC>Если я правильно помню, то let rec — это первый костыль(тм) типизированного лямбда-исчисления (в котором не бывает знакомых по нетипизированному Y-комбинаторов), который, видимо, в ML решили подчеркнуть. Ленивость тут ни при чем. Не?


    Не совсем.
    --костыль костылем, но с типами это обходится
    newtype Rec a = Rec { unrec :: Rec a -> a }
    
    y f = (\x -> f ((unrec x) x)) (Rec (\x -> f ((unrec x) x)))
    
    fact = y $ \fact' n -> 
        case n of
            0 -> 1
            otherwise -> n * fact' (n - 1)
            
    --fact 2
    --2

    Работает.
    Другое дело тут:
    type 'a Rec = Rec of ('a Rec -> 'a)
    
    let unrec = fun (Rec x) -> x
    
    let y f =  (fun x -> f ((unrec x) x)) (Rec (fun x -> f ((unrec x) x)))
    
    let fact = y (fun fact' n -> 
        match n with
            | 0 -> 1
            | _ -> n * fact' (n - 1))
    (*>fact 2
    ждите ответа-ждите ответа-ждите ответа*)
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Sinclair Россия https://github.com/evilguest/
    Дата: 14.07.10 04:45
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:

    K>Подозреваю, кстати, что подобное кто-то уже делал, но я не нашел.

    Мнэ-э-э, вот тут смотрел?
    Уйдемте отсюда, Румата! У вас слишком богатые погреба.
    Re[2]: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: Klapaucius  
    Дата: 14.07.10 07:45
    Оценка:
    Здравствуйте, Sinclair, Вы писали:

    S>Мнэ-э-э, вот тут смотрел?


    Конечно. Эта ссылка, кстати сказать, есть в моем посте, на который вы сейчас отвечаете. Там описано применение Expression.Invoke, которое и я использую для типизированного вызова методов, например. Ничего из того, что мне приходится тут самому делать: типизированного дерева, вывода типов, объявления переменных в области видимости вроде:
    M.Let(() => new List<int>()).In(
        list => 
        //используем инициализированную переменную.
    )

    там нет. В комментариях тоже, на первый взгляд, и по ссылкам нет. Может я был недостаточно внимателен?
    ... << RSDN@Home 1.2.0 alpha 4 rev. 1446>>
    'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
    Re: [ANN] Crutch: недометапрограммирование в C# 4.0
    От: hardcase Пират http://nemerle.org
    Дата: 14.07.10 09:49
    Оценка:
    Здравствуйте, Klapaucius, Вы писали:

    K>Очевидно, что писать такие вещи — не самое приятное занятие, которое можно себе найти. Поэтому, я сделал для себя встроенный язык их построения, который пока называется Crutch — название рабочее, возможно, что позже я придумаю другое, похуже. Вернее, пока это только пруф-оф-концепт.


    Квазицитаты Nemerle изобретаете?
    /* иЗвиНите зА неРовнЫй поЧерК */
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.