Как передать функцию F # в другую функцию F # из приложения C #?

#f# #class-library #c#-to-f#

#f# #class-library #c #-to-f#

Вопрос:

У меня есть сборка библиотеки классов F #, которая содержит две функции:

 let add a b = a   b
  

и

 let rec aggregateList list init (op:int -> int -> int) =
    match list with
    |[] -> init
    |head::tail ->
        let rest = aggregateList tail init op
        op rest head
  

У меня есть консольное приложение C #, которое ссылается на библиотеку F # и пытается выполнить следующее:

 FSharpList<int> l = new FSharpList<int>(1, new FSharpList<int>(2, FSharpList<int>.Empty));
int result = myFsLibrary.aggregateList(l, 0, myFsLibrary.add);
  

Однако компилятор жалуется, что [myFsLibrary.add] не может быть преобразован из ‘method group’ в FSharpFunc<int, FSharpFunc<int, int>>

Ответ №1:

Другие люди предоставили ответы, но я просто вмешаюсь, чтобы сказать, что вам не следует этого делать.

Не предоставляйте списки F # C #. Не предоставляйте C # функции curried. Несоответствие импеданса видно на этой границе, поэтому лучше предоставлять общие типы фреймворка на границах межъязыковой сборки. Смотрите

http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/fsharp-component-design-guidelines.pdf

для получения дополнительных советов.

Комментарии:

1. Почему бы и нет? Это одна из рекомендаций, которую я не понимаю. Если я хочу иметь гарантии неизменяемых коллекций, не является ли F # List единственной доступной вещью, которая обеспечивает неизменность в .NET? Если кто-то использует IEnumerable, это не дает никаких гарантий неизменности чего-либо для клиента библиотеки. Я только хотел бы, чтобы FSharpList был интерфейсом…

Ответ №2:

Вы можете явно создать функцию, используя FSharpFunc делегат. В C # удобнее создать функцию, которая принимает все аргументы в виде кортежа, так что вы можете сделать это, а затем преобразовать функцию в тип curried с помощью FuncConvert . Что-то вроде:

 FuncConvert.FuncFromTupled(new FSharpFunc<Tuple<int, int>, int>(args => 
    arags.Item1   args.Item2))
  

Однако, если вам нужно вызвать какую-либо функцию F # из вашего кода на C #, рекомендуется предоставить функцию с интерфейсом, удобным для C #. В этом случае я могу использовать Func делегат, и первый аргумент должен быть IEnumerable вместо типа списка, специфичного для F #:

 module List = 
    let AggregateListFriendly inp init (op:Func<int, int, int>) =
        aggregateList (List.ofSeq inp) init (fun a b -> op.Invoke(a, b))
  

Тогда ваше приложение C # может просто использовать:

 List.AggregateListFriendly(Enumerable.Range(0, 10), 0, (a, b) => a   b));
  

Ответ №3:

Причина в том, что add экспортируется как обычный .Функция в сетевом стиле и имеет грубую подпись

 int add(int, int)
  

C # и большинство других.Сетевые языки рассматривают это как метод, который принимает 2 int параметра и возвращает единственное int значение. Однако F # не видит функции таким образом. Вместо этого он видит, add как функция принимает int и возвращает функцию, которая, в свою очередь, принимает int и возвращает int . Такой вид функций позволяет очень легко реализовать такие операции, как каррирование.

Чтобы преобразовать представление мира C # в F #, вам нужно немного поколдовать, чтобы наложить метод на себя. Я добиваюсь этого, определяя набор методов F # factory и extension, которые творят чудеса за меня. Например

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

Я могу использовать эту библиотеку, чтобы получить соответствующий тип делегата F # для add метода следующим образом

 var del = FSharpFuncUtil.Create<int, int, int>(myFsLibrary.add);