Statically resolved string conversion function in F# - f#

I'm trying to create a function in F# that will convert certain types to a string, but not others. The objective is so that a primitive can be passed but a complex object cannot be passed by accident. Here's what I have so far:
type Conversions =
static member Convert (value:int) =
value.ToString()
static member Convert (value:bool) =
value.ToString()
let inline convHelper< ^t, ^v when ^t : (static member Convert : ^v -> string) > (value:^v) =
( ^t : (static member Convert : ^v -> string) (value))
let inline conv (value:^v) = convHelper<Conversions, ^v>(value)
Unfortunately, my conv function gets the following compile-time error:
A unique overload for method 'Convert' could not be determined based on type information
prior to this program point. A type annotation may be needed. Candidates:
static member Conversions.Convert : value:bool -> string,
static member Conversions.Convert : value:int -> string
What am I doing wrong?

This seems to work:
type Conversions = Conversions with
static member ($) (Conversions, value: int) = value.ToString()
static member ($) (Conversions, value: bool) = value.ToString()
let inline conv value = Conversions $ value
conv 1 |> ignore
conv true |> ignore
conv "foo" |> ignore //won't compile

For some reason it seems using (^t or ^v) in the constraint instead of just ^v makes it work.
type Conversions =
static member Convert (value:int) =
value.ToString()
static member Convert (value:bool) =
value.ToString()
let inline convHelper< ^t, ^v when (^t or ^v) : (static member Convert : ^v -> string)> value =
( (^t or ^v) : (static member Convert : ^v -> string) (value))
let inline conv value = convHelper<Conversions, _>(value)
Of course it means the function will also compile if the argument's type has a static method Convert from itself to string, but it's highly unlikely to ever bite you.

Well, Daniel's answer worked. Here's what I wanted in the end:
type Conversions = Conversions with
static member ($) (c:Conversions, value:#IConvertible) =
value.ToString()
static member ($) (c:Conversions, value:#IConvertible option) =
match value with
| Some x -> x.ToString()
| None -> ""
let inline conv value = Conversions $ value
The IConvertible interface type is just a convenient way for me to capture all primitives.
This results in the following behavior (FSI):
conv 1 // Produces "1"
conv (Some 1) // Produces "1"
conv None // Produces ""
conv obj() // Compiler error

Related

How to constrain type parameter must be an algebraic type (int, float, BigInteger, BigRational, ...)

-- While there are some questions on the net wrt. type constraints already, I didn't find one that can help me solving my issue. --
Goal: I want to create my own Vector/Matrix types, but so, that the implementation does not lock in to a speicific BigRational (or alike) type. All I'd prefer to require is the standard algebraic operations on such types (+ - * / % equality).
open System
type Foo<'T> (value: 'T) =
member inline __.Value : 'T = value
static member inline Add (a: Foo<'T>) (b: Foo<'T>) =
Foo<'T>(a.Value + b.Value)
module Foo =
let inline Create (v) = Foo(v)
let log (foo: #Foo<_>) =
printfn "Foo: %s" (foo.Value.ToString())
[<EntryPoint>]
let main argv =
Foo.log (Foo.Create("hi ho"))
Foo.log (Foo<int>(31415))
Foo.log (Foo<float>(3.1415))
Foo.log (Foo<int>.Add (Foo.Create(3)) (Foo.Create(4)))
let a = Foo.Create(13)
let b = Foo.Create(3.1415)
Foo.log (Foo<int>.Add (a.Value) (a.Value))
Foo.log (Foo<float>.Add (b.Value) (b.Value))
0 // return an integer exit code
I cannot get this tiny example code to compile for more than one single type, such as Foo<int> as well as Foo<float>. How could I do it right?
Many thanks in advance,
Christian.
You almost have it, actually.
In order to create a function that accepts any type that has a + operator, this function must have statically-resolved type parameters (SRTP). For this, it must be inline, which your Add is so that's ok. However here Add is not a generic method: it's a method on the generic type Foo<'T>, so it receives its 'T parameter from it. And a type cannot have SRTP.
A simple fix is to move Add from being a method on the type Foo<'T> to being a function in the module Foo. Then it will become actually generic.
open System
type Foo<'T> (value: 'T) =
member inline __.Value : 'T = value
module Foo =
let inline Create (v) = Foo(v)
let inline Add (a: Foo< ^T>) (b: Foo< ^T>) =
Foo< ^T>(a.Value + b.Value)
let log (foo: #Foo<_>) =
printfn "Foo: %s" (foo.Value.ToString())
[<EntryPoint>]
let main argv =
Foo.log (Foo.Create("hi ho"))
Foo.log (Foo<int>(31415))
Foo.log (Foo<float>(3.1415))
Foo.log (Foo.Add (Foo.Create(3)) (Foo.Create(4)))
let a = Foo.Create(13)
let b = Foo.Create(3.1415)
Foo.log (Foo.Add a a)
Foo.log (Foo.Add b b)
0
I think all you need is the inline keyword if you just want to propagate the member constraints of F#'s overloaded arithmetic operators.
type Foo<'T> (value : 'T) =
member __.Value = value
static member inline (+) (a : Foo<_>, b : Foo<_>) = Foo(a.Value + b.Value)
// static member
// ( + ) : a:Foo< ^a> * b:Foo< ^b> -> Foo< ^c>
// when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
Now you can add two Foos of the same type that support a static member (+), Foo 3 + Foo 4 or Foo 3.14 + Foo 3.14, even Foo(Foo 3) + Foo(Foo 4); but not Foo 3 + Foo 3.14. You may still instantiate types that do not possess such member.

Object Expression as Computation Builder

Given a generic interface declaration like
type IFoo<'T,'MT> =
abstract Return : 'T -> 'MT
abstract Bind : 'MT * ('T -> 'MT) -> 'MT
it's actually possible to use object expressions as computation builder expressions, which could provide for an interesting approach to the division between encapsulation and execution logic of monadic workflows.
let inline addOption mx my =
{ new IFoo<_,_> with
member __.Return x = Some x
member __.Bind(ma, f) = Option.bind f ma }
{ let! x = mx
let! y = my
return x + y }
// val inline addOption :
// mx: ^a option -> my: ^a option -> ^a option
// when ^a : (static member ( + ) : ^a * ^a -> ^a)
addOption (Some 1) (Some 2)
// val it : int option = Some 3
addOption None (Some 2)
// val it : int option = None
The compiler checks on the type of the expression if the expected methods are present. But it is only halfway there; because for real monads, I would need to get the method signature abstract Bind : 'MT * ('T -> 'MU) -> 'MU honoured, a projection to a different non-encapsulated type. Why can't this be done?

member function constraint madness [duplicate]

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

F# operator overloading riddle

In F# operator overloading seems powerful but also tricky to get right.
I have following class:
type Value<'T> =
with
static member inline (+) (a : Value<'U>, b: Value<'U>) : Value<'U> =
do stuff
If i define another overload for + with :
static member inline (+) (a : Value<'U>, b: 'U) : Value<'U> =
do stuff
It works. But if i want a symmetric operator:
static member inline (+) (b: 'U, a : Value<'U>) : Value<'U> =
do stuff
The compiler complains:
let a = Value<int>(2);
let b = a + 3 // ok
let c = 3 + a //<-- error here
Error 3 Type inference problem too complicated (maximum iteration depth reached). Consider adding further type annotations
Is there a way around this and stay generic?
I am using F# 3.1
Thanks
The compiler never has a choice: When you apply the (+) operator, you either give it something of type int or something of type Value<'U>. Something of type int cannot be considered of type Value<'U>, and vice versa.
Let's try this in the interpreter. I made the two implementations output A and B so we can tell which is being called:
> type Value<'T> =
- { a : 'T }
- with
- static member inline (+) (a : Value<'U>, b: Value<'U>) : Value<'U> =
- printfn "A"; a
- static member inline (+) (a : Value<'U>, b: int) : Value<'U> =
- printfn "B"; a
- ;;
type Value<'T> =
{a: 'T;}
with
static member ( + ) : a:Value<'U> * b:Value<'U> -> Value<'U>
static member ( + ) : a:Value<'U> * b:int -> Value<'U>
end
Now we have a type. Let's make a value of it.
> let t = { a = "foo" };;
val t : Value<string> = {a = "foo";}
We can now try the overloads. First int:
> t + 4;;
B
val it : Value<string> = {a = "foo";}
Ok. Now Value<string>:
> t + t;;
A
val it : Value<string> = {a = "foo";}
As stated in another answer, my preferred solution would be to use a base class for some overloads:
type BaseValue<'T>(v : 'T) =
member x.V = v
type Value<'T>(v : 'T) =
inherit BaseValue<'T>(v : 'T)
static member inline (+) (a : Value<_>, b: Value<_>) = Value(b.V+a.V)
type BaseValue with
static member inline (+) (a: BaseValue<_>, b) = Value(b+a.V)
static member inline (+) (b, a: BaseValue<_>) = Value(b+a.V)
// test
let v = Value(2)
let a = v + v
let b = v + 3
let c = 3 + v
let d = Value(Value 7) + Value(Value 10)
let e = 5 + 7
Note that this problem disappears if you use the same type argument on your members as you do on the class itself:
type Value<'t when 't : (static member (+) : 't * 't -> 't)> = V of 't with
static member inline (+)(V(x:'t), V(y:'t)) : Value<'t> = V(x+y)
static member inline (+)(V(x:'t), y:'t) : Value<'t> = V(x+y)
static member inline (+)(x:'t, V(y:'t)) : Value<'t> = V(x+y)
This means that you can't create instances of type Value<obj> any more, but might meet your needs.
This operator definition is not sensible, though interestingly, neither is the compiler's reaction.
First of all, think about the intended return type of adding two Value<_> objects. It could be Value<Value<_>>! Language semantics aside, there is no reason to assume that a fully generic type like this shouldn't be nested within itself. This + operator is ill-defined. Type inference will always fail on the overload that adds two Value<_> instances, as the compiler will be unable to resolve the ambiguity.
But the more fundamental question is: what does this operator even mean? Add values and maybe wrap them in an extra type? Those are two entirely different operations; I don't see the merit of combining them, let alone implicitly and in a possibly ambiguous manner. It might save a lot of hassle to only define addition for two Value instances and construct them explicitly where needed.
That aside, it looks like the compiler is quite unintuitive in this case. Someone who knows the F# specification in detail may be able to tell whether it's a bug or inconsistent design, but look at how similar variants behave:
type Value<'T> =
{ Raw : 'T }
static member inline (+) (v, w) = { Raw = v.Raw + w.Raw }
static member inline (+) (v, a) = { Raw = v.Raw + a }
static member inline (+) (a, v) = { Raw = a + v.Raw }
let a = { Raw = 2 }
let b = a + 3 // ok
let c = 3 + a // ok
let d = { Raw = obj() } // No problemo
Too far off? Try this:
type Value<'T> =
val Raw : 'T
new (raw) = { Raw = raw }
static member inline (+) (v : Value<_>, w : Value<_>) = Value(v.Raw + w.Raw)
static member inline (+) (v : Value<_>, a) = Value(v.Raw + a)
static member inline (+) (a, v : Value<_>) = Value(a + v.Raw)
let a = Value(2)
let b = a + 3 // OK
let c = 3 + a // OK
let d = Value(obj) // No problemo
Not sure what is going on here, but it's not very consistent.

Multiple arity static type constraint

Let's say I have a bunch of vector types (a la XNA) and some of them have static member Cross:
type Vector3 =
...
static member Cross (a : Vector3, b : Vector3) = new Vector3(...)
I can define the cross function and it compiles:
let inline cross (x : ^T) (y : ^T) = (^T : (static member Cross : (^T * ^T) -> ^T) ((x,y)))
Unfortunately I'm not able to use it and have following error:
let res = cross a b
^
The member or object constructor Cross
takes 2 argument(s) but is here given
1. The required signature is static member Vector3.Cross :
a:Vector3 * b:Vector3 ->
Vector3
Is it even possible at all? Thanks for helping!
You've over-parenthesized your static member signature. Try this instead:
let inline cross (x : ^T) (y : ^T) =
(^T : (static member Cross : ^T * ^T -> ^T) (x,y))
Given your definition, F# was looking for a member Cross which takes a single argument of tuple type.

Resources