In a project I'm experimenting with, I'm constantly multiplying sequence elements. I wanted to create an operator like this:
let (.*) (a:seq<'T>) (b:seq<'T>) = Seq.map2 (*) a b
However, in FSI it returns:
val ( .* ) : a:seq<int> -> b:seq<int> -> seq<int>
And the following fails:
seq [1.0;2.0] .* seq [3.0;4.0];;
-----^^^
stdin(16,6): error FS0001: This expression was expected to have type
int
but here has type
float
How can I make the operator work on a generic seq<'T> (provided 'T supports multiplication)?
You need to add the inline keyword to your operator declaration so that the correct overload can be resolved at compile time:
let inline (.*) (a:seq<'T>) (b:seq<'T>) = Seq.map2 (*) a b
val inline ( .* ) : a:seq< ^T> -> b:seq< ^T> -> seq< ^a> when ^T : (static member ( * ) : ^T * ^T -> ^a)
Related
I'm pretty new to F#, I'm trying to write some code that does not discriminate between floats and integers, and treats them both merely as "numbers". To start off with I want a simple "add" function that will add two numbers, where each number can be an integer or a float.
This is my current approach which doesn't feel at all elegant, probably due to my ignorance of the intricacies of F#
type Number =
| Integer of int
| Float of float
let add (a: Number, b: Number) : Number =
match a with
| Integer(a) ->
match b with
| Integer(b) -> Integer(a + b)
| Float(b) -> Float(double a + b)
| Float(a) ->
match b with
| Integer(b) -> Float(a + double b)
| Float(b) -> Float(a + b)
This works, but it's a lot of code for such a simple function. In particular, if I wanted also a "subtract" function, I would have to copy/paste the entire function and just change the "+"s to "-"s, which seems really inefficient. Is there a nicer way to do this in F#?
One possible approach could be this
// Generic function to reduce two numbers into a number
// i reduces nested integers
// f reduces nested floats
// In order for i,f to be merged into single reducer
// F# would have to support higher ranked types (I think)
// but F# does not
let reduce i f (a: Number) (b: Number) : Number =
match a, b with
| Integer a, Integer b -> Integer (i a b)
| Integer a, Float b -> Float (f (float a) b)
| Float a, Integer b -> Float (f a (float b))
| Float a, Float b -> Float (f a b)
// Define common reducer functions
// Operators +,-,* and / are parametric polymorphic
// So the first ( + ) becomes an int -> int -> int
// the second ( + ) becomes an float -> float -> float
let add = reduce ( + ) ( + )
let subtract = reduce ( - ) ( - )
let multiply = reduce ( * ) ( * )
let divide = reduce ( / ) ( / )
Applying the inline keyword will cause F# to use Statically-Resolved Type Parameters (SRTP) to automatically generate two functions (one for int and one for float) at compile time.
let inline add a b = a + b
add 5 6
add 5.2 5.8
The type of this function is as shown below. Note that each parameter, as well as the return value, could have a different type (^a, ^b, or ^c). The ^ symbol indicates an SRTP.
val inline add :
a: ^a -> b: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
You could, if you wish, force both the parameters and the return value to all have the same type.
let inline add (a: ^a) (b: ^a) : ^a = a + b
This function's signature is:
val inline add :
a: ^a -> b: ^a -> ^a when ^a : (static member ( + ) : ^a * ^a -> ^a)
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?
I am trying to reinvent List.fold, List.reduce, and List.sum using pointfree inline functions for the sake of learning. Here is what I had:
let flip f y x = f x y
let rec fold folder seed = function
| [] -> seed
| h :: t -> fold folder (folder seed h) t
let inline reduce< ^a when ^a : (static member Zero : ^a) > :
(^a -> ^a -> ^a) -> ^a list -> ^a =
flip fold LanguagePrimitives.GenericZero
let inline sum< ^a when
^a : (static member (+) : ^a * ^a -> ^a) and
^a : (static member Zero : ^a)> : ^a list -> ^a =
reduce (+)
// ^ Compile error here
I know such verbose type constraints are not frequently used in F# but I am trying to learn so please bear with me.
I got a very cryptic compile error which I do not understand:
Type mismatch. Expecting a
'a -> 'a -> 'a
but given a
'a -> 'b -> 'c
A type parameter is missing a constraint 'when ( ^a or ^?269978) : (static member ( + ) : ^a * ^?269978 -> ^?269979)'
A type parameter is missing a constraint 'when ( ^a or ^?269978) : (static member ( + ) : ^a * ^?269978 -> ^?269979)'
val ( + ) : x:'T1 -> y:'T2 -> 'T3 (requires member ( + ))
Full name: Microsoft.FSharp.Core.Operators.( + )
Overloaded addition operator
x: The first parameter.
y: The second parameter.
What should I add to satisfy the compiler?
I don't know a way of doing exactly as you want, because there's no way I know of to invoke a statically constrained static member in pointfree style. Note that you are not invoking (+) of ^a and the compiler does not know how to figure that out... if you are happy using a inner helper function, you can do:
let inline sum< ^a
when ^a : (static member (+) : ^a -> ^a -> ^a) and
^a : (static member Zero : ^a) > : ^a list -> ^a =
let add x y = (^a : (static member (+) : ^a -> ^a -> ^a) (x, y))
reduce add
Note the warning
warning FS0077: Member constraints with the name 'op_Addition' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.
and indeed:
sum [1; 2; 3] leads to
System.NotSupportedException: Specified method is not supported.
at FSI_0005.it#15-1.Invoke(Int32 x, Int32 y) in ...
But if you change sum to:
let inline sum list =
reduce (+) list
sum [1; 2; 3] // 6
My unhelpful answer would be: don't do that. Inline definitions are only intended to be syntactic functions, so you're not supposed to define them in a point-free way. For instance, compare the results of trying to evaluate the following three lines:
let inline identity = id
let inline identity : ^a -> ^a = id
let inline identity< ^a> : ^a -> ^a = id
Only the final one compiles, (technically this is because it's a "type function", which is basically a function that's passed an invisible unit value behind the scenes to allow it to be treated as a generic value).
However, if you insist on doing things this way, then one way to "fix" it is to make (+) less generic; by default the arguments and return type can be of different types and the constraints introduced by this flexibility are part of what's confusing the compiler. Here's one fix:
let inline reduce< ^a, 'b when ^a : (static member Zero : ^a)> : ( ^a -> 'b -> ^a) -> ('b list -> ^a) =
flip fold LanguagePrimitives.GenericZero
let inline (+) (x:^a) (y:^a) = x + y
let inline sum< ^a
when ^a : (static member ( + ) : ^a * ^a -> ^a)
and ^a : (static member Zero : ^a)> : ^a list -> ^a =
reduce (+)
Like Vandroiy, the given reduce function doesn't compile for me. I think what you wanted for your reduce function is actually:
let inline reduce< ^a when ^a : (static member Zero : ^a) > : ((^a -> ^a -> ^a) -> ^a list -> ^a) =
flip fold LanguagePrimitives.GenericZero
Doing that I was able to replicate your given error.
However, when I changed the sum function to compile by using a simple test function:
let inline sum< ^a when
^a : (static member Zero : ^a) and
^a : (static member (+) : (^a -> ^a -> ^a)) > : ^a list -> ^a =
reduce (fun i j -> i)
and trying to call sum [1..10] results in The type 'int' does not support the operator 'get_op_Addition'. So I'm not sure this methodology will even work.
EDIT: Thanks for the hints of CaringDev, I fixed the get_op_Addition error. This seems to work. Though as to why, I'm pretty baffled since 'b is just ^a in disguise...
let inline sum< ^a when
^a : (static member Zero : ^a) and
^a : (static member (+) : ^a -> ^a -> ^a) > : ^a list -> ^a =
let inline add (x:'b) (y:'b) = (+) x y
reduce add
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
I have a function to calculate the cumulated sum of a sequence.
let cumsum<'T> = Seq.scan (+) 0 >> Seq.skip 1 >> Seq.toArray
Though it looks generic, the integer 0 makes it non-generic, and thus I cannot call the function with a sequence of floats.
Is there a generic zero that can replace my hardcoded 0, or maybe a different way of making the function generic.
You can use the GenericZero primitive but you need to make your function inline and make it explicitly a function (right now your function is written in point-free style) since in principle values cannot be made inline.
let inline cumsum s =
s |> Seq.scan (+) LanguagePrimitives.GenericZero |> Seq.skip 1 |> Seq.toArray
Note that by removing the Type parameter 'T the static member constraints are inferred automatically by the compiler:
val inline cumsum :
s:seq< ^a> -> ^b []
when ( ^b or ^a) : (static member ( + ) : ^b * ^a -> ^b) and
^b : (static member get_Zero : -> ^b)
LanguagePrimitives.GenericZero
:)