Pregunta

He estado experimentando con expresiones Lambda en Oxygene. Expresión lambda recursiva muy simple para calcular un número de fibonacci:

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

Cuando ejecuto este código obtengo una excepción de referencia nula. ¿Alguna idea de lo que estoy haciendo mal?

¿Fue útil?

Solución

No estás haciendo nada malo. En todo caso, el compilador debería advertirle sobre el uso de fib, una variable no asignada, dentro del cuerpo de la lambda.

Sin embargo, el compilador debería capturar fib como una ubicación, de modo que cuando se complete la asignación y luego se invoque al delegado, fib se asigne correctamente y la recursión debería funcionar como se esperaba.

La razón más obvia posible para el fracaso es que Prism no está capturando ubicaciones, sino valores, que serían muy poco intuitivos y estarían en desacuerdo con cualquier otra implementación de cierre en lenguajes no puros.

Por ejemplo, intente este código en JavaScript (al contrario de lo que afirma Craig en los comentarios de esta publicación, JavaScript también captura ubicaciones, no valores):

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

Los cuadros de alerta después de hacer clic en el botón muestran 1 y 2 respectivamente, mientras siguen la semántica de Prism / Oxygene mostrarían 1 en ambas ocasiones.

Otros consejos

Steve:

El problema aparentemente se ha abordado en Delphi Prism 2010. El siguiente ejemplo de código funciona en el lanzamiento oficial.

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

El cuadro de mensaje muestra el valor 34.

En respuesta a la pregunta de Jeroen, este código se ejecutó en la versión de lanzamiento oficial original, 3.0.21.661.

como solución temporal puede usar:

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 maneja la captura de variables locales de manera diferente a la nativa Delphi o C #. En esos 2, todas las referencias en su código de esos locales se asignarán a los campos de la clase generada por el compilador que contendrá su método anónimo. En prisma, estos locales siguen siendo locales comunes, pero los campos de estos campos ocultos se configuran cuando se crea una instancia del método anónimo.

Una forma de obtener una lambda recursiva sería utilizar un tipo de referencia para mantener la lambda por usted.

Todo esto suena mucho más complicado de lo que realmente es.
2 métodos para lograr tu objetivo:
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) Cuando no desee tener una referencia a este contenedor, puede hacerlo así:


    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;

por cierto, la razón por la que Prism no sigue a C # aquí, es que para el enhebrado y el bucle, esta reutilización de vars capturados crea problemas de tiempo de ejecución extraños y difíciles. En Prism, las capturas se capturan realmente en el momento en que asigna el método anónimo o lambda. Que tiene un cierto toque inmutable ...

Saludos, Robert

¿Se aplica lo mismo a los métodos anónimos? Supongo que sí, pero no puedo entender la sintaxis para que esto se ejecute

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

Editar

Lo hace.

  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;

También intenté asignar la variable:

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

Todavía sin suerte.

Por curiosidad probé algo similar usando métodos anónimos.

Básicamente implementé una primera búsqueda en profundidad en un gráfico acíclico directo usando un método anónimo recursivo:

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

Esto se compiló, pero recibí el mismo error. NullReferenceException.

También intenté volver a implementar el Fibonacci como método anónimo recursivo:

  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)

otra vez mismo problema! Siempre en la segunda iteración (es decir, la primera llamada recursiva)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top