Fable icon indicating copy to clipboard operation
Fable copied to clipboard

F# 8 and static member in interface

Open lukaszkrzywizna opened this issue 10 months ago • 6 comments

Hi, @MangelMaxime I just wanted to know if there are any plans to support F# 8. Especially I'm interested in static member in interface. This would be I huge thing for the whole Fable/Feliz community since we could build strong typing for base HTMLElement/React + any other custom component with list of props as a param. Look at this example (@Zaid-Ajaj I think this could interesting for you):

open Browser.Types
open Fable.Core
open Feliz
open Fable.Core.JsInterop

// we want to distinguish between props of different components and all mkAttr() will return an extra generic arg 
// in the alternate scenario we can omit this and reuse everywhere IReactProperty to have more flexibility
[<Erase>]
type IProp<'t> = interface end

module Interop =
  let mkAttr<'comp> (name: string) (value: obj) = unbox<IProp<'comp>> (name, value)
  let createElement (name: string) (props: IProp<'comp> list) = Interop.createElement name !!props

// MOCK: Typescsript for React has generic FocusEvent<T> type, but Browser.Types don't support it
type FocusEvent<'t> = {| target: 't|}

// base props interface
[<Interface>]
type HtmlAttributes<'comp, 'element> =
  static member inline className(x: string) = Interop.mkAttr<'comp> "className" x
  static member inline onFocus(x: FocusEvent<'element> -> unit) = Interop.mkAttr<'comp> "onFocus" x 

// input props interface = Input + htmlAttributes
[<Interface>]
type input =
  inherit HtmlAttributes<input, HTMLInputElement>
  static member inline isChecked(x: bool) = Interop.mkAttr<input> "checked" x

[<Erase>]
type HTML =
  // notice: IProp<input> list - strong type check!
  static member inline input (props: IProp<input> list) = Interop.createElement "input" props
  
module Test =
  let test =
    HTML.input [
      // both HtmlAttributes and input props are available
      input.className "test"
      input.isChecked true
      // here we have a target of HTMLInputElement. no need to cast!
      input.onFocus(fun x -> printf $"{x.target.``checked``}")
    ]

Currently, we can achieve a similar effect by using type-extensions, however, this is not ideal - the user could be forced to import an extra thing to have all functions available (if there are in separated assemblies/files/namespaces).

lukaszkrzywizna avatar Oct 25 '23 08:10 lukaszkrzywizna

@lukaszkrzywizna It should already be supported in Fable v4.4.0 and above, try it.

ncave avatar Oct 25 '23 09:10 ncave

Wow! It works! I tested before with fable 4.4 but it didn't work because previous version didn't use inline. In that case, the compiler throws an error:

./StaticMember.fs(1,1): error EXCEPTION: Unexpected static interface/override call: FSharp8Lib.HtmlAttributes`2.className (L22,6-L22,28)
   at Fable.Transforms.FSharp2Fable.Util.callAttachedMember(Compiler com, FSharpOption`1 r, Type typ, CallInfo callInfo, FSharpEntity entity, FSharpMemberOrFunctionOrValue memb) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.Util.fs:line 1825
   at Fable.Transforms.FSharp2Fable.Util.makeCallWithArgInfo(IFableCompiler com, Context ctx, FSharpOption`1 r, Type typ, FSharpOption`1 callee, FSharpMemberOrFunctionOrValue memb, MemberRef membRef, CallInfo callInfo) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.Util.fs:line 2132
   at Fable.Transforms.FSharp2Fable.Compiler.transformExpr@781-41.Invoke(Context ctx) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 781
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberValue(IFableCompiler com, Context ctx, String name, FSharpMemberOrFunctionOrValue memb, FSharpExpr value) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1237
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberFunctionOrValue(IFableCompiler com, Context ctx, FSharpMemberOrFunctionOrValue memb, FSharpList`1 args, FSharpExpr body) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1379
   at Microsoft.FSharp.Primitives.Basics.List.collectToFreshConsTail[T,TResult](FSharpFunc`2 f, FSharpList`1 list, FSharpList`1 cons) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 434
   at Microsoft.FSharp.Primitives.Basics.List.collect[T,TResult](FSharpFunc`2 f, FSharpList`1 list) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 442
   at Fable.Transforms.FSharp2Fable.Compiler.transformFile(Compiler com) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1996
   at [email protected](Unit unitVar) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Cli/Pipeline.fs:line 123
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 510
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 112
./Program.fs(1,1): error EXCEPTION: Unexpected static interface/override call: FSharp8Lib.HtmlAttributes`2.className (L10,4-L10,26)
   at Fable.Transforms.FSharp2Fable.Util.callAttachedMember(Compiler com, FSharpOption`1 r, Type typ, CallInfo callInfo, FSharpEntity entity, FSharpMemberOrFunctionOrValue memb) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.Util.fs:line 1825
   at Fable.Transforms.FSharp2Fable.Util.makeCallWithArgInfo(IFableCompiler com, Context ctx, FSharpOption`1 r, Type typ, FSharpOption`1 callee, FSharpMemberOrFunctionOrValue memb, MemberRef membRef, CallInfo callInfo) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.Util.fs:line 2132
   at Fable.Transforms.FSharp2Fable.Compiler.transformExpr@781-41.Invoke(Context ctx) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 781
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/MonadicTrampoline.fs:line 8
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberValue(IFableCompiler com, Context ctx, String name, FSharpMemberOrFunctionOrValue memb, FSharpExpr value) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1237
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberFunctionOrValue(IFableCompiler com, Context ctx, FSharpMemberOrFunctionOrValue memb, FSharpList`1 args, FSharpExpr body) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1379
   at Microsoft.FSharp.Primitives.Basics.List.collectToFreshConsTail[T,TResult](FSharpFunc`2 f, FSharpList`1 list, FSharpList`1 cons) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 434
   at Microsoft.FSharp.Primitives.Basics.List.collect[T,TResult](FSharpFunc`2 f, FSharpList`1 list) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 442
   at Fable.Transforms.FSharp2Fable.Compiler.transformFile(Compiler com) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Transforms/FSharp2Fable.fs:line 1996
   at [email protected](Unit unitVar) in /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Cli/Pipeline.fs:line 123
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 510
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 112
Compilation failed

Is that expected?

lukaszkrzywizna avatar Oct 25 '23 13:10 lukaszkrzywizna

@lukaszkrzywizna Not sure, don't you need static abstract for interfaces? https://github.com/fsharp/fslang-design/blob/main/FSharp-7.0/FS-1124-interfaces-with-static-abstract-members.md#implementing-iwsams

ncave avatar Oct 25 '23 16:10 ncave

@ncave That's a separate thing. Look at the example code here. It probably won't work with fable.

lukaszkrzywizna avatar Oct 25 '23 16:10 lukaszkrzywizna

@lukaszkrzywizna You're right, looks like it's not supported in Fable yet. Here is a smaller repro that works in .NET 8, but not in Fable (yet):

[<Interface>]
type ITesting =
    static member Testing x = x

[<EntryPoint>]
let main _args =
    let a = ITesting.Testing 5
    printfn $"{a}"
    0

ncave avatar Oct 25 '23 16:10 ncave

Fortunately, the inline version works. This could be a potential workaround (for now). Thank you @ncave for your help!

lukaszkrzywizna avatar Oct 25 '23 16:10 lukaszkrzywizna