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


Answers

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 [<AllowNullLiteral>] 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 [<AllowNullLiteral>] 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.

Question

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 F# friendly, or I can make it C# friendly, but the problem is how to make it friendly for both.

Here is an example. Imagine I have the following function in F#:

let compose (f: 'T -> 'TResult) (a : 'TResult -> unit) = f >> a

This is perfectly usable from F#:

let useComposeInFsharp() =
    let composite = compose (fun item -> item.ToString) (fun item -> printfn "%A" item)
    composite "foo"
    composite "bar"

In C#, the compose function has the following signature:

FSharpFunc<T, Unit> compose<T, TResult>(FSharpFunc<T, TResult> f, FSharpFunc<TResult, Unit> a);

But of course I don't want FSharpFunc in the signature, what I want is Func and Action instead, like this:

Action<T> compose2<T, TResult>(Func<T, TResult> f, Action<TResult> a);

To achieve this, I can create compose2 function like this:

let compose2 (f: Func<'T, 'TResult>) (a : Action<'TResult> ) = 
    new Action<'T>(f.Invoke >> a.Invoke)

Now this is perfectly usable in C#:

void UseCompose2FromCs()
{
    compose2((string s) => s.ToUpper(), Console.WriteLine);
}

But now we have problem using compose2 from F#! Now I have to wrap all standard F# funs into Func and Action, like this:

let useCompose2InFsharp() =
    let f = Func<_,_>(fun item -> item.ToString())
    let a = Action<_>(fun item -> printfn "%A" item)
    let composite2 = compose2 f a

    composite2.Invoke "foo"
    composite2.Invoke "bar"

The question: How can we achieve first-class experience for the library written in F# for both F# and C# users?

So far, I couldn't come up with anything better than these two approaches:

  1. Two separate assemblies: one targeted to F# users, and the second to C# users.
  2. One assembly but different namespaces: one for F# users, and the second for C# users.

For the first approach, I would do something like this:

  1. Create F# project, call it FooBarFs and compile it into FooBarFs.dll.

    • Target the library purely to F# users.
    • Hide everything unnecessary from the .fsi files.
  2. Create another F# project, call if FooBarCs and compile it into FooFar.dll

    • Reuse the first F# project at the source level.
    • Create .fsi file which hides everything from that project.
    • Create .fsi file which exposes the library in C# way, using C# idioms for name, namespaces etc.
    • Create wrappers that delegate to the core library, doing the conversion where necessary.

I think the second approach with the namespaces can be confusing to the users, but then you have one assembly.

The question: none of these are ideal, perhaps I am missing some kind of compiler flag/switch/attribte or some kind of trick and there is a better way of doing this?

The question: has anyone else tried to achieve something similar and if so how did you do it?

EDIT: to clarify, the question is not only about functions and delegates but the overall experience of a C# user with an F# library. This includes namespaces, naming conventions, idioms and suchlike that are native to C#. Basically, a C# user shouldn't be able to detect that the library was authored in F#. And vice versa, an F# user should feel like dealing with a C# library.


EDIT 2:

I can see from the answers and comments so far that my question lacks the necessary depth, perhaps mostly due to use of only one example where interoperability issues between F# and C# arise, the issue of functions a values. I think this is the most obvious example and so this led me to use it to ask the question, but by the same token gave the impression that this is the only issue I am concerned with.

Let me provide more concrete examples. I have read through the most excellent F# Component Design Guidelines document (many thanks @gradbot for this!). The guidelines in the document, if used, do address some of the issues but not all.

The document is split it two main parts: 1) guidelines for targeting F# users; and 2) guidelines for targeting C# users. Nowhere does it even attempt to pretend that it is possible to have a uniform approach, which exactly echoes my question: we can target F#, we can target C#, but what is the practical solution for targeting both?

To remind, the goal is to have a library authored in F#, and which can be used idiomatically from both F# and C# languages.

The keyword here is idiomatic. The issue is not the general interoperability where it is just possible to use libraries in different languages.

Now to the examples, which I take straight from F# Component Design Guidelines.

  1. Modules+functions (F#) vs Namespaces+Types+functions

    • F#: Do use namespaces or modules to contain your types and modules. The idiomatic use is to place functions in modules, e.g.:

      // library
      module Foo
      let bar() = ...
      let zoo() = ...
      
      
      // Use from F#
      open Foo
      bar()
      zoo()
      
    • C#: Do use namespaces, types and members as the primary organizational structure for your components (as opposed to modules), for vanilla .NET APIs.

      This is incompatible with the F# guideline, and the example would need to be re-written to fit the C# users:

      [<AbstractClass; Sealed>]
      type Foo =
          static member bar() = ...
          static member zoo() = ...
      

      By doing so though, we break the idiomatic use from F# because we can no longer use bar and zoo without prefixing it with Foo.

  2. Use of tuples

    • F#: Do use tuples when appropriate for return values.

    • C#: Avoid using tuples as return values in vanilla .NET APIs.

  3. Async

    • F#: Do use Async for async programming at F# API boundaries.

    • C#: Do expose asynchronous operations using either the .NET asynchronous programming model (BeginFoo, EndFoo), or as methods returning .NET tasks (Task), rather than as F# Async objects.

  4. Use of Option

    • F#: Consider using option values for return types instead of raising exceptions (for F#-facing code).

    • Consider use the TryGetValue pattern instead of returning F# option values (option) in vanilla .NET APIs, and prefer method overloading to taking F# option values as arguments.

  5. Discriminated unions

    • F#: Do use discriminated unions as an alternative to class hierarchies for creating tree-structured data

    • C#: no specific guidelines for this, but the concept of discriminated unions is foreign to C#

  6. Curried functions

    • F#: curried functions are idiomatic for F#

    • C#: Do not use currying of parameters in vanilla .NET APIs.

  7. Checking for null values

    • F#: this is not idiomatic for F#

    • C#: Consider checking for null values on vanilla .NET API boundaries.

  8. Use of F# types list, map, set, etc

    • F#: it is idiomatic to use these in F#

    • C#: Consider using the .NET collection interface types IEnumerable and IDictionary for parameters and return values in vanilla .NET APIs. (i.e. do not use F# list, map, set)

  9. Function types (the obvious one)

    • F#: use of F# functions as values is idiomatic for F#, obiously

    • C#: Do use .NET delegate types in preference to F# function types in vanilla .NET APIs.

I think these should be sufficient to demonstrate the nature of my question.

Incidentally, the guidelines also have partial answer:

... a common implementation strategy when developing higher-order methods for vanilla .NET libraries is to author all the implementation using F# function types, and then create the public API using delegates as a thin façade atop the actual F# implementation.

To summarise.

There is one definite answer: there are no compiler tricks that I missed.

As per the guidelines doc, it seems that authoring for F# first and then creating a facade wrapper for .NET is the reasonable strategy.

The question then remains regarding the practical implementation of this:

  • Separate assemblies? or

  • Different namespaces?

If my interpretation is correct, Tomas suggests that using separate namespaces should be sufficient, and should be an acceptable solution.

I think I will agree with that given that the choice of namespaces is such that it does not surprise or confuse the .NET/C# users, which means that the namespace for them should probably look like it is the primary namespace for them. The F# users will have to take the burden of choosing F#-specific namespace. For example:

  • FSharp.Foo.Bar -> namespace for F# facing library

  • Foo.Bar -> namespace for .NET wrapper, idiomatic for C#




Draft F# Component Design Guidelines (August 2010)

Overview This document looks at some of the issues related to F# component design and coding. In particular, it covers:

  • Guidelines for designing “vanilla” .NET libraries for use from any .NET language.
  • Guidelines for F#-to-F# libraries and F# implementation code.
  • Suggestions on coding conventions for F# implementation code





Links