Best approach for designing F# libraries for use from both F# and C#

前端 未结 4 560
后悔当初
后悔当初 2020-12-12 08:49

I am trying to design a library in F#. The library should be friendly for use from both F# and C#.

And this is where I\'m stuck a little bit. I can make it

4条回答
  •  一生所求
    2020-12-12 09:37

    You only have to wrap function values (partially-applied functions, etc) with Func or Action, the rest are converted automatically. For example:

    type A(arg) =
      member x.Invoke(f: Func<_,_>) = f.Invoke(arg)
    
    let a = A(1)
    a.Invoke(fun i -> i + 1)
    

    So it makes sense to use Func/Action where applicable. Does this eliminate your concerns? I think your proposed solutions are overly-complicated. You can write your entire library in F# and use it pain-free from F# and C# (I do it all the time).

    Also, F# is more flexible than C# in terms of interoperability so it's generally best to follow traditional .NET style when this is a concern.

    EDIT

    The work required to make two public interfaces in separate namespaces, I think, is only warranted when they are complementary or the F# functionality is not usable from C# (such as inlined functions, which depend on F#-specific metadata).

    Taking your points in turn:

    1. Module + let bindings and constructor-less type + static members appear exactly the same in C#, so go with modules if you can. You can use CompiledNameAttribute to give members C#-friendly names.

    2. I may be wrong, but my guess is that the Component Guidelines were written prior to System.Tuple being added to the framework. (In earlier versions F# defined it's own tuple type.) It's since become more acceptable to use Tuple in a public interface for trivial types.

    3. This is where I think you have do things the C# way because F# plays well with Task but C# doesn't play well with Async. You can use async internally then call Async.StartAsTask before returning from a public method.

    4. Embrace of null may be the single biggest drawback when developing an API for use from C#. In the past, I tried all kinds of tricks to avoid considering null in internal F# code but, in the end, it was best to mark types with public constructors with [] and check args for null. It's no worse than C# in this respect.

    5. Discriminated unions are generally compiled to class hierarchies but always have a relatively friendly representation in C#. I would say, mark them with [] and use them.

    6. Curried functions produce function values, which shouldn't be used.

    7. I found it was better to embrace null than to depend on it being caught at the public interface and ignore it internally. YMMV.

    8. It makes a lot of sense to use list/map/set internally. They can all be exposed through the public interface as IEnumerable<_>. Also, seq, dict, and Seq.readonly are frequently useful.

    9. See #6.

    Which strategy you take depends on the type and size of your library but, in my experience, finding the sweet spot between F# and C# required less work—in the long run—than creating separate APIs.

提交回复
热议问题