namespace qq
{
static class Program
{
static void Main()
{
for (var i = 1; i <= 10; ++i)
Console.WriteLine(i);
}
}
}
После компиляции убеждаемся рефлектором, что генерируемый код идентичен оригиналу.
А теперь берем Nemerle в разных вариантах:
module Program
{
Mutable() : void
{
for (mutable i = 1; i <= 10; ++i)
Console.WriteLine(i);
}
ForeachList() : void
{
foreach (i in [1..10])
Console.WriteLine(i);
}
ForeachGenList() : void
{
foreach (i in $[1..10])
Console.WriteLine(i);
}
ForeachWithList() : void
{
def l = $[1..10];
foreach (i in l)
Console.WriteLine(i);
}
Main() : void
{
Mutable();
ForeachList();
ForeachGenList();
ForeachWithList();
}
}
Результат:
Mutable
private static void Mutable()
{
int i = 0;
i = 1;
while (true)
{
if (i > 10)
{
break;
}
Console.WriteLine(i);
i++;
}
}
ForeachList
private static void ForeachList()
{
int num = 0;
bool flag = false;
int num3 = 0;
num = 1;
int num2 = 10;
flag = num <= num2;
num3 = num2 - 1;
bool flag2 = num3 > num2;
while (flag)
{
num2 = num;
if (flag2)
{
flag = num >= num3;
}
else
{
flag = num <= num3;
}
num++;
Console.WriteLine(num2);
}
}
ForeachGenList
private static void ForeachGenList()
{
int num = 0;
bool flag = false;
int num3 = 0;
num = 1;
int num2 = 10;
flag = num <= num2;
num3 = num2 - 1;
bool flag2 = num3 > num2;
while (flag)
{
num2 = num;
if (flag2)
{
flag = num >= num3;
}
else
{
flag = num <= num3;
}
num++;
Console.WriteLine(num2);
}
}
ForeachWithList
private static void ForeachWithList()
{
int num = 0;
list<int>.Cons cons = null;
list<int>.Cons cons3 = null;
bool flag = false;
int num3 = 0;
cons = null;
cons3 = null;
num = 1;
int hd = 10;
flag = num <= hd;
num3 = hd - 1;
bool flag2 = num3 > hd;
while (true)
{
if (!flag)
{
break;
}
hd = num;
if (flag2)
{
flag = num >= num3;
}
else
{
flag = num <= num3;
}
num++;
list<int>.Cons cons2 = new list<int>.Cons(hd, list<int>.Nil._N_constant_object);
if (cons == null)
{
cons = cons2;
cons3 = cons2;
}
else
{
cons3.tl = cons2;
cons3 = cons2;
}
}
list<int> tl = (cons != null) ? ((list<int>) cons) : ((list<int>) list<int>.Nil._N_constant_object);
tl = tl;
while (true)
{
if (!(tl is list<int>.Cons))
{
break;
}
num = ((list<int>.Cons) tl).hd;
tl = ((list<int>.Cons) tl).tl;
Console.WriteLine(num);
tl = tl;
}
}
Выводы:
for в Nemerle не гененерирует то же код как и for в C#.
foreach (i in [1..10]) идетичен foreach (i in $[1..10]).
Генерация списков просто отвратительна. Лучше вместо создания списка создать энумератор.
Здравствуйте, _nn_, Вы писали:
__>Выводы: __>for в Nemerle не гененерирует то же код как и for в C#.
Интересно бы измерить производительность обоих вариантов.
__>foreach (i in [1..10]) идетичен foreach (i in $[1..10]).
Более того, выражения [comprehension] и $[comprehension] идентичны и обрабатываются тем же макросом.
__>Генерация списков просто отвратительна. Лучше вместо создания списка создать энумератор.
100% согласен. Мне почему-то вообще казалось, что comprehensions генерируют ленивый код.
Здравствуйте, catbert, Вы писали:
C>Здравствуйте, _nn_, Вы писали:
__>>Выводы: __>>for в Nemerle не гененерирует то же код как и for в C#. C>Интересно бы измерить производительность обоих вариантов.
Здравствуйте, _nn_, Вы писали:
__>После компиляции убеждаемся рефлектором, что генерируемый код идентичен оригиналу.
... __>Выводы: __> for в Nemerle не гененерирует то же код как и for в C#.
Ничего страшного. Это просто рефлектор не умеет его как следует декомпилировать.
Хардкейс дал правильную ссылку. Он занимался этой проблемой и сделал вывод, что с точки зрения скорости наш for (а точнее функция с концевой рекурсией) не отличается от того что генерирует компилятор C#.
__> foreach (i in [1..10]) идетичен foreach (i in $[1..10]).
Ну, дык один код используется.
__> Генерация списков просто отвратительна. Лучше вместо создания списка создать энумератор.
Это лист компрешеншон. Потому списки и генерируются. Хочешь сделать IEnumerable компрешеншон — сделай. Но в принципе уже есть более универсальное решение ComputationExpressions с помощью которых в том числе и аналог IEnumerable компрешеншон реализуется.
В общем, я не вижу смысла в том чтобы что-то менять или доделывать. И так есть много задач которые нужно успеть решить.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
[Nemerle.Macros.Hygienic]
public ExpandRange (inrange : PExpr, acc : PExpr) : option [PExpr]
{
match (inrange) {
// ...
| <[ $pat in $[$first .. $last] ]>
| <[ $pat in [$first .. $last] ]> =>// ...
}
}
Как в выделеном фрагменте узнать если передаются литералы или переменные ?
Т.е. для код foreach (i in [1..10]) или foreach(i in [10..0]) можно смело создавать обычный for.
Было бы еще хорошо создавать for если заданы переменные, но значения их известны в макросе.
def x = 1;
def y = 2;
foreach (i in [x..y]){ }
А если не передается литерал, то тогда шаг +1 или надо указать.
Например в таком коде:
foreach (i in [(int.Parse(Console.ReadLine())) .. (int.Parse(Console.ReadLine()))]){ }
Здравствуйте, _nn_, Вы писали:
__>Здравствуйте, VladD2, Вы писали:
>> foreach (i in [1..10]) идетичен foreach (i in $[1..10]).
VD>>Ну, дык один код используется.
__>Т.к. этот код используется довольно часто, хотелось бы немного его улучшить. __>Судя по коду это вызывает ListComprehensionHelper.ExpandRange ( http://code.google.com/p/nemerle/source/browse/nemerle/trunk/macros/Util.n#520 ).
__>Есть следующий код __>
__>Как в выделеном фрагменте узнать если передаются литералы или переменные ?
Литералы описываются PExpr.Literal, в которой лежит значение этого литерала.
К сожалению сейчас компилятор (класс ConstsntFolder) не умеет сворачивать в литералы код вида:
def a = 1;
def b = 2;
def c = a + b; // компилятор сгенерирует код для честного сложения чисел
__>Как в выделеном фрагменте узнать если передаются литералы или переменные ?
Добавить анализ (паттерн-матчингом) first и last. Что-то типа
match (inrange)
{
| <[ $pat in $[$first .. $last] ]>
match (first)
{
| PExpr.Literal(...) => ...
| PExpr.Ref(name) => ...
}
__>Т.е. для код foreach (i in [1..10]) или foreach(i in [10..0]) можно смело создавать обычный for.
Надо понимать, что диапазоны вроде [1..10] и [10..0] используются в основном для баловства. В реальной жизни код выглядит как-то так:
foreach (i in [0 .. ary.Length - 1])
...
и эта оптимизация не пройдет.
__>Было бы еще хорошо создавать for если заданы переменные, но значения их известны в макросе. __>
__>def x = 1;
__>def y = 2;
__>foreach (i in [x..y]){ }
__>
А тут проблема. Никто не гарантирует что x <= y. Потому макрос и генерирует такой витиеватый код. Все что мы можем сделать — это переписать макрос так, чтобы он генерировал два варианта цикла и if (в начале) который бы вызывал бы одну или другую версию. Причем и этом может привести к проблемам, так как сейчас можно динамически менять значения выражений, скажем так:
foreach (i in [f1() .. f2()]) // где f1() и f2() меняют возвращаемое значение во время итерации.
...
[/Nemerle]
Если же ввести такую оптимизацию, то значения будут вычисляться один раз, что может быть не применимо в некоторых (хотя весьма экзотических) случаях.
__>А если не передается литерал, то тогда шаг +1 или надо указать. __>Например в таком коде: __>
foreach (i in [(int.Parse(Console.ReadLine())) .. (int.Parse(Console.ReadLine()))]){ }
Тебе не кажется, что все это смахивает на борьбу с ветряными мельницами? В реальной жизни в не тривиальных случаях проще использовать for. Случаи эти так редки, что тратить на них время просто нецелесообразно, на мой взгляд.
Нам нужно сосредоточиться на устранении багов и доведении продукта до версии 1.0. А потом заняться второй версией. Там будет много вкусного, так что мелочи вроде этих покажутся просто смешными.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
<skip/>
VD>Надо понимать, что диапазоны вроде [1..10] и [10..0] используются в основном для баловства. В реальной жизни код выглядит как-то так VD>
VD>foreach (i in [0 .. ary.Length - 1])
VD> ...
VD>
VD>и эта оптимизация не пройдет.
В данном варианте можно проверить тип ary и если это массив, то генерировать for.
__>>Было бы еще хорошо создавать for если заданы переменные, но значения их известны в макросе. __>>
__>>def x = 1;
__>>def y = 2;
__>>foreach (i in [x..y]){ }
__>>
VD>А тут проблема. Никто не гарантирует что x <= y. Потому макрос и генерирует такой витиеватый код.
Почему не гарантирует ?
Ведь известно что x = 1 , y = 2 и эти значения не изменятся.
VD>Все что мы можем сделать — это переписать макрос так, чтобы он генерировал два варианта цикла и if (в начале) который бы вызывал бы одну или другую версию. Причем и этом может привести к проблемам, так как сейчас можно динамически менять значения выражений, скажем так: VD>foreach (i in [f1() .. f2()]) // где f1() и f2() меняют возвращаемое значение во время итерации. VD> ... VD>[/Nemerle] VD>Если же ввести такую оптимизацию, то значения будут вычисляться один раз, что может быть не применимо в некоторых (хотя весьма экзотических) случаях.
Сегодня значения тоже вычисляются только один раз, так что нет никаких экзотических случаев:
module A
{
mutable _x : int = 0;
X() : int
{
Console.WriteLine("X");
++_x;
_x;
}
mutable _y : int = 3;
Y() : int
{
Console.WriteLine("Y");
--_y;
_y;
}
Main() : void
{
foreach (i in [X() .. Y()])
{
Console.WriteLine(i);
}
}
}
X
Y
1
2
VD>Тебе не кажется, что все это смахивает на борьбу с ветряными мельницами? В реальной жизни в не тривиальных случаях проще использовать for. Случаи эти так редки, что тратить на них время просто нецелесообразно, на мой взгляд.
В большинстве случаев да редки, но некоторые как [1..arr.Length-1] могли бы быть более оптимальными.
VD>Нам нужно сосредоточиться на устранении багов и доведении продукта до версии 1.0. А потом заняться второй версией. Там будет много вкусного, так что мелочи вроде этих покажутся просто смешными.
Надеюсь во второй версии это можно будет сделать еще проще
VD>>foreach (i in [0 .. ary.Length - 1])
VD>> ...
VD>>
VD>>и эта оптимизация не пройдет. __>В данном варианте можно проверить тип ary и если это массив, то генерировать for.
То есть, ты предлагаешь поверять сделать закладку на конкретный паттерн приведенный выше? В принципе это даже разуно. Но это как-то не вяжется с концепцией универсальности. Ведь на все случаи закладок не сделать.
__>Почему не гарантирует ? __>Ведь известно что x = 1 , y = 2 и эти значения не изменятся.
На самом деле о том, что есть какие-то x и y, и уж тем более о том, что у них есть какие-то конкретные значения мы знать не можем. Мы ведь работаем на стадии компиляции. Макросу может быть передан абстрактный код. Например вместо y может быть функция значение которой будет известно только в рантайме. И что при этом прикажешь делать? Вот и генерируется универсальный код который может и не супер пушистый, но зато будет корректно работтать в любом случае. В общем, поставь себя на место того кто пишет этот макрос...
__>Сегодня значения тоже вычисляются только один раз, так что нет никаких экзотических случаев:...
Вот это странно. Надо глубже разобраться в коде.
Но в любом случае, о том какие значения будут у X() и Y() мы узнаем только в рантайме, а значит мы не сможем предсказать будет ли Y() больше X() или нет. Все что мы можем сделать — это закэшиовать эти значения перед выполнением цикла и вызвать одну из версий. Но это приведет к генерации сразу двух тел циклов (дублирование кода).
VD>>Тебе не кажется, что все это смахивает на борьбу с ветряными мельницами? В реальной жизни в не тривиальных случаях проще использовать for. Случаи эти так редки, что тратить на них время просто нецелесообразно, на мой взгляд. __>В большинстве случаев да редки, но некоторые как [1..arr.Length-1] могли бы быть более оптимальными.
Откровенно говоря я скорее воспользуюсь foreach с whith и индексом. Но если хочешь сделай специальную закладку на описанный случай. Увидишь сколько сил придется потратить на казалось бы простую оптимизацию и сам решишь стоит ли овчинка выделки.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.