Вывод типа результата по выражению
От: Andy77 Ниоткуда  
Дата: 08.07.09 17:57
Оценка:
Хочется добавить поддержку формул в наше spreadsheet-based приложение, как бы это поэлегантнее и с наименьшими усилиями сделать? Известны имена колонок и их типы, например:

owner — string
row — int64
col — int64
volume — double

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

volume * conc // double
owner != null && row > 5 && col < 3 // bool
owner != null && row > 5 && col < 3 ? "foo" : "bar"  // string
// int
{
   if (volume == null) return 4;
   return 3;
}


Если бы тип возвращаемого результата был заранее известен, то проблема решалась бы очень просто генерацией и компиляцией следущего кода:

public static Func<string, Int64, Int64, double, double> calc = (owner, row, col, volume) => volume * row;
public static Func<string, Int64, Int64, double, int> calc = (owner, row, col, volume) => 
{
   if (volume == null) return 4;
   return 3;
};
etc.


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

Есть ли какие-то другие идеи? Задача выглядит довольно общей, и я уверен, что кто-то уже сталкивался с ней ранее.

P.S. FW 3.5
Re: Вывод типа результата по выражению
От: Аноним  
Дата: 09.07.09 11:52
Оценка:
Здравствуйте, Andy77, Вы писали:

A>Хочется добавить поддержку формул в наше spreadsheet-based приложение, как бы это поэлегантнее и с наименьшими усилиями сделать? Известны имена колонок и их типы, например:


A>owner — string

A>row — int64
A>col — int64
A>volume — double

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

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

Мне кажется, что (в предположении, что я правильно понял задачу) лучшим решением будет следующее:

Предлагаю вместо
public static Func<string, Int64, Int64, double, double> calc = (owner, row, col, volume) => volume * row;


генерировать
public static Func<SpreadSheetRowObjectType, object> calc = (spreadSheetRow) => spreadSheetRow.volume * spreadSheetRow.row;


а пользователи в ячейках таблицы будут, соответственно, писать
spreadSheetRow.volume * spreadSheetRow.conc 
spreadSheetRow.owner != null && spreadSheetRow.row > 5 && spreadSheetRow.col < 3 
spreadSheetRow.owner != null && spreadSheetRow.row > 5 && spreadSheetRow.col < 3 ? "foo" : "bar"

{
   if (spreadSheetRow.volume == null) return 4;
   return 3;
}


Объект SpreadSheetRowObjectType должен иметь по свойству на каждую колонку, его тоже можно генерировать на лету для каждого листа.

Такое решение обладает рядом преимуществ:

1. Явно отличаются "переменные листа" от всех других имен (переменных, констант и пр.). Такой синтаксис — более прозрачен.
2. Гибкость. Наверное, со временем вы захотите использовать переменные с других листов, или из другой строки того же листа — у вас уже будет готовая синтаксическая конструкция (в дополнение к предопределенному объекту spreadSheetRow вы введете nextRow, sheets[] и т.п.
Re[2]: Вывод типа результата по выражению
От: Andy77 Ниоткуда  
Дата: 09.07.09 15:57
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Не вполне понял задачу. Если типы колонок заранее известны — это больше похоже на таблицу в базе данных, а не на электронную таблицу. Spreadsheet, как правило, подразумевает, что тип значения, хранящегося в ячейке, будет меняться. И тогда само собой напрашивается решение — использовать в качестве возвращаемого типа object.


Да, это похоже на таблицу в базе данных, но на самом деле это строго типизированный spreadsheet, что даёт кучу преимуществ.

А>Далее. Не понятно, к какому именно значению идет обращение по имени, если имя присвоено всему столбцу? Вероятно, подразумевается существование понятия "текущая строка"? Как вы тогда обращаетесь к данным, находящимся не в текущей строке?


Выражение будет вычисляться для каждой строки отдельно (функциональность будет называться "add dependent column").

А>Мне кажется, что (в предположении, что я правильно понял задачу) лучшим решением будет следующее:


А>Предлагаю вместо

А>
А>public static Func<string, Int64, Int64, double, double> calc = (owner, row, col, volume) => volume * row;
А>


А>генерировать

А>
А>public static Func<SpreadSheetRowObjectType, object> calc = (spreadSheetRow) => spreadSheetRow.volume * spreadSheetRow.row;
А>


А>а пользователи в ячейках таблицы будут, соответственно, писать

А>
А>spreadSheetRow.volume * spreadSheetRow.conc 
А>spreadSheetRow.owner != null && spreadSheetRow.row > 5 && spreadSheetRow.col < 3 
А>spreadSheetRow.owner != null && spreadSheetRow.row > 5 && spreadSheetRow.col < 3 ? "foo" : "bar"


Слишком длинно, на мой вкус.

А>Объект SpreadSheetRowObjectType должен иметь по свойству на каждую колонку, его тоже можно генерировать на лету для каждого листа.

А>Такое решение обладает рядом преимуществ:
А>1. Явно отличаются "переменные листа" от всех других имен (переменных, констант и пр.). Такой синтаксис — более прозрачен.
А>2. Гибкость. Наверное, со временем вы захотите использовать переменные с других листов, или из другой строки того же листа — у вас уже будет готовая синтаксическая конструкция (в дополнение к предопределенному объекту spreadSheetRow вы введете nextRow, sheets[] и т.п.

В 99% случаев ни переменные, ни константы, ни доступ к данным, находящимся извне данной строки не нужны; для таких случаев у нас есть полноценный скриптинг, позволяющий использовать всю объектную модель приложения. Сейчас меня интересует именно вычисления значения на основании данной строки, хочется сделать это как можно проще для пользователя.

В принципе, идея со свойствами неплоха, нужно будет её обмозговать. Действительно, почему бы не генерировать такой класс —

class RowBasedCalculator
{
   public Row _r;

   int row { get { return (int)_r["row"]; } }
   int col { get { return (int)_r["col"]; } }
   double volume { get { return (int)_r["col"]; } }
   string owner { get { return (string)_r["owner"]; } }

   // выделенный кусок - ввод пользователя
   public Func<double> calc = () => volume * row;
}


Правда, изначальный вопрос — как определять тип результата по выражению — так и остался нерешенным. Хотя перебор поддерживаемых типов колонок и попытка скомпилировать код с каждым из них, в принципе, сработает.

Спасибо за комментарии!
Re: Вывод типа результата по выражению
От: _FRED_ Черногория
Дата: 09.07.09 16:42
Оценка:
Здравствуйте, Andy77, Вы писали:

A>Хочется добавить поддержку формул в наше spreadsheet-based приложение, как бы это поэлегантнее и с наименьшими усилиями сделать?


Уверен, что написать граматику+парсер будет дороже (и по стоимости реализации и по стоимости выполнения)? На сколько "навороченным" может быть систаксис формул?
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Вывод типа результата по выражению
От: Andy77 Ниоткуда  
Дата: 09.07.09 17:57
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Уверен, что написать граматику+парсер будет дороже (и по стоимости реализации и по стоимости выполнения)? На сколько "навороченным" может быть систаксис формул?


Ага, своя грамматика и парсер у нас была в старых версиях, сейчас же хочется просто воспользоваться возможностями, предоставляемыми .NET (писать формулы на C#). Единственная проблема была в том, что неохота заставлять пользователя указывать тип результата; в идеале пользователь вводит просто "volume * concentration", а программа сама определяет, что раз volume и concentration являются double, то и тип колонки-результата тоже будет double. Этого, в принципе, можно достичь, попробовав скомпилировать формулу со всеми поддерживаемыми типами колонок, так что, в принципе, проблема решена — не самым элегантным образом, но работать будет.
Re[3]: Вывод типа результата по выражению
От: _FRED_ Черногория
Дата: 09.07.09 18:00
Оценка:
Здравствуйте, Andy77, Вы писали:

A>Единственная проблема была в том, что неохота заставлять пользователя указывать тип результата;


Хорошо, тогда можно попробовать компилить не "код на шарпе", а "код на питоне", например, то есть на языке, в котором можно пользоваться статически не типизированным кодом (DLR).
Help will always be given at Hogwarts to those who ask for it.
Re[4]: Вывод типа результата по выражению
От: Andy77 Ниоткуда  
Дата: 09.07.09 20:40
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Хорошо, тогда можно попробовать компилить не "код на шарпе", а "код на питоне", например, то есть на языке, в котором можно пользоваться статически не типизированным кодом (DLR).


А чем нам это поможет? Я как раз за строгую типизацию — тем более, что все равно нужно создавать колонку правильного типа.
Re[5]: Вывод типа результата по выражению
От: _FRED_ Черногория
Дата: 10.07.09 02:40
Оценка:
Здравствуйте, Andy77, Вы писали:

_FR>>Хорошо, тогда можно попробовать компилить не "код на шарпе", а "код на питоне", например, то есть на языке, в котором можно пользоваться статически не типизированным кодом (DLR).


A>А чем нам это поможет? Я как раз за строгую типизацию — тем более, что все равно нужно создавать колонку правильного типа.


А как вы собираетесь защититься от неправильных формул
owner != null && row > 5 && col < 3 ? (object)(volume * conc) : (object)"ку-ку";



Может просто, заключать всю формулу в Convert.ToString(…) и считать результат строкой?
Help will always be given at Hogwarts to those who ask for it.
Re[6]: Вывод типа результата по выражению
От: Andy77 Ниоткуда  
Дата: 13.07.09 14:44
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>А как вы собираетесь защититься от неправильных формул

_FR>
_FR>owner != null && row > 5 && col < 3 ? (object)(volume * conc) : (object)"ку-ку";
_FR>

_FR>

Тип object в качестве колонки нашим приложением не поддерживается, поэтому такая формула не скомпилируется

_FR>Может просто, заключать всю формулу в Convert.ToString(…) и считать результат строкой?


Нет, так не пойдет, результат нам нужен правильного типа.
Re[7]: Вывод типа результата по выражению
От: _FRED_ Черногория
Дата: 13.07.09 15:25
Оценка:
Здравствуйте, Andy77, Вы писали:

A>Тип object в качестве колонки нашим приложением не поддерживается, поэтому такая формула не скомпилируется


Как так не скомпилируется? Чем не скомпилируется? Почему не скомпилируется? Если я правильно понял workflow, то компирировать вы собираетесь компилятором шарпа, а он такой код скомпилирует. Если же у вас есть свой компилятор, то проблема оптеределия типа выражения стоять не должна

_FR>>Может просто, заключать всю формулу в Convert.ToString(…) и считать результат строкой?

A>Нет, так не пойдет, результат нам нужен правильного типа.

А что есть "правильный тип"? Как и кто это будет\должен проверять? В общем, что-то не до конца получается описана задача
Help will always be given at Hogwarts to those who ask for it.
Re: Вывод типа результата по выражению
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.07.09 03:48
Оценка:
Здравствуйте, Andy77, Вы писали:

A>Если бы тип возвращаемого результата был заранее известен, то проблема решалась бы очень просто генерацией и компиляцией следущего кода:

A>Задача выглядит довольно общей, и я уверен, что кто-то уже сталкивался с ней ранее.
Хм. А типы исходных колонок уже известны?
Тогда можно попробовать скомпилировать вот такой вот код:
class Program
{
    static void Main(string[] args)
    {
        Expression<Action<double, int>> calc = (conc, volume) => Test(conc * volume);

        var mce = calc.Body as MethodCallExpression;
        Console.WriteLine(mce.Arguments[0].Type);
    }

    private static void Test<T>(T p)
    {
        throw new NotImplementedException("This is a placeholder method only");
    }
}
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.