This question already has answers here:
How do I write this member constraint in F#?
(1 answer)
F# Error compiling
(1 answer)
Closed 7 years ago.
I should probably go to sleep... but all it takes is to make this dang member constraint to be accepted...
I think I applied the member function constraints correctly and in abundance and still I get:
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.
I am even tempted to think that I applied it in too many spots...
// ... inside a class type
member public this.ToJSON() =
let inline arrayToString (arr : ^T [] when ^T : (member ToJSON : unit -> string)) =
let toString x = x.ToString()
let len = Array.length arr
let inline folder (sb : System.Text.StringBuilder,i) (element : ^T when ^T : (member ToJSON : unit -> string)) =
sb.Append(element.ToJSON() + if i < len then "," else ""), i+1
//--------------------^^^^^^^^^^^^^^^ complains here!
arr
|> Array.fold folder (new System.Text.StringBuilder(),1)
|> fst
|> toString
let inline arrayToString2 (arr : ^T [][] when ^T : (member ToJSON : unit -> string)) =
let toString x = x.ToString()
let len = Array.length arr
arr
|> Array.fold
( fun (sb : System.Text.StringBuilder,i) a ->
sb.Append("[" + (arrayToString a) + if i < len then "]," else "]"),i+1
) (new System.Text.StringBuilder(),1)
|> fst
|> toString
sprintf
"""{"SymbolTable":[%s],"ProductionTable":[%s],"ParserTables":[%s],"InitialTable":%d,"InitialState":%d}"""
(arrayToString this.SymbolTable)
(arrayToString this.ProductionTable)
(arrayToString2 this.ParserTables)
(this.InitialTable)
(this.InitialState)
Can anyone tell me how to do it right? The arrayToString is supposed to be generic over the array types contained as members in my class type...
F# 3.1 btw. On .NET (not on mono or something). In VS 2013 Community. Portable Library project.
Thanks, in advance!
Edit:
Thanks to ildjarns tip in the comment section, the cryptographically foolproof syntax to get it right looks like this:
let inline folder (sb : System.Text.StringBuilder,i) (element : ^T when ^T : (member ToJSON : unit -> string)) =
sb.Append((^T : (member ToJSON : unit -> string) element) + if i < len then "," else ""), i+1
Related
I'm trying to create an infix operator to make System.Text.StringBuilder slightly easier to use.
I have the following inline function using statically resolved type parameters:
let inline append value builder = (^T : (member Append : _ -> ^T) (builder, value))
which handles all the overloads of StringBuilder.Append. This works fine as a regular function:
StringBuilder()
|> append 1
|> append " hello "
|> append 2m
|> string
// Result is: '1 hello 2'
When I try to use define an infix operator like so:
let inline (<<) builder value = append value builder
it works when all parameters in a chain are of the same type:
StringBuilder()
<< 1
<< 2
<< 3
|> string
// Result is: '123'
but fails with parameters of different types:
StringBuilder()
<< 1
<< "2" // <- Syntax error, expected type 'int' but got 'string'.
<< 123m // <- Syntax error, expected type 'int' but got 'decimal'.
The expected type seems to be inferred by the first usage of the << operator in the chain. I would assume that each << would be applied separately.
If the chain is split into separate steps the compiler is happy again:
let b0 = StringBuilder()
let b1 = b0 << 1
let b2 = b1 << "2"
let b3 = b2 << 123m
b3 |> string
// Result is: '12123'
Is it possible to create such an operator?
Edit
A hacky "solution" seems to be to pipe intermediate results through the identity function whenever the type of the argument changes:
StringBuilder()
<< 1 // No piping needed here due to same type (int)
<< 2 |> id
<< "A" |> id
<< 123m
|> string
// Result is: '12A123'
This is quite odd - and I would say it may be a compiler bug. The fact that you can fix this by splitting the pipeline into separate let bindings is what makes me think this is a bug. In fact:
// The following does not work
(StringBuilder() << "A") << 1
// But the following does work
(let x = StringBuilder() << "A" in x) << 1
I think the compiler is somehow not able to figure out that the result is again just StringBuilder, which can have other Append members. A very hacky version of your operator would be:
let inline (<<) builder value =
append value builder |> unbox<StringBuilder>
This performs an unsafe cast to StringBuilder so that the return type is always StringBuilder. This makes your code work (and it chooses the right Append overlaods), but it also lets you write code that uses Append on non-StringBuilder things and this code will fail at runtime.
I can possibly add a data point to this mystery, albeit I am
only able to surmise that this behavior might have something to do with the particular overload for value: obj. If I uncomment that line and try to run it, the compiler says:
Script1.fsx(21,14): error FS0001: Type mismatch. Expecting a
'a -> 'c
but given a
System.Text.StringBuilder -> System.Text.StringBuilder
The type ''a' does not match the type 'System.Text.StringBuilder'
This happened while trying to map the various overloads of System.Text.StringBuilder to statically resolved type parameters on an operator. This seems to be a fairly standard technique in similar cases, since it will produce compile-time errors for unsupported types.
open System.Text
type Foo = Foo with
static member ($) (Foo, x : bool) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : byte) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : char[]) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : char) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : decimal) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : float) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : float32) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : int16) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : int32) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : int64) = fun (b : StringBuilder) -> b.Append x
// static member ($) (Foo, x : obj) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : sbyte) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : string) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : uint16) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : uint32) = fun (b : StringBuilder) -> b.Append x
static member ($) (Foo, x : uint64) = fun (b : StringBuilder) -> b.Append x
let inline (<.<) b a =
(Foo $ a) b
// val inline ( <.< ) :
// b:'a -> a: ^b -> 'c
// when (Foo or ^b) : (static member ( $ ) : Foo * ^b -> 'a -> 'c)
let res =
StringBuilder()
<.< 1
<.< 2
<.< 3
<.< "af"
<.< 2.32m
|> string
// val res : string = "123af2,32"
I think there is a solution in the following:
let inline append value builder = (^T: (member Append: _ -> ^S) (builder, value))
let inline (<<) builder value = append value builder
let builder = new StringBuilder()
let result =
builder
<< 1
<< " hello "
<< 2m
|> string
printfn "%s" result
As seen the return value from Append is set to ^S instead of ^T and ^S is resolved to require Append as member.
It will find the correct overload for Append which you can see, it you use the following mockup of a StringBuilder:
type MyStringBuilder() =
member this.Append(value: int) =
printfn "int: %d" value;
this
member this.Append(value: string) =
printfn "string: %s" value;
this
member this.Append(value: decimal) =
printfn "decimal: %f" value;
this
member this.Append(value: obj) =
printfn "obj: %A" value
this
let builder = new MyStringBuilder()
let result =
builder
<< 1
<< " hello "
<< 2m
|> string
Warning: There is though a peculiarity in the following setup:
let builder = StringBuilder()
let result =
builder
<< 1
<< " hello "
<< 2m
<< box " XX "
|> string
when compiling this with the extra << box " XX " the compiler gets lost somewhere in the process and is rather long time to compile (only when using StringBuilder() - not MyStringBuilder()) and the intellisense and coloring etc. seems to disappear - in my Visual Studio 2019 as least.
At first, I thought it has something to do with the box value, but it rather seems to have something to do with the number of chained values???
The below works:
let inline (<<) (builder:StringBuilder) (value:'T) = builder.Append(value)
let x = StringBuilder()
<< 1
<< 2
<< 3
<< "af"
<< 2.32m
|> string
I think you need to be specific about the StringBuilder type otherwise it will pick only one of the overloads.
Is it possible to do an F# type test pattern with a member constraint?
Such as:
let f x =
match x with
| :? (^T when ^T : (static member IsInfinity : ^T -> bool)) as z -> Some z
| _ -> None
or
let g x =
match x with
| (z : ^T when ^T : (static member IsInfinity : ^T -> bool)) -> Some z
| _ -> None
Neither which work.
You cannot do this, as Petr said, statically resolved type parameters are resolved at compile time. They're actually a feature of the F# compiler rather than being a .NET feature, hence why this kind of information isn't available at runtime.
If you wish to check this at runtime, you could use reflection.
let hasIsInfinity (x : 'a) =
typeof<'a>.GetMethod("IsInfinity", [|typeof<'a>|])
|> Option.ofObj
|> Option.exists (fun mi -> mi.ReturnType = typeof<bool> && mi.IsStatic)
This will check for a static method called IsInfinity with type sig: 'a -> bool
Please explain the magic behind drawShape function. 1) Why it works at all -- I mean how it calls the Draw member, 2) why it needs to be inline?
type Triangle() =
member x.Draw() = printfn "Drawing triangle"
type Rectangle() =
member x.Draw() = printfn "Drawing rectangle"
let inline drawShape (shape : ^a) =
(^a : (member Draw : unit->unit) shape)
let triangle = Triangle()
let rect = Rectangle()
drawShape triangle
drawShape rect
And the next issue is -- is it possible to write drawShape function using parameter type annotation like below? I found that it has exactly the same signature as the first one, but I'm unable to complete the body.
let inline drawShape2 (shape : ^a when ^a : (member Draw : unit->unit)) =
...
Thanks in advance.
This Voodoo-looking syntax is called "statically resolved type parameter". The idea is to ask the compiler to check that the type passed as generic argument has certain members on it (in your example - Draw).
Since CLR does not support such checks, they have to be done at compile time, which the F# compiler is happy to do for you, but it also comes with a price: because there is no CLR support, there is no way to compile such function to IL, which means that it has to be "duplicated" every time it's used with a new generic argument (this technique is also sometimes known as "monomorphisation"), and that's what the inline keyword is for.
As for the calling syntax: for some reason, just declaring the constraint on the parameter itself doesn't cut it. You need to declare it every time you actually reference the member:
// Error: "x" is unknown
let inline f (a: ^a when ^a: (member x: unit -> string)) = a.x()
// Compiles fine
let inline f a = (^a: (member x: unit -> string)( a ))
// Have to jump through the same hoop for every call
let inline f (a: ^a) (b: ^a) =
let x = (^a: (member x: unit -> string)( a ))
let y = (^a: (member x: unit -> string)( b ))
x+y
// But can wrap it up if it becomes too messy
let inline f (a: ^a) (b: ^a) =
let callX t = (^a: (member x: unit -> string) t)
(callX a) + (callX b)
// This constraint also implicitly carries over to anybody calling your function:
> let inline g x y = (f x y) + (f y x)
val inline g : x: ^a -> y: ^a -> string when ^a : (member x : ^a -> string)
// But only if those functions are also inline:
> let g x y = (f x y) + (f y x)
Script.fsx(49,14): error FS0332: Could not resolve the ambiguity inherent in the use of the operator 'x' at or near this program point. Consider using type annotations to resolve the ambiguity.
The MSDN doc on Type Extensions states that "Before F# 3.1, the F# compiler didn't support the use of C#-style extension methods with a generic type variable, array type, tuple type, or an F# function type as the “this” parameter." (http://msdn.microsoft.com/en-us/library/dd233211.aspx)
How can be a Type Extension used on F# function type? In what situations would such a feature be useful?
Here is how you can do it:
[<Extension>]
type FunctionExtension() =
[<Extension>]
static member inline Twice(f: 'a -> 'a, x: 'a) = f (f x)
// Example use
let increment x = x + 1
let y = increment.Twice 5 // val y : int = 7
Now for "In what situations would such a feature be useful?", I honestly don't know and I think it's probably a bad idea to ever do this. Calling methods on a function feels way too JavaScript-ey, not idiomatic at all in F#.
You may simulate the . notation for extension methods with F#'s |> operator. It's a little clumsier, given the need for brackets:
let extension f x =
let a = f x
a * 2
let f x = x*x
> f 2;;
val it : int = 4
> (f |> extension) 2;;
val it : int = 8
> let c = extension f 2;; // Same as above
val c : int = 8
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Type extension errors
I would like to add an extension method in F# to System.Collections.Generic.Dictionary. The trouble is that I cannot seem to get the type constraints right. I was hoping something like the following would work:
type Dictionary<'k, 'd when 'k : equality> with
static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
let res = new Dictionary<'k, 'd> ()
for (k, d) in xs do
res.Add (k, d)
res
However, the compiler complains that my declaration differs from that of Dictionary. It does not produce that particular error when I leave out the equality constraint. But then it warns that it's missing. Many thanks for any hints, preferably other that "turn the warning level down" :-)
EDIT
Many thanks to KVB for providing the answer I wanted.
type Dictionary<'k, 'd> with
static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
let res = new Dictionary<'k, 'd> (EqualityComparer<'k>.Default)
for (k, d) in xs do
res.Add (k, d)
res
EDIT: Here is an example to better explain my reply to RJ. It shows that type arguments are optional when instantiating a type provided the compiler can infer them. It compiles without warnings or errors.
type System.Collections.Generic.Dictionary<'k, 'd> with
static member test (dict:System.Collections.Generic.Dictionary<'k, 'd>) : bool =
dict.Values |> List.ofSeq |> List.isEmpty
let test (x:System.Collections.Generic.Dictionary<'k, 'd>) =
System.Collections.Generic.Dictionary.test x
For some reason the names of the type parameters have to match - this works fine for me
open System.Collections.Generic
type Dictionary<'TKey, 'TValue> with
static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
let res = new Dictionary<'k, 'd> ()
for (k, d) in xs do
res.Add (k, d)
res
I have no idea why this is the case (30 second look at the spec provides no clues either).
Update - the error is actually when the Dictionary parameters are the same as what is written in the method - doing
type Dictionary<'a, 'b> with
static member ofList (xs:list<'k * 'd>) : Dictionary<'k, 'd> =
let res = new Dictionary<'k, 'd> ()
for (k, d) in xs do
res.Add (k, d)
res
works just fine. This actually now makes sense. When the parameters are the same, there is an additional unspecified constraint - 'k:equality due to the new Dictionary<'k,'d>. However, for some reason, we can't put constraints in the extension definition (avoiding duplication?) so there is an error.
If you need ofSeq functions for various collections, you might consider an approach similar to C# collection initializers. That is, make it work for any collection with an Add method. This also sidesteps your present problem.
open System.Collections.Generic
open System.Collections.Concurrent
module Dictionary =
let inline ofSeq s =
let t = new ^T()
for k, v in s do
(^T : (member Add : ^K * ^V -> ^R) (t, k, v)) |> ignore
t
module Collection =
let inline ofSeq s =
let t = new ^T()
for v in s do
(^T : (member Add : ^V -> ^R) (t, v)) |> ignore
t
open Dictionary
let xs = List.init 9 (fun i -> string i, i)
let d1 : Dictionary<_,_> = ofSeq xs
let d2 : SortedDictionary<_,_> = ofSeq xs
let d3 : SortedList<_,_> = ofSeq xs
open Collection
let ys = List.init 9 id
let c1 : ResizeArray<_> = ofSeq ys
let c2 : HashSet<_> = ofSeq ys
let c3 : ConcurrentBag<_> = ofSeq ys
Interestingly, you can even limit it to collection types with a specific constructor overload. For example, if you wanted to use structural equality you could do:
let t = (^T : (new : IEqualityComparer< ^K > -> ^T) (HashIdentity.Structural))
You won't be able to get rid of the warning, because you are referring to a type that doesn't exist, Dictionary as opposed to Dictionary<_,_>. You could make a Dictionary module if this is how you want to access it.
open System.Collections.Generic
type Dictionary<'a,'b> with
static member ofList (xs:list<'k*'v>) =
let res = new Dictionary<_,_> ()
for k, v in xs do
res.Add (k, v)
res
module Dictionary =
let ofList xs = Dictionary<_,_>.ofList xs
Then you get rid of the warning.
Dictionary.ofList ["1",1;"2",2];;
val it : Dictionary<string,int> = dict [("1", 1); ("2", 2)]