Removal of ITypeResolver<>
The release notes for Thoth.Json 6.0.0 mentioned that Fable v4 might be removing ITypeResolver<>, and @MangelMaxime suggested I open an issue here to discuss it as I'm currently depending on this in my library.
I think I can work around it - I didn't realise you can inline extension members for example - but it's a handy tool when shaping a member-based API which does a bit of light type validation, and tries not to expose too many internals to inlined call sites.
Is there a reason for its removal beyond it being a maintenance burden out of curiosity?
Well, the main reason was I though only Thoth.Json used the feature 😅 It's true removing it relieves the maintenance burden a bit, but most importantly it removes a Fable artifact that's not standard F# and it's not for JS interaction either. At one point Inject was meant to provide pseudo-type classes in Fable but we decided to prevent divergence from standard F# so it remained mainly for type resolution.
Because most of the times you can resolve generic types using inlining which is a standard F# technique I was thinking to phase out ITypeResolver in Fable 4 to reduce complexity, although I acknowledge it has some benefits over inlinining.
Not sure... it shouldn't be too difficult to bring back ITypeResolver in Fable 4 to avoid breaking changes though. Do you have some code you can share to see if there are alternative solutions?
Ah interesting! Agreed that it's probably best to keep things as close to 'full' F# as possible, it's one of the great things about Fable - every now and then I'm sure something super complicated couldn't work 'in the browser', and it works first time 😄 The past year of using it has been a real joy!
Anyway, here's an example of the current usage in a constructor: https://github.com/robitar/MobF/blob/main/src/MobF/Model.fs#L36
I'm using inline in the 'builder' functions already, so I think I can just collect the type there and pass that directly as an optional param instead of the type resolver.
The other case was extending an interface with a member which attaches a field to the JS object using the type as a key, but it looks like you can inline extension members:
module MobF =
type IExtendable<'T> = interface end
[<AutoOpen>]
module Extension =
type IExtendable<'T> with
member inline _.Test() =
let t = typeof<'T>
printfn "key: %s" (t.FullName)
module MyModel =
type State = {
Name: string
}
type IComputed =
inherit MobF.IExtendable<State>
//additional computed properties..
open MobF
open MyModel
let x = { new MyModel.IComputed }
x.Test()
//prints: key: Test.MyModel.State
I haven't fully tried this out yet in my library, just playing around in the REPL. It does feel like I'm using up all my lives here though, there are also a few other concerns with inlining all the things:
- The stuff the inline functions call has to be accessible to the caller, which makes API design trickier in some cases, where you're trying to hide some of the inner workings.
- You might have to be wary of code size in chains of inlined functions which are called a lot
All that being said, I suppose its hard to justify keeping it around 'just in case', unless there's an edge case where it's still a bit of a life saver that I haven't run into yet!
Yes, you're right. Though ITypeResolver sometimes also needs inlining when the type cannot be resolved at the call site. But in any case it's true inlinining may force you to expose too many things. A pattern that I usually use to avoid this (which is also common in .NET APIs) is to have two overloads, one generic and the other accepting a System.Type argument:
class MyApi() =
member inline this.Foo<'T>() = this.Foo(typeof<'T>)
member this.Foo(t: System.Type) = ... // Do something with the type info
Just to follow up, I'm planning to do a bit of work on the library this week, so I'll see about removing the use of ITypeResolver<>, if that goes well then I'll close this off (although I don't want to hold anything up in either case!).