I am trying to create an active pattern Scan around FSharpPlus's trySscanf, so that the following works:
let res = // res = 42
match "Hello 42 World" with
| Scan "Hello %i World" n -> n
The way I understand incomplete active patterns to work, I simply need to return an option, trySscanf already returns an option so I tried the following:
let (|Scan|_|) = trySscanf
When that didn't work I tried a more explicit
let (|Scan|_|) pattern input = trySscanf pattern input
They both fail with the following compilation error:
Type constraint mismatch when applying the default type 'obj' for a type inference variable. No overloads match for method 'TryParseArray'.
Known return type: (string [] -> obj option)
Known type parameters: < obj , Internals.TryParseArray >
Available overloads:
- static member Internals.TryParseArray.TryParseArray : ^t * obj -> (string [] -> ^t option) when (Control.TryParse or ^t) : (static member TryParse : ^t * Control.TryParse -> (string -> ^t option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2' * 't3 * 't4 * 't5 * 't6 * 't7) * Internals.TryParseArray -> (string [] -> ( ^a7 * ^a8 * ^a9 * ^a10 * ^a11 * ^a12 * ^a13) option) when (Control.TryParse or ^a7) : (static member TryParse : ^a7 * Control.TryParse -> (string -> ^a7 option)) and (Control.TryParse or ^a8) : (static member TryParse : ^a8 * Control.TryParse -> (string -> ^a8 option)) and (Control.TryParse or ^a9) : (static member TryParse : ^a9 * Control.TryParse -> (string -> ^a9 option)) and (Control.TryParse or ^a10) : (static member TryParse : ^a10 * Control.TryParse -> (string -> ^a10 option)) and (Control.TryParse or ^a11) : (static member TryParse : ^a11 * Control.TryParse -> (string -> ^a11 option)) and (Control.TryParse or ^a12) : (static member TryParse : ^a12 * Control.TryParse -> (string -> ^a12 option)) and (Control.TryParse or ^a13) : (static member TryParse : ^a13 * Control.TryParse -> (string -> ^a13 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2' * 't3 * 't4 * 't5 * 't6) * Internals.TryParseArray -> (string [] -> ( ^a6 * ^a7 * ^a8 * ^a9 * ^a10 * ^a11) option) when (Control.TryParse or ^a6) : (static member TryParse : ^a6 * Control.TryParse -> (string -> ^a6 option)) and (Control.TryParse or ^a7) : (static member TryParse : ^a7 * Control.TryParse -> (string -> ^a7 option)) and (Control.TryParse or ^a8) : (static member TryParse : ^a8 * Control.TryParse -> (string -> ^a8 option)) and (Control.TryParse or ^a9) : (static member TryParse : ^a9 * Control.TryParse -> (string -> ^a9 option)) and (Control.TryParse or ^a10) : (static member TryParse : ^a10 * Control.TryParse -> (string -> ^a10 option)) and (Control.TryParse or ^a11) : (static member TryParse : ^a11 * Control.TryParse -> (string -> ^a11 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2' * 't3 * 't4 * 't5) * Internals.TryParseArray -> (string [] -> ( ^a5 * ^a6 * ^a7 * ^a8 * ^a9) option) when (Control.TryParse or ^a5) : (static member TryParse : ^a5 * Control.TryParse -> (string -> ^a5 option)) and (Control.TryParse or ^a6) : (static member TryParse : ^a6 * Control.TryParse -> (string -> ^a6 option)) and (Control.TryParse or ^a7) : (static member TryParse : ^a7 * Control.TryParse -> (string -> ^a7 option)) and (Control.TryParse or ^a8) : (static member TryParse : ^a8 * Control.TryParse -> (string -> ^a8 option)) and (Control.TryParse or ^a9) : (static member TryParse : ^a9 * Control.TryParse -> (string -> ^a9 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2' * 't3 * 't4) * Internals.TryParseArray -> (string [] -> ( ^a4 * ^a5 * ^a6 * ^a7) option) when (Control.TryParse or ^a4) : (static member TryParse : ^a4 * Control.TryParse -> (string -> ^a4 option)) and (Control.TryParse or ^a5) : (static member TryParse : ^a5 * Control.TryParse -> (string -> ^a5 option)) and (Control.TryParse or ^a6) : (static member TryParse : ^a6 * Control.TryParse -> (string -> ^a6 option)) and (Control.TryParse or ^a7) : (static member TryParse : ^a7 * Control.TryParse -> (string -> ^a7 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2' * 't3) * Internals.TryParseArray -> (string [] -> ( ^a3 * ^a4 * ^a5) option) when (Control.TryParse or ^a3) : (static member TryParse : ^a3 * Control.TryParse -> (string -> ^a3 option)) and (Control.TryParse or ^a4) : (static member TryParse : ^a4 * Control.TryParse -> (string -> ^a4 option)) and (Control.TryParse or ^a5) : (static member TryParse : ^a5 * Control.TryParse -> (string -> ^a5 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : ('t1 * 't2) * Internals.TryParseArray -> (string [] -> ( ^a2 * ^a3) option) when (Control.TryParse or ^a2) : (static member TryParse : ^a2 * Control.TryParse -> (string -> ^a2 option)) and (Control.TryParse or ^a3) : (static member TryParse : ^a3 * Control.TryParse -> (string -> ^a3 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : Internals.Id<'t1> * Internals.TryParseArray -> (string [] -> Internals.Id< ^a1> option) when (Control.TryParse or ^a1) : (static member TryParse : ^a1 * Control.TryParse -> (string -> ^a1 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : Tuple< ^t1> * Internals.TryParseArray -> (string [] -> Tuple< ^t1> option) when (Control.TryParse or ^t1) : (static member TryParse : ^t1 * Control.TryParse -> (string -> ^t1 option)) // Argument at index 1 doesn't match
- static member Internals.TryParseArray.TryParseArray : t: ^t * Internals.TryParseArray -> (string [] -> ^t option) when ^t : (member get_Item1 : ^t -> ^t1) and ^t : (member get_Item2 : ^t -> ^t2) and ^t : (member get_Item3 : ^t -> ^t3) and ^t : (member get_Item4 : ^t -> ^t4) and ^t : (member get_Item5 : ^t -> ^t5) and ^t : (member get_Item6 : ^t -> ^t6) and ^t : (member get_Item7 : ^t -> ^t7) and ^t : (member get_Rest : ^t -> ^tr) and (Control.TryParse or ^t1) : (static member TryParse : ^t1 * Control.TryParse -> (string -> ^t1 option)) and (Control.TryParse or ^t2) : (static member TryParse : ^t2 * Control.TryParse -> (string -> ^t2 option)) and (Control.TryParse or ^t3) : (static member TryParse : ^t3 * Control.TryParse -> (string -> ^t3 option)) and (Control.TryParse or ^t4) : (static member TryParse : ^t4 * Control.TryParse -> (string -> ^t4 option)) and (Control.TryParse or ^t5) : (static member TryParse : ^t5 * Control.TryParse -> (string -> ^t5 option)) and (Control.TryParse or ^t6) : (static member TryParse : ^t6 * Control.TryParse -> (string -> ^t6 option)) and (Control.TryParse or ^t7) : (static member TryParse : ^t7 * Control.TryParse -> (string -> ^t7 option)) and (Internals.TryParseArray or ^tr) : (static member TryParseArray : ^tr * Internals.TryParseArray -> (string [] -> ^tr option)) // Argument 't' doesn't match
- static member Internals.TryParseArray.TryParseArray : unit * Internals.TryParseArray -> (string [] -> unit) // Argument at index 1 doesn't match Consider adding further type constraintsF# Compiler(71)
Clearly, trySscanf has a bunch of overloads which I'm not taking into account, and I'm not sure how to do that.
Is what I'm trying to do even possible?
Interestingly, if I add the match expression, the compilation error disappears, however I may only use the PrintfFormat of the first match case I try:
let parseLine line =
match line with
| Scan "mem[%i] = %i" (address, value) -> Op address value
| Scan "mask = %s" str -> Mask str
Last line signals an error:
This expression was expected to have type
'int * int'
but here has type
'string' (F# Compiler(1))
You just forgot to declare your function inline, do that and it will work just fine:
#r "nuget: FSharpPlus"
open FSharpPlus
let inline (|Scan|_|) pattern input = trySscanf pattern input
let res48 =
match "Hello 42 6 World" with
| Scan "Hello %i World" n -> n
| Scan "Hello %i %i World" (n1, n2) -> n1 + n2
The reason why you have declare it inline is because functions with constraints can only be declared inline, it's the same case as if you want to write a function that doubles the numeric input for every numeric type.
Related
I am trying to learn more about inline and SRTP. Unfortunately I don't have the understanding to make this sample more minimal, but it's not too big:
type ComponentCollection<'props, 'comp when 'props : comparison> =
{
Components : Map<'props, 'comp>
}
with
static member AddComponent (collection, props, comp) =
{
Components =
collection.Components
|> Map.add props comp
}
module ComponentCollection =
let inline add< ^t, ^props, ^comp
when ^t : (static member AddComponent : ((^t * ^props * ^comp) -> ^t)) >
(props : ^props) (comp : ^comp) (xs : ^t) =
(^t : (static member AddComponent : (^t * ^props * ^comp -> ^t ) ) () ) (xs, props, comp)
type Component<'t> =
{
Props : 't
}
type Foo =
{
Foo : int
}
[<EntryPoint>]
let main argv =
let foo = { Foo = 1 }
let a : ComponentCollection<Foo, Component<Foo>> =
{ Components = Map.empty }
|> ComponentCollection.add foo { Props = foo }
0
The gives a compilation error:
error FS0001: The type 'ComponentCollection<'a,'b>' does not support the operator 'get_AddComponent'
This is very misleading to me, since I am using static member AddComponent, not get_AddComponent. I am not sure where the compiler gets get_AddComponent from.
What am I doing wrong here?
Additionally, it would be very helpful if someone could explain this bit of the code:
(^t : (static member AddComponent : (^t * ^props * ^comp -> ^t ) ) () )
I understand that it somehow provides the static member AddComponent of ^t as a free-function, but I cobbled it together from other examples, and the documentation for this syntax is lacking. For example, why is a () required?
As far as I can see, the only thing that is wrong with your snippet is that you have some excessive parentheses which, unfortunately in this case, actually have semantic meaning. Changing the code as follows resolves the problem:
module ComponentCollection =
let inline add< ^t, ^props, ^comp
when ^t : (static member AddComponent : ^t * ^props * ^comp -> ^t) >
(props : ^props) (comp : ^comp) (xs : ^t) =
(^t : (static member AddComponent : ^t * ^props * ^comp -> ^t) (xs, props, comp))
The issue is that F# actually makes a difference between:
static member AddComponent : ^t * ^props * ^comp -> ^t
static member AddComponent : (^t * ^props * ^comp) -> ^t
static member AddComponent : ((^t * ^props * ^comp) -> ^t)
The first is a regular static method with three arguments
The second is a static method taking three-element tuple as a single argument
The last is a static property returning a function value taking a tuple
Regarding your second question - in the working version, the call looks like this:
(^t : (static member AddComponent : ^t * ^props * ^comp -> ^t) (xs, props, comp))
This tells the compiler to access the static member (which is required by the constraint) and invoke it with arguments xs, props and comp. In your original version, this was invoking a getter method of the property (confusingly though, it somehow allowed you to do this without arguments - I suspect this would lead to another type error later).
From experience, SRTP's are complex and hard to use. They require a lot of inlines and Error messages tend to be very obscure.
I would recommend limiting their use.
Also, they would not be C# compatible if that is something you are interested in.
I also changed your code to fix the issues:
type ComponentCollection<'props, 'comp when 'props : comparison> =
{
Components : Map<'props, 'comp>
}
with
member collection.AddComponent(props, comp) =
{
Components =
collection.Components
|> Map.add props comp
}
module ComponentCollection =
let inline add< ^t, ^props, ^comp
when 'props : comparison and ^t : (member AddComponent : ^props * ^comp -> ^t) >
(props : ^props) (comp : ^comp) (xs : ^t) =
(^t : (member AddComponent : ^props * ^comp -> ^t ) (xs, props, comp ))
type Component<'t> =
{
Props : 't
}
type Foo =
{
Foo : int
}
[<EntryPoint>]
let main argv =
let foo = { Foo = 1 }
let a =
{ Components = Map.empty }
|> ComponentCollection.add foo { Props = foo }
0
I am trying to use PrintfFormat to type enforce resolution of parsers and it initially appeared to work for int but then same approach for string did not work... while float did work, so I thought was Value/Ref type issue but then tried bool and that didn't work like String.
int & float work, string & bool do not!?
(ParseApply methods are dummy implementations for now)
type System.String with static member inline ParseApply (path:string) (fn: string -> ^b) : ^b = fn ""
type System.Int32 with static member inline ParseApply (path:string) (fn: int -> ^b) : ^b = fn 0
type System.Double with static member inline ParseApply (path:string) (fn: float -> ^b) : ^b = fn 0.
type System.Boolean with static member inline ParseApply (path:string) (fn: bool -> ^b) : ^b = fn true
let inline parser (fmt:PrintfFormat< ^a -> ^b,_,_,^b>) (fn:^a -> ^b) (v:string) : ^b
when ^a : (static member ParseApply: string -> (^a -> ^b) -> ^b) =
(^a : (static member ParseApply: string -> (^a -> ^b) -> ^b)(v,fn))
let inline patternTest (fmt:PrintfFormat< ^a -> Action< ^T>,_,_,Action< ^T>>) (fn:^a -> Action< ^T>) v : Action< ^T> = parser fmt fn v
let parseFn1 = patternTest "adfadf%i" (fun v -> printfn "%i" v; Unchecked.defaultof<Action<unit>> ) // works
let parseFn2 = patternTest "adf%s245" (fun v -> printfn "%s" v; Unchecked.defaultof<Action<unit>> ) // ERROR
let parseFn3 = patternTest "adfadf%f" (fun v -> printfn "%f" v; Unchecked.defaultof<Action<unit>> ) // works
let parseFn4 = patternTest "adfadf%b" (fun v -> printfn "%b" v; Unchecked.defaultof<Action<unit>> ) // ERROR
The error I get on result2 function format string input is The type 'string' does not support the operator 'ParseApply', similarly, result4 error is The type 'bool' does not support the operator 'ParseApply'.
I don't know why there is this inconsistency, is it a bug or am I missing something?
As #ChesterHusk said, at the moment extensions are not visible to trait calls.
See also Error on Extension Methods when Inlining
At the moment, the way to make it work is using an intermediate class with an operator-like trait call (operators normally look into their own class and in user defined classes).
open System
type T = T with
static member inline ($) (T, _:string) : _ ->_ -> ^b = fun (path:string) (fn: string -> ^b)-> fn ""
static member inline ($) (T, _:int) : _ ->_ -> ^b = fun (path:string) (fn: int -> ^b) -> fn 0
static member inline ($) (T, _:float) : _ ->_ -> ^b = fun (path:string) (fn: float -> ^b) -> fn 0.
static member inline ($) (T, _:bool) : _ ->_ -> ^b = fun (path:string) (fn: bool -> ^b) -> fn true
let inline parser (fmt:PrintfFormat< ^a -> ^b,_,_,^b>) (fn:^a -> ^b) (v:string) : ^b = (T $ Unchecked.defaultof< ^a> ) v fn
let inline patternTest (fmt:PrintfFormat< ^a -> Action< ^T>,_,_,Action< ^T>>) (fn:^a -> Action< ^T>) v : Action< ^T> = parser fmt fn v
let parseFn1 = parser "adfadf%i" (fun v -> printfn "%i" v; Unchecked.defaultof<int>)
let parseFn2 = parser "adf%s245" (fun v -> printfn "%s" v; Unchecked.defaultof<string>)
let parseFn3 = parser "adfadf%f" (fun v -> printfn "%f" v; Unchecked.defaultof<float>)
let parseFn4 = parser "adfadf%b" (fun v -> printfn "%b" v; Unchecked.defaultof<bool>)
This can be written with named methods by replicating the way operators trait call are desugared.
I think this is still an open gap in the F# compiler, ie that Extension Members are not visible to type constraints. There's a WIP PR here that bridges the gap.
How do I implement statically resolved type parameters?
Specifically, I want to make a function work for all numeric types.
I've tried the following:
let isAbsoluteProductGreaterThanSum a b =
Math.Abs(a * b) > (a + b)
let inline isAbsoluteProductGreaterThanSum a b =
Math.Abs(a * b) > (a + b)
let inline isAbsoluteProductGreaterThanSum ^a ^b =
Math.Abs(^a * ^b) > (^a + ^b)
let inline isAbsoluteProductGreaterThanSum (val1:^a) (val2:^b) =
Math.Abs(val1 * val2) > (val1 + val2)
I did view this documentation but was still unable to resolve my question.
This will work just fine:
let inline isAbsoluteProductGreaterThanSum a b =
abs(a * b) > (a + b)
signature for this one will be like:
val isAbsoluteProductGreaterThanSum:
a: ^a (requires static member ( * ) and static member ( + ) )->
b: ^b (requires static member ( * ) and static member ( + ) )
-> bool
I thought maybe you need explicitly made it with constraints, so here you go:
let inline isAbsoluteProductGreaterThanSum'< ^a, ^b, ^c
when (^a or ^b): (static member (+): ^a * ^b -> ^c)
and (^a or ^b): (static member (*): ^a * ^b -> ^c)
and ^c : (static member Abs: ^c -> ^c)
and ^c : comparison>
a b =
let productOfAb = ((^a or ^b): (static member (*): ^a * ^b -> ^c) (a, b))
let sumOfAb = ((^a or ^b): (static member (+): ^a * ^b -> ^c) (a, b))
abs (productOfAb) > sumOfAb
given this code
type Baz = Baz of int with
static member bar f (Baz(b)) = f b
let inline foo< ^T, ^U when ^T : (static member bar : (^U -> ^T) -> ^T -> ^T)>
(f:(^U -> ^T)) (t:^T) : ^T =
(^T : (static member bar : (^U -> ^T) -> ^T -> ^T) f, t )
let x = foo (fun x -> (Baz 0)) (Baz 1)
I get this error
error FS0043: Method or object constructor 'bar' not found
I assume that signature of my static member can not really be unified to (^U -> ^T) -> ^T -> ^T
How can I solve this?
Looking at the previous question (i.e. switching back to the member function) and your comments, perhaps this would work:
type Baz = Baz of int with
member this.bar (f: 'a -> 'b): 'b = match this with
| Baz i -> f i
let inline foo (f: ^U -> ^T) (t:^T) =
let foo' = (^T : (member bar : (^U -> ^T) -> ^T) (t, f))
foo'
let x = foo (fun x -> (Baz 0)) (Baz 1)
// This returns Baz 0
printfn "%A" x
Compiles:
let inline f< ^T when ^T : (static member (<<<) : ^T * int -> ^T) > (x : ^T) = x <<< 1
Does not compile:
let inline f< ^T when ^T : (static member (>>>) : ^T * int -> ^T) > (x : ^T) = x >>> 1
Errors:
Attempted to parse this as an operator name, but failed
Unexpected symbol '>' in member signature. Expected ')' or other token.
A type parameter is missing a constraint 'when ^T : (static member ( >>> ) : ^T * int32 -> ^T)'
Adding spaces doesn't help; this line yields the same compiler errors:
let inline f< ^T when ^T : (static member ( >>> ) : ^T * int -> ^T) > (x : ^T) = x >>> 1
I've searched both the documentation and the specification, to no avail. Is this a bug? Is there some way to include the > characters in the member signature?
Sure looks like a bug. It's ugly, but one workaround is to use the long form of the operator name:
let inline f< ^T when ^T : (static member op_RightShift : ^T * int -> ^T)> (x : ^T) =
x >>> 1
Do you even need an explicit constraint? This works just as well:
let inline f (x: ^T) : ^T = x >>> 1