To define a JS class in Fable and call it later from JS, I follow fable docs#name-mangling. The documentation states that there is no name mangling for abstract memebrs, which is expected.
However, when trying out Fable 3 example#nodejs, the program went unexpected.
Clone fable3-samples/nodejs, and use code like in the fable repl:
https://fable.io/repl/#?code=LYewJgrgNgpgBAQQA5ILACgMBcCeT4AiAksElADwDkWAfABQCUcAvBnO3LFnGAIZa8WcAMo4AzlhjAAdAGEQUWAGMsASxAA7MdIDiMDTABOqpdIAyqiVVqM2HYFIBGRuAH1X0pBDEALOAA8WOw4efl5pBDAwAIxY9ABtcnlSVVgwADleBzoAIgIchhoAXWw8Qmt6JmY4JSheMTFgzhhuVQ0DQyFiUgpqSqbeRwlDXhU4B2BnTq9fOAAuOGo4AFoaOAgNVSwmsBgAM15obndPbz9A6raO09n-Jox9MCA&html=Q&css=Q
expected push method definition:
push(x) {
const __ = this;
DImpl$1__push_2B595(__.inner, x);
}
the actual:
["App.D.push2B595"](x) {
const __ = this;
DImpl$1__push_2B595(__.inner, x);
}
Fable version: dotnet fable --version
Fable: F# to JS compiler 3.0.0-nagareyama-rc-011
Thanks to the contributor! @mexx
3.0.0-nagareyama-rc-011
Yes, sorry! Fable 3 supports a Mangle attribute when you need to explicitly mangle an interface (to support overloading or implementing a method with the same name from multiple interfaces), but for abstract classes Fable 3 does the mangling by default (unless the class is decorated with [<Import>] because I was just assuming these would only be used in F#.
We can go back to Fable 2 behavior (so you need to explicitly add Mangle attribute to abstract classes) or update the docs to say now only interface members are not mangled. What do you need to with the class exported to JS? Would it be possible to use an object expression instead? Something like:
type D<'t> =
abstract push: 't -> unit
let D<'t>() =
let data = System.Collections.Generic.List<'t>()
{ new D<'t> with
member _.push x = data.Add(x) }
let d = D<int>()
but for abstract classes Fable 3 does the mangling by default (unless the class is decorated with [
] because I was just assuming these would only be used in F#.
Thanks for this instruction.
We can go back to Fable 2 behavior (so you need to explicitly add Mangle attribute to abstract classes) or update the docs to say now only interface members are not mangled.
I remembered I saw you discussed a lot about the default name mangling for abstract members, hence I'm not a fan of rollback.
There may be another option: we keep the current behavior of Fable 3, and add a [<NoMangle>] to allow users to specify JS-interoperable fields for abstract classes. No name mangling for abstract members may introduce some issues(overloading, etc.), users working with [<NoMangle>] have to understand and maintain them.
F#
[<CompiledName("D")>]
type D<'t>() =
[<NoMangle>]
abstract push : 't -> unit
default _.push x = ...
What do you need to with the class exported to JS?
I'm now teaching my friends F# and they feel like to use Leetcode to train their skills. Hence I made a template project to support writing Leetcode exercises with F#. This is achieved by Fable F#->JS transpilation(you guys are awesome).
However the Leetcode exercises sometimes require defining a JS class with designated shape. This seems to be impossible with pure F#. My current workaround is writing an emit statement manually.
Any instructions or suggestions from a Fable maintainer does help a lot!
Thanks for the explanations @thautwarm! It's great you're using F#/Fable with Leetcode. The fact that Fable doesn't attach members to classes comes from the way F# represents class members in the AST. At one point we tried to "correct" that, but it actually works quite well for tree shaking so we're considering it "a feature" now. However, it's true that for some interop scenarios we need to have classes that look exactly as in JS. Instead of the NoMangle attribute maybe we could add one like AttachMembers so Fable knows members must be attached and non-mangled for that class (overloads won't work in that case though). So you can write something like:
[<AttachMembers>]
type MinStack() =
let inner = createMinStack()
member _.push(a) = push(inner, a)
member _.pop() = pop(inner)
member _.top() = top(inner)
member _.getMin() = getMin(inner)
And Fable will translate it as:
export class MinStack{
constructor(){
this.inner = createMinStack()
}
push(a){ push(this.inner, a) }
pop(){ pop(this.inner) }
top(){ return top(this.inner) }
getMin(){ return getMin(this.inner) }
}
What do you think?
@alfonsogarciacaro
What do you think?
Perfect, indeed! Hope to see this sooner and I'll then close this issue!
The AttachMembers attribute is now included in Fable.Core 3.2.2, can you please give it a try? You will also need to update Fable to 3.0.5 in order to use it.
confirmed.
Most helpful comment
Thanks for the explanations @thautwarm! It's great you're using F#/Fable with Leetcode. The fact that Fable doesn't attach members to classes comes from the way F# represents class members in the AST. At one point we tried to "correct" that, but it actually works quite well for tree shaking so we're considering it "a feature" now. However, it's true that for some interop scenarios we need to have classes that look exactly as in JS. Instead of the
NoMangleattribute maybe we could add one likeAttachMembersso Fable knows members must be attached and non-mangled for that class (overloads won't work in that case though). So you can write something like:And Fable will translate it as:
What do you think?