Expressões lambda em Delphi Prism / Oxygene
Pergunta
Tenho feito experiências com expressões lambda em Oxygene. Muito simples expressão lambda recursiva para calcular um número de Fibonacci:
var fib : Func<int32, int32>;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
Quando eu executar esse código eu recebo um NullReferenceException. Todas as ideias sobre o que estou fazendo de errado?
Solução
Você não está fazendo nada de errado. Se qualquer coisa, o compilador deve avisá-lo sobre o uso de lorota, uma variável não atribuída, dentro do corpo do lambda.
No entanto, o compilador deve ser capturar FIB como um local, de modo que quando os concluída atribuição e o delegado é mais tarde chamado, FIB está atribuído corretamente e recursão deve funcionar como esperado.
A possível razão óbvia mais para a falha é que Prism não está capturando locais, mas os valores, o que seria extremamente intuitiva e em desacordo com qualquer outra aplicação de fechamento em línguas não-puras.
Por exemplo, tente este código em JavaScript (contrariamente à afirmação de Craig nos comentários a este post, JavaScript também captura locais, e não 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>
As caixas de alerta após clicar no botão Mostrar 1 e 2, respectivamente, enquanto seguindo a semântica Prism / Oxygene eles mostrariam 1 duas vezes.
Outras dicas
Steve:
A questão tem sido aparentemente abordados em Delphi Prism 2010. O exemplo de código a seguir funciona na versão 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);
Os shows MessageBox o valor 34.
Em resposta à pergunta de Jeroen, este código foi executado no original, compilação de lançamento oficial, 3.0.21.661.
como uma solução temporária, você pode 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 lida com a captura de variáveis ??locais diferente, então nativo Delphi ou C #. Naqueles 2 todas as referências em seu código desses locais vão ser mapeados para campos da classe gerado compilador que irá realizar o seu método anônimo. No prisma, estes locais ficar moradores comuns, mas os campos deste campos ocultos são definidos quando você instanciar o método anônimo.
Uma maneira de obter um lambda recursiva, seria a utilização de um tipo de referência para manter o lambda para você.
Tudo isso soa muito mais complicado, então ele realmente é.
2 métodos de realizar seu 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) Quando você não quer ter uma referência a esse wrapper, você pode fazê-lo assim:
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, a razão por trás Prism não seguir C # aqui, é que para threading e loop, esta reutilização de capturado vars faz para problemas de tempo de execução estranhas duras. Em Prism, capturas são realmente capturou o momento de atribuir o método anônimo ou lambda. Que tem um certo toque immuatble a ele ...
Cheers, Robert
O mesmo se aplica a métodos anônimos? Eu estou supondo que ele faz, mas não consigo descobrir a sintaxe para obter este para executar
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
Ele faz.
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;
Eu também tentou atribuir a variável:
var fib : Func<int32, int32> := nil;
fib := n -> iif(n > 1, fib(n - 1) + fib(n - 2), n);
fib(3);
Ainda sem sorte.
Por curiosidade eu tentei algo semelhante usando métodos anônimos.
Basicamente eu implementou uma busca em profundidade em um grafo acíclico direto usando um método anônimo recursiva:
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));
Este compilado, mas eu tenho o mesmo erro. NullReferenceException.
Eu também tentei re-implementar o Fibonacci como um método anônimo recursiva:
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)
novamente mesmo problema! Sempre na segunda iteração (ou seja primeira chamada recursiva)