Вопрос

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

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

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

Это было полезно?

Решение

Вы не делаете ничего плохого. Во всяком случае, компилятор должен предупредить вас об использовании 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 оба раза.

Другие советы

Стив:

Эта проблема, по-видимому, была решена в 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);

В поле сообщения отображается значение 34.

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

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

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);

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

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

Все это звучит гораздо сложнее, чем есть на самом деле.
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 захваты действительно фиксируются в тот момент, когда вы назначаете анонимный метод или лямбда-выражение.В этом есть что-то неизменное...

Твое здоровье, Роберт

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

  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;

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

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)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top