Fable
Fable copied to clipboard
[Feature Request] Type extensions in FSharp with [<AttachMembers>]
Description
We (@freymaurer) want to transpile a class with type extensions and the [<AttachMembers>]
attribute. As of now, the extensions are not transpiled as members of the classes, but standalone functions with mangled names.
Repro code
See REPL
Expected results
I would like to use the extension members with the same syntax as the main members:
myObject.Double // this
Class_MyType__MyType_get_Double(myObject) // not this
Possible Solution
Maybe it is possible to use Monkey patching
or similar techniques to mimic FSharp extension behaviour.
E.g.
// MyType.js
export class MyType {
constructor(i) {
this.i = (i | 0);
}
get I() {
const this$ = this;
return this$.i | 0;
}
}
// Extension.js
import { MyType } from "./MyType.js";
MyType.prototype.Double = function(){
return this.I * 2;
}
// Run.js
import { MyType } from "./MyType.js";
import "./Extension.js";
export const myObject = new MyType(5);
console.log(myObject.I) // 5
console.log(myObject.Double()) // 10
Rationale
This would mimic the FSharp behaviour quite nicely. Only those extensions that are specificially imported are considered (import file with extension = open module with extension). As far as I can tell, any issues with function shadowing would be the same as in FSharp.
Here are some questions that I have in mind when reading this issue.
-
Should we apply the transformation if the parent class has the
[<AttachMembers>]
or define a new attribute[<AttachOnPrototype>]
to be used on members / properties?I think, introducing a new attribute would add more complexity that needed. Like what happen if something use the attribute on a class not decorated with
AttachMembers
or if he decorated only some of the members withAttachOnPrototype
. -
Make sure that if we do the transformation, Fable use the new syntax on F# called code
-
What happens if
MyType
is defined in a NuGet package with the[<AttachMembers>]
and the user consume that library and use type extension onMyType
. -
Do we have in the AST the information that the parent class has the attribute
[<AttachMembers>]
?
@HLWeil Is there a reason why you can't define the extended member at the same time as the type ? Just trying to understand the use case and global picture here.
Hello! So this is my first time looking at AST so please excuse any wrong keywords and feel free to correct me 😅
I used the fantomas online viewer to explore the AST for one of the examples with a custom attribute. Example.
Do we have in the AST the information that the parent class has the attribute [<AttachMembers>]?
Sadly it seems like a no. Extensions
module start at line 255 in AST and MyType
extensions starts at 271, without any reference to the MyAttribute
attribute. I am unsure if we can identify parent classes via AST or if getting parent class attributes would be a feasible solution.
Make sure that if we do the transformation, Fable use the new syntax on F# called code
I am not sure what this means
What happens if MyType is defined in a NuGet package with the [<AttachMembers>] and the user consume that library and use type extension on MyType.
Fable and nuget must always include the source files so this could be possible? The question is if it should be done automatically, as we cannot use overloads with AttachMembers and this might lead to some "magic black box" for the fable users with some hard to debug errors.
Should we apply the transformation if the parent class has the [<AttachMembers>] or define a new attribute [<AttachOnPrototype>] to be used on members / properties? I think, introducing a new attribute would add more complexity that needed. Like what happen if something use the attribute on a class not decorated with AttachMembers or if he decorated only some of the members with AttachOnPrototype.
This is an interesting question. Personally i would also like to just apply it if the parent has the AttachMembers attribute but i can see issues with the missing AST information or extending classes from nuget packages, as the AttachMembers attribute comes with some limitations in what f# code you can write. So maybe a new attribute might be needed.
@HLWeil Is there a reason why you can't define the extended member at the same time as the type ? Just trying to understand the use case and global picture here.
I can give an example for Lukas. Some examples:
- In a few types we use circular references, and because the AttachMembers attribute does not work on type extensions we now have a giant file to support nice js syntax.
- We introduce a new type meant to interop with a type from a sub project and want to add type extensions to imrpove usability.
- We want to split different IO logics for one type over multiple files to increase findability/readability but add fromIO/toIO type extensions.
Most of these issues can be solved by having huge files with most code inside of it. But i don't think that is a good solution.
Edit: Sorry for the late answer
Do we have in the AST the information that the parent class has the attribute []?
Sadly it seems like a no.
Extensions
module start at line 255 in AST andMyType
extensions starts at 271, without any reference to theMyAttribute
attribute. I am unsure if we can identify parent classes via AST or if getting parent class attributes would be a feasible solution.
I know that for attributes on members we are using a patched F# compiler because some information are lost currently. I don't know if this is the same problem or not.
https://github.com/dotnet/fsharp/issues/13786
The first step if to look in the Fable AST, to check if we have the information or not. If not, then we need to check how we can get it from the F# AST if possible.
Make sure that if we do the transformation, Fable use the new syntax on F# called code
I am not sure what this means
What I meant by that was when calling the member from Fable we need to generate the correct JavaScript usage.
At the time of writing I was thinking we would need to generates myObject.protoype.Double
on the caller side. But I don't think this is the case anymore, it was just me forgetting how prototype works in JavaScript.
What happens if MyType is defined in a NuGet package with the [] and the user consume that library and use type extension on MyType.
Fable and nuget must always include the source files so this could be possible? The question is if it should be done automatically, as we cannot use overloads with AttachMembers and this might lead to some "magic black box" for the fable users with some hard to debug errors.
I don't think a lot of Fable NuGet libraries use AttachMembers
because this is specific to when you want to generate code to used from JavaScript code.
But thank you for speaking about the limitation on AttachMembers because I totally forgot how it.