Como você passa uma função F# para outra função F# do aplicativo C#?
-
14-11-2019 - |
Pergunta
Eu tenho um assembly de biblioteca de classes F# que contém duas funções:
let add a b = a + b
e
let rec aggregateList list init (op:int -> int -> int) =
match list with
|[] -> init
|head::tail ->
let rest = aggregateList tail init op
op rest head
Eu tenho um aplicativo de console C# que faz referência à biblioteca F# e está tentando fazer o seguinte:
FSharpList<int> l = new FSharpList<int>(1, new FSharpList<int>(2, FSharpList<int>.Empty));
int result = myFsLibrary.aggregateList(l, 0, myFsLibrary.add);
No entanto, o compilador reclama que [myFsLibrary.add] não pode ser convertido de 'grupo de métodos' para FSharpFunc<int, FSharpFunc<int, int>>
Solução
Você pode criar explicitamente uma função usando o FSharpFunc
delegar.Em C#, é mais conveniente criar uma função que receba todos os argumentos como uma tupla, então você pode fazer isso e depois converter a função em um tipo curried usando FuncConvert
.Algo como:
FuncConvert.FuncFromTupled(new FSharpFunc<Tuple<int, int>, int>(args =>
arags.Item1 + args.Item2))
No entanto, se você precisar chamar alguma função F# do seu código C#, é recomendável expor uma função com uma interface amigável ao C#.Neste caso, você pode usar Func
delegado e o primeiro argumento deve ser IEnumerable
em vez do tipo de lista específico do F#:
module List =
let AggregateListFriendly inp init (op:Func<int, int, int>) =
aggregateList (List.ofSeq inp) init (fun a b -> op.Invoke(a, b))
Então seu aplicativo C# pode apenas usar:
List.AggregateListFriendly(Enumerable.Range(0, 10), 0, (a, b) => a + b));
Outras dicas
Outras pessoas forneceram respostas, mas vou apenas intervir para dizer que você não deveria fazer isso.
Não exponha listas F# a C#.Não exponha funções com curry ao C#.A incompatibilidade de impedância é visível neste limite, portanto é melhor expor tipos de estrutura comuns em limites de assembly entre linguagens.Ver
para mais conselhos.
A razão é que add é exportado como uma função normal de estilo .Net e tem a assinatura aproximada
int add(int, int)
C# e a maioria das linguagens .Net veem isso como um método que leva 2 int
parâmetros e retorna um único int
valor.Porém, F# não vê funções dessa maneira.Em vez disso, ele vê add
como uma função assume um int
e retorna uma função que por sua vez assume um int
e retorna um int
.Essa visão das funções facilita muito a implementação de operações como curry.
Para converter da visão de mundo C# para F#, você precisa fazer um pouco de mágica para dobrar um método sobre si mesmo.Eu consigo isso definindo um conjunto de métodos de fábrica e extensão do F# para fazer a mágica para mim.Por exemplo
[<Extension>]
type public FSharpFuncUtil =
[<Extension>]
static member ToFSharpFunc<'a,'b,'c> (func:System.Func<'a,'b,'c>) =
fun x y -> func.Invoke(x,y)
static member Create<'a,'b,'c> (func:System.Func<'a,'b,'c>) =
FSharpFuncUtil.ToFSharpFunc func
Posso usar esta biblioteca para obter o tipo de delegado F# apropriado para o add
método assim
var del = FSharpFuncUtil.Create<int, int, int>(myFsLibrary.add);