Лямбда-выражения в Delphi Prism / Oxygene - PullRequest
2 голосов
/ 07 ноября 2008

Я экспериментировал с лямбда-выражениями в Oxygene. Очень простое рекурсивное лямбда-выражение для вычисления числа Фибоначчи:

var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);

Когда я запускаю этот код, я получаю исключение nullreference. Есть идеи, что я делаю не так?

Ответы [ 6 ]

5 голосов
/ 07 ноября 2008

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

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

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

Например, попробуйте этот код в JavaScript (вопреки утверждению Крейга в комментариях к этому сообщению, JavaScript также фиксирует местоположения, а не значения):

<html>
<head>
<script language='javascript'>
function main()
{
    var x = 1;
    var f = function() { return x; };
    alert(f());
    x = 2;
    alert(f());
}
</script>
</head>
<body>
<input type=button onclick="javascript:main()"></input>
</body>
</html>

В окнах предупреждений после нажатия на кнопку отображаются соответственно 1 и 2, а в соответствии с семантикой Prism / Oxygene они будут показывать 1 оба раза.

4 голосов
/ 29 сентября 2009

Стив:

Эта проблема, по-видимому, была устранена в Delphi Prism 2010. Следующий пример кода работает в официальном выпуске.

 var fib : Func<int32, int32>;
 fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
 var i := fib(9); //1,1,2,3,5,8,13,21,34
 MessageBox.Show(i.ToString);

MessageBox показывает значение 34.

В ответ на вопрос Jeroen этот код был запущен в оригинальной официальной сборке релиза, 3.0.21.661.

1 голос
/ 07 ноября 2008

В качестве временного обходного пути вы можете использовать:

var f := new class(f: Tfib := nil);
f.f := method(n : Int32): Int32
begin
  if n > 1 then  
    Result := f.f(n-1) + f.f(n-2)
  else
    Result := n;
end;
f.f(3);
0 голосов
/ 07 ноября 2008

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

  var f : Tfib;
  f := method(n : Int32): Int32
  begin
    if n > 1 then  
      Result := f(n-1) + f(n-2)
    else
      Result := n;
  end;

Редактировать

Да.

  var f := new class(call : TFib := nil);
  f.call := method(n : Int32): Int32
  begin
    if n > 1 then  
      Result := f.call(n-1) + f.call(n-2)
    else
      Result := n;
  end;
0 голосов
/ 07 ноября 2008

Prism обрабатывает захват локальных переменных иначе, чем нативный Delphi или C #. В этих 2 все ссылки в вашем коде этих локальных объектов будут сопоставлены с полями сгенерированного компилятором класса, который будет содержать ваш анонимный метод. В призме эти локальные объекты остаются обычными локальными, но поля этих скрытых полей устанавливаются при создании экземпляра анонимного метода.

Один из способов получить рекурсивную лямбду - это использовать ссылочный тип для хранения лямбды.

Все это звучит намного сложнее, чем на самом деле.
2 способа достижения цели:
1)


    var fib := new class(Call : Func<Integer, Integer> := nil);  
    fib.Call := n -> iif(n > 1, fib.Call(n - 1) + fib.Call(n - 2), n);  
    var x := fib.Call(3);  

2) Если вы не хотите иметь ссылку на эту оболочку, вы можете сделать это так:


    var fib : Func;  
    with fibWrapper := new class(Call : Func<Integer, Integer> := nil) do  
    begin  
        fibWrapper.Call := n -> iif(n > 1, fibWrapper.Call(n - 1) + fibWrapper.Call(n - 2), n);  
        fib := fibWrapper.Call;  
    end;

Кстати, причина, по которой Prism здесь не следует C #, заключается в том, что для многопоточности и зацикливания повторное использование захваченных переменных создает серьезные странные проблемы во время выполнения. В Prism захваты действительно захватываются, когда вы назначаете анонимный метод или лямбду. Который имеет определенное неизменное прикосновение к нему ...

Ура, Роберт

0 голосов
/ 07 ноября 2008

Я также попытался присвоить переменную:

var fib : Func<int32, int32> := nil;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);

Все еще без удачи.

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

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

var dfs : dfsmethod;
dfs := method(Vertex : IVertex)
begin
  var IsDone : Boolean;
  Visited[Vertex.Key] := True;
  aMethod(Vertex.Key, Vertex.Weight, var IsDone);  //PreVisit
  if IsDone then Exit;
  for each Successor in Vertex.Successors do
    if not Visited[Successor.Key] then
      dfs(Successor);
end;
dfs(InternalGetVertex(aStart));

Это скомпилировано, но я получил ту же ошибку. NullReferenceException.

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

  var f : Tfib;
  f := method(n : Int32): Int32
  begin
    if n > 1 then  
      Result := f(n-1) + f(n-2)
    else
      Result := n;
  end;
  f(3)

опять та же проблема! Всегда на второй итерации (т.е. первом рекурсивном вызове)

...