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

💡 Explore using method extension to provide a direct method for a delegate creating from FunctionType #144

Open
MangelMaxime opened this issue Oct 30, 2024 · 3 comments

Comments

@MangelMaxime
Copy link
Contributor

MangelMaxime commented Oct 30, 2024

This is an idea to avoid having to use Invoke from the user POV, I am not sure if this path we want to take or not.

Context

When converting FunctionType we generates a delegate in order to keep the named arguments, but this means the user needs to Invoke the delegate as they can't be called like a normal member in F#.

export interface MyObject {
    random: (min: number, max: number) => number;
}
[<AllowNullLiteral>]
[<Interface>]
type MyObject =
    abstract member random: MyObject.random with get, set

module MyObject =

    type random =
        delegate of min: float * max: float -> float

let value = unbox<MyObject> null

value.random.Invoke(0.0, 1.0)  

Proposition

An idea was proposed to create a member as an alias to the delegate to offer the standard F# member call API:

[<AllowNullLiteral>]
[<Interface>]
type MyObject =
    abstract member random: MyObject.random with get, set

type MyObject with
    member inline this.Random(min: float, max: float) =
        this.random.Invoke(min, max)

module MyObject =

    type random =
        delegate of min: float * max: float -> float

let value = unbox<MyObject> null

value.Random(0.0, 1.0)

Impact

But this solution has 2 impacts:

  1. It means we need to change the casing of the added member (random -> Ramdom) in order to access it. We can't really suffix the low level member because we need it for the setter too.

  2. Here the example above works as is, but sometimes type are defined in nested modules. In this case, the extension method would not be visible until the user open the correct modules

This can be workaround by adding a new Extensions module decorated with [<AutoOpen>] .

module ModuleA =

    module ModuleB =

        module MyObject =

            type random =
                delegate of min: float * max: float -> float

        [<AllowNullLiteral>]
        [<Interface>]
        type MyObject =
            abstract member random: MyObject.random with get, set

[<AutoOpen>]
module Extensions =

    type ModuleA.ModuleB.MyObject with
        member inline this.Random(min: float, max: float) =
            this.random.Invoke(min, max)

let value = unbox<ModuleA.ModuleB.MyObject> null

value.Random(0.0, 1.0)

Note: Make sure to check #143 for all the ideas

@MangelMaxime
Copy link
Contributor Author

We probably also want to checks how it behaves with inheritance. TypeScript allows inheritance for class at minimal, not sure about interface.

So we need to check what happens with the method extensions or if we need to re-declare them on the inheriting type.

@roboz0r
Copy link
Contributor

roboz0r commented Oct 30, 2024

Did some testing and F# 9 allows extension methods with the same name as the underlying object's property. Both can be random instead of random & Random

@MangelMaxime
Copy link
Contributor Author

I confirm that using latest version of Ionide which supports F# 9, then using random for both the property and method extension works.

We will need to check the behavior in Fable 5.

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

No branches or pull requests

2 participants