simple SRTP for method and property - f#

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"

Related

F# How to write a function that takes int list or string list

I'm messing around in F# and tried to write a function that can take an int list or a string list. I have written a function that is logically generic, in that I can modify nothing but the type of the argument and it will run with both types of list. But I cannot generically define it to take both.
Here is my function, without type annotation:
let contains5 xs =
List.map int xs
|> List.contains 5
When I try to annotate the function to take a generic list, I receive a warning FS0064: the construct causes the code to be less generic than indicated by the type annotations. In theory I shouldn't need to annotate this to be generic, but I tried anyway.
I can compile this in two separate files, one with
let stringtest = contains5 ["1";"2";"3";"4"]
and another with
let inttest = contains5 [1;2;3;4;5]
In each of these files, compilation succeeds. Alternately, I can send the function definition and one of the tests to the interpreter, and type inference proceeds just fine. If I try to compile, or send to the interpreter, the function definition and both tests, I receive error FS0001: This expression was expected to have type string, but here has type int.
Am I misunderstanding how typing should work? I have a function whose code can handle a list of ints or a list of strings. I can successfully test it with either. But I can't use it in a program that handles both?
You are running into value restrictions on the automatic generalization of the type inference system as outlined here
Specifically,
Case 4: Adding type parameters.
The solution is to make your function generic rather than just making its parameters generic.
let inline contains5< ^T when ^T : (static member op_Explicit: ^T -> int) > (xs : ^T list) =
List.map int xs
|> List.contains 5
You have to make the function inline because you have to use a statically resolved type parameter, and you have to use a statically resolved type parameter in order to use member constraints to specify that the type must be convertible to an int. As outlined here
You can use inline to prevent the function from being fixed to a particular type.
In FSI, the interactive REPL:
> open System;;
> let inline contains5 xs = List.map int xs |> List.contains 5;;
val inline contains5 :
xs: ^a list -> bool when ^a : (static member op_Explicit : ^a -> int)
> [1;2;3] |> contains5;;
val it : bool = false
> ["1";"2";"5"] |> contains5;;
val it : bool = true
Note that the signature of contains5 has a generic element to it. There's more about inline functions here.
This is already answered correctly above, so I just wanted to chime in with why I think it's a good thing that F# appears to makes this difficult / forces us to lose type safety. Personally I don't see these as logically equivalent:
let inline contains5 xs = List.map int xs |> List.contains 5
let stringTest = ["5.00"; "five"; "5"; "-5"; "5,"]
let intTest = [1;2;3;4;5]
contains5 stringTest // OUTPUT: System.FormatException: Input string was not in a correct format.
contains5 intTest // OUTPUT: true
When inlined, the compiler would create two logically distinct versions of the function. When performed on the list<int> we get a boolean result. When performed on a list<string> we get a boolean result or an exception. I like that F# nudges me towards acknowledging this.
let maybeInt i =
match Int32.TryParse i with
| true,successfullyParsedInteger -> Some successfullyParsedInteger
| _ -> None
let contains5 xs =
match box xs with
| :? list<int> as ixs ->
ixs |> List.contains 5 |> Ok
| :? list<string> as sxs ->
let successList = sxs |> List.map maybeInt |> List.choose id
Ok (successList |> List.contains 5)
| _ ->
Error "Error - this function expects a list<int> or a list<string> but was passed something else."
let stringTest = ["5.00"; "five"; "5"; "-5"; "5,"]
let intTest = [1;2;3;4;5]
let result1 = contains5 stringTest // OUTPUT: Ok true
let result2 = contains5 intTest // OUTPUT: Ok true
Forces me to ask if some of the values in the string list cannot be parsed, should I drop out and fail, or should I just try and look for any match on any successful parse results?.
My approach above is horrible. I'd split the function that operates on the strings from the one that operates on the integers. I think your question was academic rather than a real use case though, so I hope I haven't gone off on too much of a tangent here!
Disclaimer: I'm a beginner, don't trust anything I say.

In F#, is there a way to simplify this wrapper for OpenFile and SaveFile dialogs'

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())

F# - Can I use type name as a function which acts as the default constructor?

To create a sequence of my class,
type MyInt(i:int) =
member this.i = i
[1;2;3] |> Seq.map(fun x->MyInt(x))
where fun x->MyInt(x) seems to be redundant. It would be better if I can write Seq.map(MyInt)
But I cannot. One workaround I can think of is to define a separate function
let myint x = MyInt(x)
[1;2;3] |> Seq.map(myint)
Is there a better way to do this?
If gratuitous hacks don't bother you, you could do:
///functionize constructor taking one arg
let inline New< ^T, ^U when ^T : (static member ``.ctor`` : ^U -> ^T)> arg =
(^T : (static member ``.ctor`` : ^U -> ^T) arg)
type MyInt(i: int) =
member x.i = i
[0..9] |> List.map New<MyInt, _>
EDIT: As kvb pointed out, a simpler (and less hacky) signature can be used:
let inline New x = (^t : (new : ^u -> ^t) x)
Note, this switches the type args around, so it becomes New<_, MyInt>.
In short, no.
Object constructors aren't first-class functions in F#. This is one more reason to not use classes, discriminated unions is better to use here:
type myInt = MyInt of int
let xs = [1;2;3] |> Seq.map MyInt
If you don't like explicit lambdas, sequence expression looks nicer in your example:
let xs = seq { for x in [1;2;3] -> MyInt x }
Alternatively, your workaround is a nice solution.
To give an update on this - F# 4.0 has upgraded constructors to first-class functions, so they can now be used anywhere a function or method can be used.
I use static methods for this purpose. The reason is that sometimes your object constructor needs two arguments taken from different sources, and my approach allows you to use List.map2:
type NumRange(value, range) =
static member Make aValue aRange = new NumRange(aValue, aRange)
let result1 = List.map2 NumRange.Make values ranges
Partial application is not prohibited as well:
let result2 =
values
|> List.map NumRange.Make
|> List.map2 id <| ranges
If you dislike using id here, you may use (fun x y -> x y) which is more readable.

F# type constraints and overloading resolution

I am trying to emulate a system of type classes in F#; I would like to create pair printer which automatically instantiates the right series of calls to the printing functions. My latest try, which is pasted here, fails miserably since F# cannot identify the right overload and gives up immediately:
type PrintableInt(x:int) =
member this.Print() = printfn "%d" x
let (!) x = PrintableInt(x)
type Printer() =
static member inline Print< ^a when ^a : (member Print : Unit -> Unit)>(x : ^a) =
(^a : (member Print : Unit -> Unit) x)
static member inline Print((x,y) : 'a * 'b) =
Printer.Print(x)
Printer.Print(y)
let x = (!1,!2),(!3,!4)
Printer.Print(x)
Is there any way to do so? I am doing this in the context of game development, so I cannot afford the runtime overhead of reflection, retyping and dynamic casting: either I do this statically through inlining or I don't do it at all :(
What you're trying to do is possible.
You can emulate typeclasses in F#, as Tomas said maybe is not as idiomatic as in Haskell. I think in your example you are mixing typeclasses with duck-typing, if you want to go for the typeclasses approach don't use members, use functions and static members instead.
So your code could be something like this:
type Print = Print with
static member ($) (_Printable:Print, x:string) = printfn "%s" x
static member ($) (_Printable:Print, x:int ) = printfn "%d" x
// more overloads for existing types
let inline print p = Print $ p
type Print with
static member inline ($) (_Printable:Print, (a,b) ) = print a; print b
print 5
print ((10,"hi"))
print (("hello",20), (2,"world"))
// A wrapper for Int (from your sample code)
type PrintableInt = PrintableInt of int with
static member ($) (_Printable:Print, (PrintableInt (x:int))) = printfn "%d" x
let (!) x = PrintableInt(x)
let x = (!1,!2),(!3,!4)
print x
// Create a type
type Person = {fstName : string ; lstName : string } with
// Make it member of _Printable
static member ($) (_Printable:Print, p:Person) = printfn "%s, %s" p.lstName p.fstName
print {fstName = "John"; lstName = "Doe" }
print (1 ,{fstName = "John"; lstName = "Doe" })
Note: I used an operator to avoid writing the constraints by hand, but in this case is also possible to use a named static member.
More about this technique here.
What you're trying to do is not possible (edit: apparently, it can be done - but it might not be idiomatic F#), because the constraint language cannot capture the constraints you need for the second Print operation. Basically, there is no way to write recursive constraints saying that:
Let C be a constraint specifying that the type either provides Print or it is a two-element tuple where each element satisfies C.
F# does not support type-classes and so most of the attempts to emulate them will (probably) be limited in some way or will look very unnatural. In practice, instead of trying to emulate solutions that work in other languages, it is better to look for an idiomatic F# solution to the problem.
The pretty printing that you're using as a sample would be probably implemented using Reflection or by wrapping not just integers, but also tuples.

Confusing error message "Expected type unit but was X"

I'm either not seeing something obvious or just generally confused
The code I have looks like:
let inline createContext source destination =
let src = (fun amount (s:^T) -> (^T : (member DecreaseBalance : decimal -> ^a) (s, amount)))
let dst = (fun amount (d:^T) -> (^T : (member IncreaseBalance : decimal -> ^a) (d, amount)))
let log = (fun msg a -> (^T : (member LogMessage : string -> ^a) (a, msg)))
let f = fun amount -> src amount source |> ignore
log "" source |> ignore
let f = fun amount -> dst amount destination |> ignore
log "" destination |> ignore
new Context (source, destination, src, dst, log)
let src = new Account(0m)
let dst = new Account(0m)
let ctxt = createContext src dst
The type Account fullfils the member constraints of createContext. Intellisense claims the signartur of createContext to be Account -> Account -> Context but the compiler complains at src in the last line with "This expression was expected to have type unit but here has type Account"
Any idea of what I'm missing?
If I rename a member function of Account so it no longer meets the constraints I get
"The type 'Account' does not support any operators named 'LogMessage'" which is what I would expect in that scenario. I get the same error message if I pass () as the first argument. That unit doesn't support LogMessage (not that it would bring me any good if that actually compiled)
This compiles fine for me, given the following types
type Context(a, b, c, d, e) = class end
type Account(a) =
member __.DecreaseBalance(a) = Unchecked.defaultof<_>
member __.IncreaseBalance(a) = Unchecked.defaultof<_>
member __.LogMessage(a) = Unchecked.defaultof<_>
I suspect something else is going on. Can you show more of the code?
It's strange that createContext is inferred to be Account -> Account -> Context. I would expect 'T -> 'T -> Context ('T requires member DecreaseBalance and IncreaseBalance and LogMessage). That may or may not be a clue to your problem.
If possible, replace the static member constraints with an interface.

Resources