F# Interface with static member
Asked Answered
N

2

6

How to define a static member in an Interface?
Why it is not possible?

I want to force a F# type (class) to have a static method to create an instance of itself from a string (JSON parsing). I want this Interface example:

[<Interface>]
type public ILikeJson<'T> =
    abstract member ToJson: unit -> string         // OK
    static abstract member FromJson: string -> 'T  // <-- "static" is not valid here !

Alternatively a constructor from a string can do the work but a static method sounds better because it will have an appropriate name and I don't know how to define a constructor in the Interface too.

Negligible answered 18/4, 2020 at 15:24 Comment(1)
As of 8/9/2022, F# does support interfaces with static abstract members (RFC FS-1124). For example, this will compile in dotnet fsi: type I = static abstract i: unit -> string;; type A = A interface I with static member i () = "lofa";; (albeit, I couldn't figure out how to use it as A.i() will blow up... I also couldn't find any docs other than the linked RFC itself).Magnesium
L
5

The present CLR specification states that interfaces are only implemented for object instances and do not apply for the types themselves.

C# 8 has a proposal for defining static interface members, but it is required to provide an implementation for the static methods within the interface definition itself. So you won't be able to implement a FromJson method per class.

If you try this in F#, you'll get:

FS0868: Interfaces cannot contain definitions of concrete members. You may need to define a constructor on your type to indicate that the type is a class.

One solution to this problem is to use static type constraints. They allow you to look up the existence of a method on a type.

let inline create< ^T when ^T : (static member FromJson: string -> ^T)> json = 
     (^T : (static member FromJson: string -> ^T) (json))

This supports any type which has a static method FromJson with the string -> T signature.

type Number(num: double) =
    member _.Value = num
    static member FromJson (json) = new Number(Double.Parse(json))

and to use:

create<Number> "1.5" //creates a Number(1.5)
Librettist answered 18/4, 2020 at 16:59 Comment(5)
Thanks, I also went trought the "inline" keyword trying to find a different solution, interesting. My goal was to force a bunch of classes/types to expose the same "ToJson" and static "FromJson" methods. Kind of "forced naming convention". Wrong use of Interface, I finished to just add these methods drectly to the classes/types, any other solution seems to be too cumbersome (also because the FromJson will be used from C#). Useful example anyway, thanks.Negligible
A common pattern is to implement a Parse method which takes in a json string, which modifies the class' fields. This is similar to the approach taken by ISerializable.Librettist
I don't know the higher purpose of this - but to serialize/deserialize types in specific ways, I've always implemented a custom data contract resolver.Librettist
The higher purpose is actaully a problem with JSON serialization. Both Newtonsoft and JIL fail to serialize the type "OpenOrder" (alex75.visualstudio.com/_git/…) and I don't want to add a parameterles contructor (required by JIL).Negligible
Also JsonContructor attribute in a child element constructor (CurrencyPair type) does not work and in general I don't want to pollute a type with too many attributes. Using a custom Serializer class that manage parsing a string from/to JSON for that types was another approach I have think about. For now the two specific methods in the instance of the class (OpenOrder) will solve my problem. Thanks for the tips.Negligible
T
1

2024: static interface members arrived in F#. Explicit interface access only raises the difficulty on how to call static members at least in many scenarios:

// works: define interface with statics
type IRabbit =
    static abstract member F : unit -> int
    abstract member g: unit -> int

type BugsBunny() =

    member _.f() = 42

    // works: implement interface
    interface IRabbit with
        static member F() = 42
        member _.g() = 72

let b = BugsBunny()
b.f()

let r : IRabbit = b
r.g() // explicit interface call

// but diffulties here:

r.F() // error, calling static member on instance
(BugsBunny :?> IRabbit).F() // error, cannot cast a type
Tarsier answered 21/7 at 14:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.