Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: Improve C# to F# interoperability #1254

Closed
eiriktsarpalis opened this issue Jun 9, 2016 · 31 comments
Closed

Discussion: Improve C# to F# interoperability #1254

eiriktsarpalis opened this issue Jun 9, 2016 · 31 comments

Comments

@eiriktsarpalis
Copy link
Member

I'm starting this issue to discuss potential changes that could improve C# to F# interoperability.
F# to C# interoperability works fine, but anyone who's ever used an F# library API from C# might have come across some of the following issues:

Optional parameters

Adding optional parameters to F# methods:

type Foo =
    static member Bar(?x : int) = ...

introduces an implicit dependency on F# option types:

using Microsoft.FSharp.Core;

Foo.Bar(x: FSharpOption<int>.None); // use default parameter
Foo.Bar(x: FSharpOption<int>.Some(42)); // override the parameter

this can be extremely off-putting for a non-F# user, and it gets worse as the number of optional parameters increases. The situation can be somewhat improved using this technique, but it still is a tedious exercise to decorate all possible optional parameters in your public api with proper attributes. Even if this is done correctly, overridden parameters would still have to be wrapped in Some.

Here are a few potential fixes:

  • Have the F# compiler automatically attach the Optional and DefaultValue attributes to every optional parameter it encounters. I believe that this does not introduce any backward compatibility issues.
  • Implement a "CompatibleOptional" attribute to be attached to arguments and/or method definitions. This would tell the F# compiler to attach the Optional and DefaultValue attributes to the parameters and potentially expose the parameters in a non-F# option representation: perhaps nullable for value types and null for classes.
  • We could perhaps sweeten the pill without making radical changes: add an FSharp.Compat namespace in FSharp.Core which implements helper methods for easily creating/wrapping values in F# option. Here's an example of how it could be done.

Lambda parameters

Public F# APIs that expose lambda parameters are virtually impossible to consume from other languages: users have to explicitly define a class that inherits FSharpFunc<_,_> which is extremely prohibitive.

Here are a few potential solutions:

  • Add an FSharp.Compat namespace in FSharp.Core which implements helper methods for wrapping common delegate types in F# functions. Here's an example of how it could be done.
  • Might be too far-fetched, but perhaps we could convince the roslyn team have lambda literals target F# funcs?

Conclusion

Please add any other interop issues that you might have come across. I firmly believe that the aforementioned issues seriously hurt widespread .NET adoption of good F# libraries. We can do much better than that.

@rojepp
Copy link
Contributor

rojepp commented Jun 9, 2016

This seems a noble cause.

Have the F# compiler automatically attach the Optional and DefaultValue attributes to every optional parameter it encounters. I believe that this does not introduce any backward compatibility issues.

Wouldn't this break existing C# to F# 'interopers'? If someone has explicitly passed in a FSharpOption<int> from C#, and these attributes are added, wouldn't that be breakage?
The function now expects (from C#) an int or nothing, but gets an option.

@eiriktsarpalis
Copy link
Member Author

@rojepp the attributes do not change the method's signature, they merely allow for optional attributes to be omitted. If somebody passed all arguments in correct order, it would still work as before.

@dsyme
Copy link
Contributor

dsyme commented Jun 10, 2016

@eiriktsarpalis I think this could made directly into an RFC - doing work in this area would be great.

There may also be a set of op_Implicit or op_Explicit conversions that can help things from C#?

@eiriktsarpalis
Copy link
Member Author

@dsyme Implicit converters are enticing, however it looks like they do not work well with lambda literals. Using the System.Converter implicit converter we see that

Converter<int, int> func = x => x + 1;
FSharpFunc<int, int> fsfunc = func;

works as expected, however

FSharpFunc<int, int> func = x => x + 1;

complains with

(1,29): error CS1660: Cannot convert lambda expression to type 'FSharpFunc<int, int>' because it is not a delegate type

@dsyme
Copy link
Contributor

dsyme commented Jun 10, 2016

@eiriktsarpalis Yeah it would be ideal if that worked. Perhaps add a Roslyn issue about it - Mads might actually do this.

BTW is that with adding actual op_Implicit definitions to the FSharpFunc type?

@dsyme
Copy link
Contributor

dsyme commented Jun 10, 2016

@eiriktsarpalis Oh yes, I forgot we had those already.

@eiriktsarpalis
Copy link
Member Author

@dsyme So you're saying we should ask the roslyn guys to enable implicit conversions for lambda literals? Or just support F# funcs?

@eiriktsarpalis
Copy link
Member Author

Btw, I just picked up from the internets that this also works but it looks a bit confusing:

FSharpFunc<int, int> func = (Converter<int,int>)(x => x + 1);

@dsyme
Copy link
Contributor

dsyme commented Jun 10, 2016

@eiriktsarpalis It would be up to the C# designers. This may be one instance of a more general problem they are encountering elsewhere (i.e. things that are lambda-like but aren't delegates).

@dsyme
Copy link
Contributor

dsyme commented Jun 10, 2016

@eiriktsarpalis Of course preparing a PR to roslyn would help enormously.

@isaacabraham
Copy link
Contributor

Maybe single method interfaces like Java 8 has ;-)


From: Don Symemailto:notifications@github.com
Sent: ‎10/‎06/‎2016 17:36
To: Microsoft/visualfsharpmailto:visualfsharp@noreply.github.com
Subject: Re: [Microsoft/visualfsharp] Discussion: Improve C# to F# interoperability (#1254)

@eiriktsarpalis It would be up to the C# designers. This may be one instance of a more general problem they are encountering elsewhere (i.e. things that are lambda-like but aren't delegates).


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
#1254 (comment)

@smoothdeveloper
Copy link
Contributor

It would be nice to have [<Extension>] added automatically to a class/module if it contains a member with it defined. And it also should define it automatically on the assembly otherwise VB.NET won't find the extension methods.

@smoothdeveloper
Copy link
Contributor

Support an [<CompiledNameConvention(CompiledNameConventions.TitleCasePublicMembers)>] attribute on module definition to make [<CompiledName("MyFunction")>] boilerplate go away while retaining C#/VB.NET friendly naming conventions and keeping the nice names in F#.

@ovatsus
Copy link

ovatsus commented Jul 21, 2016

@ovatsus
Copy link

ovatsus commented Jul 21, 2016

Having added 145! [<Optional>] attributes on FSharp.Data, I would really welcome if they were auto added :)

@dsyme
Copy link
Contributor

dsyme commented Jul 21, 2016

I'd really like to see progress on the optional attributes issue, perhaps in time for F# 4.1 (though the window is closing rapidly on that, partly because there's a considerable amount of other non-language work to get through in https://github.com/Microsoft/visualfsharp). I don't yet think I can prioritize it fo myself, though would welcome someone else fleshing out an RFC.

@eiriktsarpalis
Copy link
Member Author

eiriktsarpalis commented Jul 22, 2016

Another possibility is adding an implicit converter to FSharpOption:

type Option<'T> with
    static member op_Implicit : 'T -> 'T option

Note that implicit converters can only be placed at the type definition and do not work when extension methods. I tried it out with a custom build of FSharp.Core and here are the results:
op_implicit
Seems to be working nicely. If we additionally have the F# compiler automatically append the optional/default attributes to optional parameters, we will have practically solved the optional parameter interop issue. My understanding is that the presence of op_Implicit will not affect any existing F# code.

@dsyme
Copy link
Contributor

dsyme commented Jul 22, 2016

My understanding is that the presence of op_Implicit will not affect any existing F# code.

That's correct. It might be interesting to do this for FSharpAsync<T> (from Task) and even FSharpList<T> (from IEnumerable)

@eiriktsarpalis
Copy link
Member Author

eiriktsarpalis commented Jul 22, 2016

@dsyme I think though that those conversions might be dangerous since the first one entails potential side-effects and the latter might cause problems if the IEnumerable is large or infinite.

EDIT: Actually, scratch that first one, I read it in the opposite :)

@isaacabraham
Copy link
Contributor

Surely it would only go one way i.e. FSharpList -> IEnumerable, and not the other way around?

@smoothdeveloper
Copy link
Contributor

Pardon my ignorance, but what would it take for C# async to be able to return FSharpAsync<T/Unit> ?

@eiriktsarpalis
Copy link
Member Author

@isaacabraham they're already in a subtype relationship so this shouldn't be an issue.

@smoothdeveloper C# computation expressions :-) Actually, there's a new Roslyn feature under way that might do just that. Look up the new ValueTask feature for more details.

@eiriktsarpalis
Copy link
Member Author

@dsyme I'd love to see optional parameter enhancements find their way in F# 4.1. I could create a PR.

@isaacabraham
Copy link
Contributor

@eiriktsarpalis except as you said, IEnumerable is lazy whereas List isn't - the latter can safely go into the former but not the other way around?

@eiriktsarpalis
Copy link
Member Author

@isaacabraham It's potentially lazy. Lists, arrays and collections are instances of IEnumerable that are materialized whereas a state machine that produces infinite streams could be considered lazy.

@Jand42
Copy link

Jand42 commented Aug 15, 2016

About lambda parameters:

When adding delegate overloads to previously FSharpFunc methods by hand, sadly it can be a breaking change in existing F# code. Implicit conversion from F# lambda to delegate causes an ambigous overload. Similarly with quoted versions:

open System
open FSharp.Quotations
open System.Linq
open System.Linq.Expressions

type T() =
    member this.X(f: int -> int) = 0
    member this.X([<ReflectedDefinition>] f: Expr<int -> int>) = 1
    member this.X(f: Func<int,int>) = 2
    member this.X(f: Expression<Func<int,int>>) = 3

T().X(fun x -> x) // having any two overloads results in error

When removing the FSharpFunc overload (keeping only delegate version), that is breaking previous code that passes a function value instead of a lambda (we chose this).

If no other solution is implemented, it would be nice to at least have a priority resolution here, no errors if only one overload is found in the highest category, priority from highest to lowest:

  • F# quotation
  • Linq expression
  • F# function
  • Delegate

If there are multiple parameters, it could use the one with the highest category.
(Should I move this proposal to uservoice or separate issue?)

@forki
Copy link
Contributor

forki commented Aug 15, 2016

That compiled name attribute was also discussed on fable. I think it would be super useful. /cc @alfonsogarciacaro

@smoothdeveloper
Copy link
Contributor

This is another thing which I find upsetting trying to use option from C# need to have this defined: http://www.fssnip.net/so/title/Make-option-type-usable-in-C

All types from FSharp.Core should be reviewed to make their use easy from C#.

@ghost
Copy link

ghost commented Apr 14, 2017

would be very nice to turn FSharp.Options into nullables from a Record with compiler attributes from from F# side, or when marked with [] already convert options into nullables with default value null, so that methods/constructors have multiple overloads from c# side without many efforts from F# side, and also interface from C# point of view is nullables, options types (until c#7 or later) should be only considered a C# thing. it's quite weird to have to reference an FSharp.Core library from a c# project just to read a CLIMutable record with options type. would be much better if options are converted to nullables for interop, and then re-arrange nulls withing f# as it's much more easy.

@cartermp
Copy link
Contributor

cartermp commented Dec 6, 2017

Closing old discussion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants