Domanda

Ho sperimentato le espressioni Lambda in Oxygene. Espressione lambda ricorsiva molto semplice per calcolare un numero di fibonacci:

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

Quando eseguo questo codice ottengo un'eccezione nullreference. Qualche idea su cosa sto facendo di sbagliato?

È stato utile?

Soluzione

Non stai facendo nulla di male. Semmai, il compilatore dovrebbe avvertirti dell'uso di fib, una variabile non assegnata, all'interno del corpo della lambda.

Tuttavia, il compilatore dovrebbe catturare fib come posizione, in modo che quando il compito viene completato e il delegato viene successivamente invocato, fib viene assegnato correttamente e la ricorsione dovrebbe funzionare come previsto.

La ragione più ovvia possibile del fallimento è che Prism non sta catturando posizioni, ma valori, che sarebbero grossolanamente poco intuitivi e in contrasto con ogni altra implementazione di chiusura in linguaggi non puri.

Ad esempio, prova questo codice in JavaScript (contrariamente a quanto afferma Craig nei commenti a questo post, JavaScript acquisisce anche posizioni, non valori):

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

Le caselle di avviso dopo aver fatto clic sul pulsante mostrano rispettivamente 1 e 2, mentre seguendo la semantica Prisma / Oxygene mostrerebbero 1 entrambe le volte.

Altri suggerimenti

Steve:

Apparentemente il problema è stato risolto in Delphi Prism 2010. Il seguente esempio di codice funziona nella versione ufficiale.

 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 mostra il valore 34.

In risposta alla domanda di Jeroen, questo codice è stato eseguito nella versione di rilascio ufficiale originale, 3.0.21.661.

come soluzione temporanea puoi usare:

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 gestisce la cattura di variabili locali in modo diverso rispetto a Delphi nativo o C #. In quei 2 tutti i riferimenti nel tuo codice di quei locali saranno mappati ai campi della classe generata dal compilatore che conterrà il tuo metodo anonimo. Nel prisma, questi locali rimangono ordinari locali, ma i campi di questi campi nascosti vengono impostati quando si crea un'istanza del metodo anonimo.

Un modo per ottenere un lambda ricorsivo sarebbe quello di utilizzare un tipo di riferimento per contenere il lambda per te.

Tutto ciò sembra molto più complicato di quanto non sia in realtà.
2 metodi per raggiungere il tuo obiettivo:
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) Quando non si desidera avere un riferimento a questo wrapper, è possibile farlo in questo modo:


    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;

btw, la ragione per cui Prism non segue C # qui, è che per il threading e il loop, questo riutilizzo dei Vars catturati crea problemi di runtime difficili. In Prism, le acquisizioni vengono realmente catturate nel momento in cui si assegna il metodo anonimo o lambda. Che ha un certo tocco inestimabile ...

Saluti, Robert

Lo stesso vale per i metodi anonimi? Immagino che lo sia, ma non riesco a capire la sintassi per far funzionare questo

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

Modifica

Sì.

  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;

Ho anche provato ad assegnare la variabile:

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

Ancora senza fortuna.

Per curiosità ho provato qualcosa di simile usando metodi anonimi.

Fondamentalmente ho implementato una prima ricerca approfondita su un grafico aciclico diretto usando un metodo anonimo ricorsivo:

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

Questo compilato, ma ho avuto lo stesso errore. NullReferenceException.

Ho anche cercato di implementare nuovamente Fibonacci come metodo anonimo ricorsivo:

  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)

di nuovo lo stesso problema! Sempre sulla seconda iterazione (ovvero prima chiamata ricorsiva)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top