Redefine some top-level operators to allow for overloading #531

Open
royalstream opened this Issue Jan 27, 2017 · 4 comments

Projects

None yet

2 participants

@royalstream
royalstream commented Jan 27, 2017 edited

Many operators have standard global definitions that make them unsuitable for operator overloading, e.g. ! & @ %
Redefining them is not an option because you lose the standard behavior and that would obviously break existing code.

But sometimes they can be redefined in such a way that the standard behavior remains unaffected while allowing for operator overloading in your classes.

For example, the ! operator could be redefined like this:

type ExclamationDefaults() = class end

let inline (!) x = 
    let inline aux (a: ^a, b: ^b) = ( (^a or ^b) : (static member op_Exclamation: ^b -> _) b)
    aux (ExclamationDefaults(), x)

type ExclamationDefaults with static member op_Exclamation(x:'a ref) = x.contents

This still works in the same way for reference types, but you can overload it for a custom class:

type SomeClass() = 
    static member op_Exclamation(x:SomeClass) = ...

let r = ref 10
let a = !r   // works as usual

let c = SomeClass()
let b = !c   // calls SomeClass.op_Exclamation

Pros and Cons

The advantages of making this adjustment is that some existing operators become overloadable (operator ! in the example).

The disadvantages of making this adjustment to F# are not obvious at this moment. If all the test cases for reference types pass then we could say it has no downsides.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

Affadavit (must be submitted)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I would be willing to help implement and/or test this
  • I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@gmpl
gmpl commented Jan 28, 2017 edited

Just a note, the code you posted doesn't work, it fails with:

~vsEB00.fsx(5,5): error FS0193: Type constraint mismatch. The type 
    'a    
is not compatible with type
    'c Microsoft.FSharp.Core.ref    
The type ''a' does not match the type ''c Microsoft.FSharp.Core.ref'

But you can make it work by moving the overload to the type definition and adding another overload to make the overload resolution ambiguous:

type ExclamationDefaults() =
    static member op_Exclamation(x:'a option) = x.Value
    static member op_Exclamation(x:'a ref) = x.contents

let inline (!) x = 
    let inline aux (a: ^a, b: ^b) = ( (^a or ^b) : (static member op_Exclamation: ^b -> _) b)
    aux (ExclamationDefaults(), x)

Now your code works, you can even do something like:

let e = ! ! (Some (ref (10)))
// val e : int 10

And extend it:

type SomeWrappingClass<'a> = SomeWrappingClass of 'a with
    static member op_Exclamation(SomeWrappingClass x) = x

let f = ! ! !(Some (ref (SomeWrappingClass 10)))
// val f : int 10
@gmpl
gmpl commented Jan 28, 2017 edited

The above code is more or less the technique used by F# libraries like FSharpPlus, Fleece (replicated later in Chiron), FSharpGPU and possible others that I don't know yet.

We can either add code like that in libraries or as you suggest in F# core.

The difference is that if it's added in F# core it will be done with simulated members, as it is now with all the math operators.

In both cases the main problem is to come up with a default definition for existing types, since they can't be added later in a natural way. So doing it in libraries give us some more flexibility, since implementing changes in F# core is a complicated and long process, given the importance of the core lib.

Regarding breaking existing code, with the above code if you define a function like:

let f x = ! x

It will no longer compile:

~vsEB00.fsx(40,11): error FS0332: Could not resolve the ambiguity inherent in the use of the operator 'op_Exclamation' at or near this program point. Consider using type annotations to resolve the ambiguity.

But if it is done with simulated members, a default can be added which would solve this scenario, still I think there might be some specific scenarios, in presence of inline functions, where type annotations will be required.

So I'm not sure if it's good to add them in F# core, it sounds interesting but we'll need an extensive analysis and discussion about the simulated members for existing types.

@royalstream

I also had the "second overload" code at hand but, strangely, both compile fine for me. In any event both show the same principle.

Using simulated members sounds like the right approach but then it's no longer simple. I understand your point.

@gmpl
gmpl commented Jan 29, 2017 edited

I tried it with VS2015.
Now trying with VS2017RC3 I get:

~vsDCA7.fsx(5,5): error FS0073: internal error: Undefined or unsolved type variable: '_?42589

What's your platform? Maybe we should report the bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment