For me it seems that compiler has all information for that point, but I get error message. Why?
let inline getLength< ^a when ^a : (member Length : int ) > (x: ^a) = x.Length
To call a member required by a static constraint, you need to use a more involved syntax:
let inline getLength< ^a when ^a : (member Length : int ) > (x: ^a) =
(^a : (member Length : int) x)
This is a bit ugly - which I think emphasizes the point that static member constraints are not the primary way of achieving things (often, you can use e.g. interfaces or other more usual techniques instead).
Also, if you are interested mainly in mathematical code, then you can just use standard operators and functions (together with a few primitives in LanguagePrimitives) and you won't have to invoke members explicitly.
Related
I have some production code that I'd like to simplify (especially in light of new SRTP F# behaviour).
The intention is to statically resolve a method/function based solely on the return type required.
A simplified version of it is this:
type TypeOf<'a> = T
type ZeroFactory = Z with
static member Zero(_: ZeroFactory,_ : TypeOf<int>) : _ = 0
static member Zero(_: ZeroFactory,_ : TypeOf<string>) : _ = ""
let inline inlineZero t =
((^T or ^N) : (static member Zero : ^T * TypeOf< ^N > -> ^N)
(t, T))
let inline zero () = inlineZero Z
let foo : int = zero ()
let bar : string = zero ()
this code compiles and does what I intend, but has always felt overly contrived.
I CAN write this:
let inline inlineZero2 t =
(^T : (static member Zero : ^T * TypeOf< ^N > -> ^N)
(t, T))
and to my eyes, that would seem to be good enough, but if i write:
let inline zero2 () = inlineZero2 Z
I get
Error FS0043 A unique overload for method 'Zero' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known return type: 'a
Known type parameters: < ZeroFactory , TypeOf<'a> >
Candidates:
- static member ZeroFactory.Zero: ZeroFactory * TypeOf<int> -> int
- static member ZeroFactory.Zero: ZeroFactory * TypeOf<string> ->
my hunch is that all the static type parameters in the method specification have to be mentioned on the left.
I can't remember how to get SRTPs to work any more
I want a function that takes a param and calls a specified method, easy?
let inline YearDuck< ^a when ^a : (member Year : Unit -> string)> (x : ^a) : string =
x.Year ()
but I get this
Severity Code Description Project File Line Suppression State
Error FS0072 Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
it knows x is an ^a, I've specified ^a has method Year, so whats the problem?
(actually I want Year to be a property, but I thought i would walk before running)
Try it this way instead:
let inline YearDuck x =
(^a : (member Year : unit -> string) x)
Test code:
open System
type YearTest (dt : DateTime) =
member _.Year() = string dt.Year
YearTest(DateTime.Now)
|> YearDuck
|> printfn "%A" // "2022"
I wish I could say why it works this way and not the way you tried, but I really don't know, and I don't think it's clearly documented anywhere. SRTP is just dark magic in its current form.
If you want a property instead, try this:
let inline YearDuck x =
(^a : (member get_Year : unit -> string) x)
type YearTest (dt : DateTime) =
member _.Year = string dt.Year
YearTest(DateTime.Now)
|> YearDuck
|> printfn "%A" // "2022"
consider this code
open System.Runtime.CompilerServices
[<Extension>]
type Foo =
[<Extension>]
static member inline classID (any : ^T) : ^U =
(^T : (member classID : Unit -> ^U) any)
[<Extension>]
static member inline parent (any : ^T) : ^U =
(^T : (member parent : Unit -> ^U) any)
let inline foo (tx : _) =
tx.classID() + "!" + tx.parent().classID()
the first question is, what IS the signature of foo?
my intellisense says its
'a -> string (requires member classID and member parent and member classID)
which to be fair, is at best vague as there are 2 implicit type parameters involved here and it doesnt specify which type requires which member, but that may be a tooling issue.
Is it possible to write an explicit signature for this finction e.g. like this:
let bar : 'a -> string =
fun _ -> ""
i.e.
let [var] : [signature] =
fun [param] -> [result]
what would [signature] for foo?
(there will be an additional question about extracting this signature at run time, but I'll leave that as a seperate question)
Here's how I would write foo with explicit type annotations:
let inline foo (tx : ^T
when ^T : (member classID : unit -> string)
and ^T : (member parent : unit -> ^U)
and ^U : (member classID : unit -> string)) : string =
tx.classID() + "!" + tx.parent().classID()
So, in theory, I suppose its signature is:
(^T
when ^T : (member classID : unit -> string)
and ^T : (member parent : unit -> ^U)
and ^U : (member classID : unit -> string)) -> string
It's not possible to write an explicit signature for bar because bar would have to be inline, and only functions can be inline, so bar itself is impossible.
This is why I wrote "in theory" above. You can't use foo's signature in actual F# code, so does it really have a signature at all? The root problem here is that statically resolved type parameters in F# are (IMHO) very flaky and poorly documented, so it's hard to know for sure if there is any truly correct answer to your question.
I need to compare objects of different types which I know all have properties Id and Legacy_id. Unfortunately I cannot add interface to them since the types come from database schema. I hoped the following comparer would work:
type Comparer<'T >()=
interface System.Collections.Generic.IEqualityComparer<'T> with
member this.Equals (o1:'T,o2:'T)=
o1.Legacy_id=o2.Legacy_id
member this.GetHashCode(o:'T)=
o.Id+o.Legacy_id
Also I have instantiations of the comparer type with the types. So, theoretically compiler has enough information.
But it gives an error: "Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved."
I wonder why F# fails here? Are there any real/theoretical restrictions or it is just not implemented? Such kind of inference could be very useful.
I suspect that an explanation is about F# compiler is only forward walking. The limitation C# doesn't have. That's what error message is complaining about. Is that so?
Member constraints cannot be used on types, which is why you can't do this. See here.
What you could do is create a comparer class that accepts an explicit equality-checking and hash code generation function for a specific type, e.g.
type Comparer<'T>(equalityFunc, hashFunc) =
interface System.Collections.Generic.IEqualityComparer<'T> with
member this.Equals (o1:'T,o2:'T)=
equalityFunc o1 o2
member this.GetHashCode(o:'T)=
hashFunc o
Then you could use an inline function to generate instances of the above for types that match the constraints you wish to impose:
let inline id obj =
( ^T : (member Id : int) (obj))
let inline legacyId obj =
( ^T : (member Legacy_id : int) (obj))
let inline equals o1 o2 =
id o1 = id o2
let inline hash o =
id o + legacyId o
let inline createComparer< ^T when ^T : (member Id: int) and ^T : (member Legacy_id : int) >() =
Comparer< ^T >(equals, hash) :> System.Collections.Generic.IEqualityComparer< ^T >
Say you have some type TestType that has the two required properties:
type TestType =
member this.Legacy_id = 7
member this.Id = 9
You can then do, for example, createComparer<TestType>() to generate an equality comparer appropriate to your type.
A concise way of getting a comparer for e.g. creating a HashSet is:
let inline getId o = (^T : (member Id : int) o)
let inline getLegacyId o = (^T : (member Legacy_id : int) o)
let inline legacyComparer< ^T when ^T : (member Id : int) and ^T : (member Legacy_id : int)>() =
{ new System.Collections.Generic.IEqualityComparer<'T> with
member __.GetHashCode o = getId o + getLegacyId o
member __.Equals(o1, o2) = getLegacyId o1 = getLegacyId o2 }
Type inference is about inferring a type, not proving that some generic type parameter satisfies arbitrary conditions for all instantiations (see nominal vs. structural type systems). There are many good answers on statically resolved type parameters on SO already.
type MyOpenFileDialog(dg: OpenFileDialog) =
member x.ShowDialog = dg.ShowDialog
member x.OpenFile = dg.OpenFile
type MySaveFileDialog(dg: SaveFileDialog) =
member x.ShowDialog = dg.ShowDialog
member x.OpenFile = dg.OpenFile
The following indicated 'T (^T) would escape it's scope:
type MyFileDialog<'T
when
'T : (member OpenFile:unit->Stream) and
'T : (member ShowDialog:unit->Nullable<bool>)
>(dg: 'T) =
member x.ShowDialog = dg.ShowDialog
member x.OpenFile = dg.op
Note, the constraint expression MyFileDialog<'T...>(dg: 'T)= should all be on one line. Is split for clarity (should be allowed in language I think :) )
Static member constraints let you do quite a lot of things, but they are somewhat cumbersome (they work well for generic numerical computations, but not so well as a general-purpose abstraction). So, I would be careful about using them.
Anyway, if you want to do this, then you can - to some point. As John mentioned in the comments, code that uses static member constraints need to be inline. But you can make that a static member, which captures the operations that your type uses:
type MyFileDialog
private(openFile:unit -> Stream, showDialog : unit -> DialogResult) =
member x.OpenFile() = openFile()
member x.ShowDialog() = showDialog()
static member inline Create(dg : ^T) =
MyFileDialog
( (fun () -> (^T : (member OpenFile:unit -> Stream) dg)),
(fun () -> (^T : (member ShowDialog:unit -> DialogResult) dg)) )
let d1 = MyFileDialog.Create(new OpenFileDialog())
let d2 = MyFileDialog.Create(new SaveFileDialog())