Как сделать активные паттрены.
От: WolfHound  
Дата: 19.10.10 09:18
Оценка: 26 (3)
Ответ на: Re[12]: Scala / F# / Nemerle и мейнстрим
Автор: VladD2
Дата: 19.10.10

Получилось много и по делу. По этому пишу сюда.

Здравствуйте, VladD2, Вы писали:

WH>>Всетки активные паттерны это очень мощьная штука.

VD>Это очень медленная штука. Как я понимаю, они там создают копии объектов.
То что авторы F# тормоза не значит что и я тормоз
Там можно все сделать очень эффективно.

VD>У меня на этот вопрос есть свое мнение. Можно просто описывать так называемый виртуальный (не существующий в реалии) конструктор который будет сообщать компилятору какие поля или свойства использовать для эмуляции конструктора. Тогда можно будет любой объект любого класса "можно будет матчить прямо как варианты".

Тут можно сделать все еще интересней.
1)Можно в паттернах использовать функции.
Первый аргумент (в случае методов this) будет принимать тот объект который матчим.
Если есть еще аргументы их нужно будет указать явно.
Возвращаемое значение и out параметры будут возвращаемыми значениями паттерна.
Их можно связать с переменной или счемнибудь сматчить.
Тогда можно будет делать так
match (someString)
{
    | string.IsNullOrEmpty(true) =>
    | int.TryParse(value, true) =>
    | Contains("qwe", true) =>
    | Contains("asd", true) =>
    | Contains("zxc", true) =>
}

Для функций возвращающих bool имеет смысл ввести сахар который матчит возвращаемое значенис с true.
Тогда код станет таким
match (someString)
{
    | string.IsNullOrEmpty =>
    | int.TryParse(value) =>
    | Contains("qwe") =>
    | Contains("asd") =>
    | Contains("zxc") =>
}

match (someDictionary)
{
    | TryGetValue("qwe", value) =>
    | TryGetValue("asd", value) =>
    | TryGetValue("zxc", value) =>
}

Эта функциональность перекрывает твой "виртуальный конструктор" с большим запасом.
Просто пишем такую функцию:
SomeTypeVirtualConstructor(obj : SomeType, field1 : out Type1, field3 : out Type4, field6 : out Type21) : void
{
    field1 = obj.field1;
    field3 = obj.field3;
    field6 = obj.field6;
}

В качестве бонуса можно возвращать bool и проверять состояние объекта.
Можно будет для этого даже макру сделать.

2)Функции конечно хорошо но bool не всегда достаточно.
Иногда при анализе объекта может выясниться что есть более чем один вариант который матчится или нет.
Например:
Можно написать так
variant EvenOdd
{
    | Even
    | Odd
}
IsEvenOdd(value : int) : EvenOdd
{
    if (value % 2 == 0) Even() else Odd()
}
match (someInt)
{
    | IsEvenOdd(Even) =>
    | IsEvenOdd(Odd) =>
}

Но это громоздко и не эффективно.

Для этого можно ввести такую конструкцию:
pattern EvenOdd
{
    | Even
    | Odd

    match (value : int)
    {
        if (value % 2 == 0) Even() else Odd()
    }
}
match (someInt)
{
    | Even =>
    | Odd =>
}

Рассахаривается это во что-то типа:
class EvenOdd
{
    public enum Patterns
    {
        | Even
        | Odd
    }

    public Match (value : int) : Patterns
    {
        if (value % 2 == 0) Even() else Odd()
    }
}


Если у нас варианты с параметрами то получится что-то типа такого
pattern Asdasdasd
{
    | Asd { a : int; b : string; x : float; }
    | Qwe { c : string; d : int; }

    match (value : SomeType)
    {
        ...
    }
}

Рассахаривается это во что-то типа:
class Asdasdasd
{
    public enum Patterns
    {
        [ArgumentsMap("1 2 3", "a b x")]//Это метаданные для того чтобы из других сборок можно было использовать
        | Asd
        [ArgumentsMap("2 1", "c d")]
        | Qwe
    }

    public Match (value : SomeType, res1 : out int, res2 : out string, res3 : out float) : Patterns
    {
        ...
    }
}

Таким образом никаких лишних выделений памяти не происходит.
На практике поля внутри разных опций одного варианта не отличаются разнообразием типов. По этому out параметры функции будут очень хорошо склееваться.
... << RSDN@Home 1.2.0 alpha 4 rev. 1472>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 11:54
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>1)Можно в паттернах использовать функции.

WH>Первый аргумент (в случае методов this) будет принимать тот объект который матчим.
WH>Если есть еще аргументы их нужно будет указать явно.
WH>Возвращаемое значение и out параметры будут возвращаемыми значениями паттерна.
WH>Их можно связать с переменной или счемнибудь сматчить.

Вот эта функция и съест всю производительность. Меж тем весь механизм имеется. Например, следующий код работает:
using System.Console;

[Record]
class A
{
  public Prop1 : int { get; set; }
}

[Record]
class B : A
{
  public Prop2 : A { get; set; }
}

module Program
{
  Main() : void
  {
    def a : A = B(1, B(2, null));

    match (a)
    {
      | B where(Prop1=1, Prop2=B where(Prop1=2)) => WriteLine("OK");
      | _ => WriteLine("Fail");
    }
  }
}

/*
BEGIN-OUTPUT
OK
END-OUTPUT
*/


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

WH>Тогда можно будет делать так

WH>
WH>match (someString)
WH>{
WH>    | string.IsNullOrEmpty(true) =>
WH>    | int.TryParse(value, true) =>
WH>    | Contains("qwe", true) =>
WH>    | Contains("asd", true) =>
WH>    | Contains("zxc", true) =>
WH>}
WH>


Ну, и зачем это все? Нам нужно вместо синтаксиса приведенного в моем примере использовать синтаксис:

...
    def a : A = B(1, B(2, null));

    match (a)
    {
      | B(1, B(2, _)) => WriteLine("OK");
      | _ => WriteLine("Fail");
    }
...

А вот функции нам особо не нужны. Ну, или это какой-то совершенно другой случай.

Так вот для того чтобы такой код стал возможен нам нужно всего лишь научиться передавать компилятору описание отображения параметров вымышленного конструктора на члены класса. Например, это можно сделать так:
[assembly: PatternMatchingCtorMapping(B(prop1=Prop1, prop2=Prop2))]

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

WH>Для функций возвращающих bool имеет смысл ввести сахар который матчит возвращаемое значенис с true.

WH>Тогда код станет таким
WH>
WH>match (someString)
WH>{
WH>    | string.IsNullOrEmpty =>
WH>    | int.TryParse(value) =>
WH>    | Contains("qwe") =>
WH>    | Contains("asd") =>
WH>    | Contains("zxc") =>
WH>}
WH>


Это и сейчас делается с помощью защитников (when ...). Я бы не отаказался если бы защитники можно было бы внедрить в паттерн, так как это позволит делать более сложные рекрсивыне паттерны. Но подобную переделку точно нужно отложить до 2.0.

WH>Эта функциональность перекрывает твой "виртуальный конструктор" с большим запасом.


Это будет "тормоз", так как потребуются действия в рантайме, и сильное изменение компилятора.

WH>Просто пишем такую функцию:

WH>
WH>SomeTypeVirtualConstructor(obj : SomeType, field1 : out Type1, field3 : out Type4, field6 : out Type21) : void
WH>{
WH>    field1 = obj.field1;
WH>    field3 = obj.field3;
WH>    field6 = obj.field6;
WH>}
WH>

WH>В качестве бонуса можно возвращать bool и проверять состояние объекта.

Здорово! И это реализовано в Скале, если не ошибаюсь. Но все же это не то.

Если рассматривать такую функцию не как реальную функцию, а как синтаксис декларации того самого виртуального конструктора, то опять же синтаксис больно громоздкий и допускает императив. Я же предпочел бы декларативное объявление без дополнительного геморроя вроде необходиости отрытия типов в которых объявлены такие функции-паттерны.
Если же негерировать настоящую функцию, то это приведет к замедлению работы ПМ.

В общем, как дополнительно расширение для 2.0 — это приемлемо.

Но тем не менее декларативное объявление виртуального конструктора удобнее и проще в реализации. Его можно и в этой версии реализовать.

WH>2)Функции конечно хорошо но bool не всегда достаточно...


Поскипанное вообще мрак. Целый огород на ровном месте. Это я бы просто отправил в лес.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Как сделать активные паттрены.
От: catbert  
Дата: 19.10.10 16:07
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Вот эта функция и съест всю производительность. МНам нужно только придумать как описать для компилятора вымышленный конструктор для класса и связать параметры этого конструктора с членами класса (полями или свойствами).


А почему, собственно, функция съест производительность, а свойства не съедят?
Re[3]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 16:12
Оценка:
Здравствуйте, catbert, Вы писали:

C>А почему, собственно, функция съест производительность, а свойства не съедят?


А нет никаких свойств. Декларация подразумеваемого конструктора нужна только в качестве метаинформции — подсказки компилятору о том как надо разбирать паттерн. В конечном коде будет прямая работа с объектом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Как сделать активные паттрены.
От: Jack128  
Дата: 19.10.10 18:53
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, catbert, Вы писали:


C>>А почему, собственно, функция съест производительность, а свойства не съедят?


VD>А нет никаких свойств. Декларация подразумеваемого конструктора нужна только в качестве метаинформции — подсказки компилятору о том как надо разбирать паттерн. В конечном коде будет прямая работа с объектом.


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

как ты предлагаешь хотя бы самый простой пример описывать?:
let (|Even|Odd|) i = if i % 2 = 0 then Even else Odd
Re[5]: Как сделать активные паттрены.
От: catbert  
Дата: 19.10.10 18:59
Оценка:
Здравствуйте, Jack128, Вы писали:

J>как ты предлагаешь хотя бы самый простой пример описывать?:

J>
J>let (|Even|Odd|) i = if i % 2 = 0 then Even else Odd
J>


Проблема с самыми простыми примерами — они не дают представления о чем фича.
Почему не написать if (i % 2) ... else ...; ?

Вообще, не могу придумать ни одного юз-кейса к фиче, который не сводился бы разбору по свойствам.
Re[6]: Как сделать активные паттрены.
От: Jack128  
Дата: 19.10.10 19:07
Оценка:
Здравствуйте, catbert, Вы писали:

C>Здравствуйте, Jack128, Вы писали:


J>>как ты предлагаешь хотя бы самый простой пример описывать?:

J>>
J>>let (|Even|Odd|) i = if i % 2 = 0 then Even else Odd
J>>


C>Проблема с самыми простыми примерами — они не дают представления о чем фича.

C>Почему не написать if (i % 2) ... else ...; ?

C>Вообще, не могу придумать ни одного юз-кейса к фиче, который не сводился бы разбору по свойствам.


ну вот посложение пример:

match str with
| DateTime dt -> "это - дата"
| Integer i -> "это - число"
| Float d -> "это - вещ. число"
| _ -> "а хз, что это такое"
Re[6]: Как сделать активные паттрены.
От: Рысцов Денис  
Дата: 19.10.10 19:31
Оценка:
Здравствуйте, catbert, Вы писали:

C>Вообще, не могу придумать ни одного юз-кейса к фиче, который не сводился бы разбору по свойствам.


Насколько я понял одна из хороших сторон активных паттернов — декомпозиция: если match разросся до внушительных размеров из-за правил (части, которые после '|' и до '=>'), то обычными средствами сократить код не получится и получается неудобочитаемая каша. Активные паттерны позволяют внести правила в отдельные функции и, таким образом, сократить тело match.
Re[5]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 19:53
Оценка:
Здравствуйте, Jack128, Вы писали:

J>только это будут уже не активные паттеры, а просто разбор объекта на основании его свойств.


В мои планы и не входила реализация этих финтифлюшек.

J>как ты предлагаешь хотя бы самый простой пример описывать?:

J>
J>let (|Even|Odd|) i = if i % 2 = 0 then Even else Odd
J>


А зачем оно нужно то? Чем хуже:
if (i %|| 2) a else b

?

Вот паттерн-матчинг по объекта, да еще и быстрый — это фича очень полезная. А эти рюшечки... Я без них проживу и не заскучаю.

Как я уже сказал, в 2.0, если у кого-то будет желание и время можете реализовать описанную Вольфхаундом идею. А ПМ по объектам можно и в 1.0 добавить.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 19:59
Оценка: :)
Здравствуйте, catbert, Вы писали:

C>Вообще, не могу придумать ни одного юз-кейса к фиче, который не сводился бы разбору по свойствам.


На мой взгляд — это результат погони за общностью реализации. Некоторым товарищам общность важнее производительности, а иногда и здравого смыла.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 20:03
Оценка:
Здравствуйте, Jack128, Вы писали:

J>ну вот посложение пример:


J>
J>match str with
J>| DateTime dt -> "это - дата"
J>| Integer i -> "это - число"
J>| Float d -> "это - вещ. число"
J>| _ -> "а хз, что это такое"
J>


Ага. Отлично демонстрирует бесполезность данной фичи. Взять и затормозить код который можно написать так же кратко и понятно, но быстро и без выпендрежей:
match (str)
{
  | dt is DateTime => "это - дата"
  | i  is int      => "это - число"
  | d  is float    => "это - вещ. число"
  | _              => "а хз, что это такое"
}


В общем, понятно, что придумать что-то наверно можно. Только вот реального преимущество от этого будет стремиться к нулю, время будет тратиться на создание объектов, плюс потребуется тратить время на доработку компилятора.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 19.10.10 20:05
Оценка:
Здравствуйте, Рысцов Денис, Вы писали:

РД>Насколько я понял одна из хороших сторон активных паттернов — декомпозиция: если match разросся до внушительных размеров из-за правил (части, которые после '|' и до '=>'), то обычными средствами сократить код не получится и получается неудобочитаемая каша. Активные паттерны позволяют внести правила в отдельные функции и, таким образом, сократить тело match.


И заодно его затормозить, так как появляются объекты которые ранее отсутствовали, а вызов приводит к тому, что алгоритм построения дерева решений не сможет высчитать оптимальное дерево и скатится к последовательному перебору.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Как сделать активные паттрены.
От: Jack128  
Дата: 20.10.10 05:47
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, Jack128, Вы писали:


J>>ну вот посложение пример:


J>>
J>>match str with
J>>| DateTime dt -> "это - дата"
J>>| Integer i -> "это - число"
J>>| Float d -> "это - вещ. число"
J>>| _ -> "а хз, что это такое"
J>>


VD>Ага. Отлично демонстрирует бесполезность данной фичи. Взять и затормозить код который можно написать так же кратко и понятно, но быстро и без выпендрежей:

VD>
VD>match (str)
VD>{
VD>  | dt is DateTime => "это - дата"
VD>  | i  is int      => "это - число"
VD>  | d  is float    => "это - вещ. число"
VD>  | _              => "а хз, что это такое"
VD>}
VD>


Мне казалось, что название перемнной должно явно указать на её тип. str — это строка. у меня не тип тестируется, а содержимое строки.

аналог на си шарпе

if (DateTime.Parse(str, out dt)) return "это - дата"; else
if (int.TryParse(str, out i)) return "это - чисто"; else
if (float.TryParse(str, out d) return "это - вещ. число"; else
return "а хз, что это такое";
Re[6]: Как сделать активные паттрены.
От: Jack128  
Дата: 20.10.10 05:53
Оценка: +1
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, Jack128, Вы писали:


J>>только это будут уже не активные паттеры, а просто разбор объекта на основании его свойств.


VD>В мои планы и не входила реализация этих финтифлюшек.


ну так бы сразу и сказал. А то тема то назвывается активные паттрены, а не "ПМ по свойствам объекта"
Re[6]: Как сделать активные паттрены.
От: Воронков Василий Россия  
Дата: 20.10.10 06:40
Оценка:
Здравствуйте, catbert, Вы писали:

C>Проблема с самыми простыми примерами — они не дают представления о чем фича.

C>Почему не написать if (i % 2) ... else ...; ?

Фича, мне казалось, "о том", чтобы собрать в кучку весь императивный код, спрятать его в этом самом актив паттерне, а наружу выставить красивую декларативную хрень. Ведь насколько же лучше будут выглядеть эти псевдоконструкторы Event/Odd по сравнению с твоим if.
Re[8]: Как сделать активные паттрены.
От: WolfHound  
Дата: 20.10.10 08:34
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>И заодно его затормозить, так как появляются объекты которые ранее отсутствовали, а вызов приводит к тому, что алгоритм построения дерева решений не сможет высчитать оптимальное дерево и скатится к последовательному перебору.

Ты споришь со страшилками которые ты сам выдумал.
Прочитай внимательно то что я сейчас напишу.
Берем код:
pattern Asdasdasd
{
    | Asd { a : int; b : string; x : float; }
    | Qwe { c : string; d : int; }

    match (value : SomeType)//*
    {
    ...
    }
}

И вот такой матч
match (someTypeValue)
{
    | Asd (a, b, x) =>
    | Qwe (c, d) =>
}

Сколько раз управление попадет в метод отмеченый *?
Правильный ответ: один.

Сколько объектов создает этот код не считая тех что программист явно создал внутри функции?
class Asdasdasd
{
    public enum Patterns
    {
        [ArgumentsMap("1 2 3", "a b x")]//Это метаданные для того чтобы из других сборок можно было использовать
        | Asd
        [ArgumentsMap("2 1", "c d")]
        | Qwe
    }

    public Match (value : SomeType, res1 : out int, res2 : out string, res3 : out float) : Patterns
    {
        ...
    }
}

Правильный ответ: 0.

Можно ли использовать опкод switch если у нас много вариантов?
Правильный ответ: да.

Так что где ты тут нашол тормоза я не понимаю.
Хватит уже спорить со своим воображением.
... << RSDN@Home 1.2.0 alpha 4 rev. 1472>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[7]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 20.10.10 09:03
Оценка:
Здравствуйте, Jack128, Вы писали:

J>ну так бы сразу и сказал. А то тема то назвывается активные паттрены, а не "ПМ по свойствам объекта"


Тема является развитием сообщения на которое приведена ссылка в начале темы.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Как сделать активные паттрены.
От: Воронков Василий Россия  
Дата: 20.10.10 09:10
Оценка: +1
Здравствуйте, WolfHound, Вы писали:

VD>>И заодно его затормозить, так как появляются объекты которые ранее отсутствовали, а вызов приводит к тому, что алгоритм построения дерева решений не сможет высчитать оптимальное дерево и скатится к последовательному перебору.

WH>Ты споришь со страшилками которые ты сам выдумал.
WH>Прочитай внимательно то что я сейчас напишу.

ИМХО тут было бы интересней другое. Хотелось бы сначала понять, что фича в таком виде действительно полезна. Скажем, мне неясно, что мешает просто написать функцию-конвертер, превращающую исходный объект в вариант, и вызывать ее до входа в матч — при этом никаких изменений в язык вводить не требуется.
То, что это есть в F# по большому счету не есть критерий полезности фичи, в F# много весьма "академических" и чисто экспериментальных вещей. Я бы для примера смотрел на более практичный и широко используемый язык.
Re[9]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 20.10.10 09:10
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Ты споришь со страшилками которые ты сам выдумал...


Пока что я вижу сложное, не рекурсивное решине которое еще к тому же требуте каких-то рантайм-вызовов.

И это в то время когда есть простое решение не трбующее рантайм-вызовов и легко реализуемое без серьезного изменения компилятора.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Как сделать активные паттрены.
От: VladD2 Российская Империя www.nemerle.org
Дата: 20.10.10 09:35
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Берем код:

WH>
WH>pattern Asdasdasd
WH>{
WH>    | Asd { a : int; b : string; x : float; }
WH>    | Qwe { c : string; d : int; }

WH>    match (value : SomeType)//*
WH>    {
WH>    ...
WH>    }
WH>}
WH>

WH>И вот такой матч
WH>
WH>match (someTypeValue)
WH>{
WH>    | Asd (a, b, x) =>
WH>    | Qwe (c, d) =>
WH>}
WH>

WH>Сколько раз управление попадет в метод отмеченый *?
WH>Правильный ответ: один.

Тут еще вопрос — зачем мне писать кучу ненужного кода, когда задача — упростить синтаксис ПМ по объектам?
Ты не забыл какую задачу надо было решить в начале?

Представь себе здадчу — нам нужно написать LINQ-провайдер. Для этого нужно разбирать с помощью ПМ деревья объектов составляющих Expression tree. Там куча дремучих иерархий. Для каждой из них мне потребуется написать вот такой вот "pattern". Получится гора кода. Плю мы получаем кучу вызовов в рнтайме и менее эффективный код. В случае же с декларативным подходом мы просто описываем отображение конструкторов для каждого типа и приступаем к работе. При этом генерируется оптимальный код. Никаких проблем с рекурсией и т.п. В общем, простой, быстрое и декларативное решение. Единственная беда — конкретное, а не абстракное. Но беда ли это?

В ощем, если у тебя еще не пропадет желание, можешь попробовать реализовать вою задумку в 2.0. А отображение кострукторов стоит сделать уже сейчас.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.