This works in F#4.0:
type Something<'a, 'b when 'b :> seq<'b>>() =
This doesn't:
type Something<'b when 'b :> seq<'b>, 'a>() =
Unexpected symbol ',' in type name. Expected '>' or other token.
What's the reason that the order of the type constraint matter?
Because it is in the spec - the relevant part is this (from the start of section 5):
typar-defns:= < typar-defn, ..., typar-defn typar-constraints_opt>
the constraints need to go at the end.
In this typar-constraints must always start with when and can't appear anywhere else.
Type constraints and type arguments are two different things. In your example, 'a and 'b are two type arguments, and when 'b :> seq<'b> is the (only) constraint.
Now, the way you've written your first example it seems to suggest that the type argument definitions have something to do with the type constraints, but that's only in the appearance. Note this (working) code:
type Something<'b, 'a when 'b :> seq<'b>>() =
member this.A (a : 'a, b : 'b) =
()
First, you define all the type arguments. Only afterwards come the type constraints, and the constraint still applies to 'b, not 'a - while it looks a bit confusing, the constraint expression isn't 'a when 'b :> seq<'b>, it's just the when 'b :> seq<'b>.
This is actually pretty much the same as in C#, another .NET language:
public class Something<TA, TB> where TA: someConstraint where TB: someOtherConstraint
The constraints are more visually separate in C#, so people don't tend to make the mistake you made in F#.
Along with the above answers, the ordering allows the constraints to depend on multiple types. e.g.
type Something<'a, 'b when 'b :> seq<'a>>() =
Note:seq<'a> not seq<'b>
Related
I'm wish to use F#'s type inference to infer the types of my code simply by the methods/functions that are called.
This CAN be achieved quite nicely by using extension methods of the form...
[<Extension>]
type AttributeExtensions =
[<Extension>]
static member ``BroadcastOffset``<'a, 'b when 'a :> ``BroadcastOffset``<'b>> (this: 'a) =
this.``
when I have code that reads
x.BroadcastOffset()
type inference will kick in and infers that x, must indeed be of type BroadcastOffset<'a>.
A small fly in the ointment though is F#'s own types for example, if I write
x.Value
F# will not infer its an Option<_>, thats understandable, but I can use the same extension trick to get there?
[<Extension>]
type AttributeExtensions =
[<Extension>]
static member Value<'a,'b when 'a :> Option<'b>> (this: 'a) =
this.Value
and that SHOULD in theory mean
x.Value()
should trigger the inference of x being an Option<_>
sadly this ISNT the case, because F# rejects the extension method as an invalid constraint because Option<_> is sealed and thus 'a can only have 1 solution.
True 'a CAN only have 1 solution but, in my book that does NOT make the constraint invalid, it is perfectly valid, just trivial to infer, and because of this overzelous rejection, I am frustrated in making "Value" trigger type inference.
any ideas how to get around it? (apart from changing the F# compiler myself).
(I can of course define a function 'Value' instead, but my intention is to use F# to infer types from "methods", and extension methods fit the requirement where simple functions don't...I have my reasons)
I got compile errors using your code and then factored out what I think you are after into a function, then used the function in the extension. This allowed me to call .Value() and it work out that it was of type 'a option which is what I think you were after?
open System.Runtime.CompilerServices
let v (x : 'a option) = x.Value
[<Extension>]
type AttributeExtensions =
[<Extension>]
static member Value(this) = v(this)
let myFunc o = o.Value()
In following case
let a = [1]
let f x = x
let b = a |> List.map f
a is an int list. We call List.map so for sure f must be a int->? function. Why is f still typed as 'a ->'a instead of int->int?
If this is how type inference (not)works in F# i see little benefits for it.
Is there an workaround without specifying the type for x in f explicitly?
Lets imagine x has type (A*B*C list ( D option, E list)))
Also another interesting thing:
open System.Collections.Generic
let d = new Dictionary()
d.[1] ="a"
This does not work unless the "new" keyword is removed.
Type inferencing works just fine here. f is inferred as having the type 'a -> 'a, which means that it'll take a value of the generic type 'a and return a value of the same generic type 'a.
In this case, type inferencing kicks in when you use it. Because a has the type int list, and because List.map has the signature ('T -> 'U) -> 'T list -> 'U list, it can infer types from the expression
let b = a |> List.map f
In this case, the 'T list argument is a, so it has the type int list. This means it now knows that 'T is int.
The function passed to List.map has the general form 'T -> 'U, but in this case we pass f, which has the form 'a -> 'a, which is equivalent to 'T -> 'T. Because of this, the compiler understands that in this case, 'T and 'U are the same types.
This means that it can infer that the return type 'U list must also be int list.
The function f is typed 'a -> 'a because it works at every type, not just int:
> f "foo";;
val it = "foo" : string
> f 0.8;;
val it = 0.8 : float
This an advantage: the generic type 'a -> 'a gives you the freedom to use f when it is safe to do so, as opposed to only at the type int -> int.
To add a little something to the fine answers already listed here.
There is one circumstance where you will want to not use the generic inferred type. The generic version of f will be slower than the type specific version. But F# has a keyword for taking care of that for you:
let inline f x = x
Which tells the compiler to use the type specific version of any operations you apply to x. The inferred type will remain generic. You will only need this in time critical section of code.
As noted elsewhere, the generic inference is a useful thing and you'll probably grow to like it.
Is there any way to determine whether a given type parameter satisfies the F# comparison constraint through reflection?
I would suspect not, since the expression
typedefof<Set<_>>.MakeGenericType [| typeof<System.Type> |]
appears to yield no errors. Still, I would like to hear some authoritative opinion on this.
Quoting from Don Syme's thorough post on equality and comparison constraints:
The constraint type : comparison holds if:
if the type is a named type, then the type definition doesn't have the NoComparison attribute; and
the type definition implements System.IComparable; and
any “comparison dependencies” of the type also satisfy tyi : comparison
The constraint 'T when 'T :> IComparable can be encoded in CIL and reflected upon, whereas neither is true of 'T when 'T : comparison.
Since the two constraints are not equivalent, marking comparable types with the IComparable constraint is a bit misleading since it would make it impossible to distinguish between the two using reflection.
There's a similar relationship between the equality constraint and IEquatable<_>.
EDIT
Jack's mention that the comparison constraint could be encoded in F# metadata prompted me to look at the metadata reader in PowerPack. It can be used to detect the constraint:
open Microsoft.FSharp.Metadata
let setEntity = FSharpAssembly.FSharpLibrary.GetEntity("Microsoft.FSharp.Collections.FSharpSet`1")
for typeArg in setEntity.GenericParameters do
printfn "%s - comparison=%b"
typeArg.Name
(typeArg.Constraints |> Seq.exists (fun c -> c.IsComparisonConstraint))
Here's a contrived example that shows the disparity between implementing IComparable and satisfying comparison:
type A() =
interface IComparable with
member __.CompareTo(_) = 0
[<NoComparison>]
type B() =
inherit A()
type C<'T when 'T : comparison>() = class end
type D<'T when 'T :> IComparable>() = class end
let c = C<B>() //ERROR
let d = D<B>() //OK
Taking the idea of phantom types explained in this post Implementing phantom types in F# , I'm trying to constrain the input parameter of an abstract type member, and more generally any type member. This may be impossible, and I'm happy to get that message.
Here is what I am attempting:
// my phantom type
type Ascending = interface end
type Descending = interface end
type HeapOrder =
inherit Ascending
inherit Descending
// my type
type IHeap<'a when 'a : comparison> =
inherit System.Collections.IEnumerable
inherit System.Collections.Generic.IEnumerable<'a>
...
abstract member Order : HeapOrder with get
...
// ...and one or the other of these inherited types :
type IHeap<'c, 'a when 'c :> IHeap<'c, 'a> and 'a : comparison> =
inherit IHeap<'a>
// or:
type IHeap<'a, 'd when 'a : comparison and 'd :> HeapOrder> =
inherit System.Collections.IEnumerable
inherit System.Collections.Generic.IEnumerable<'a>
type IHeap<'c, 'a, 'd when 'c :> IHeap<'c, 'a, 'd> and 'a : comparison and 'd :> HeapOrder> =
inherit IHeap<'a>
...
// ...and the member I want to constrain, so that the HeapOrder of the
// input parameter matches the HeapOrder of the instantiated type.
// Conceptually one of these alternatives (none of these build,
// but they should convey what I'm attempting):
//
abstract member Merge : ('c when 'c<'c, 'a> : (member Order when Order :> 'd))
// or
abstract member Merge : ('c when c' :> IHeap<'c, 'a, 'd>) -> 'c
// or
abstract member Merge : ('c when c' : #IHeap<'c, 'a, 'd>) -> 'c
You can't further constrain a class type parameter for a particular member, if that's what you're asking. You can introduce a new member type parameter that is independently constrained, if you like. I am not sure whether that solves your problem.
I think both Ascending and Descending should inherit from HeapOrder, instead of the other way around.
Then you can instantiate a heap with either Ascending or Descending, and the 'd should capture that fact so you can't Merge with another one with a different HeapOrder. I don't think you need separate constraints on Merge for that.
Just in case, you can constrain type parameters of abstract members by putting the when at the end. Also you have a typo in your 'c in the last two merges, the ' comes after the c.
My first thought was:
type ManyNavigationPropertyInfo<'a,'b>(cfg:ManyNavigationPropertyConfiguration<'a, 'b>) =
however it resolves 'a and 'b as obj, but it should be classes - therefore I did:
type ManyNavigationPropertyInfo<'a
when 'a : not struct,'b when 'b : not
struct>(cfg:ManyNavigationPropertyConfiguration<'a,
'b>) =
but that just throws an error saying
Unexpected symbol ',' in type name.
Expected '>' or other token.
What's the correct way for declaring such a type?
UPDATE:
My full code is:
type ManyNavigationPropertyInfo<'a,'b>(cfg:ManyNavigationPropertyConfiguration<'a, 'b>) =
member x.WithMany (expr: Expr<'a -> ICollection<'b>>) =
cfg.WithMany(ToLinq(expr))
and it comes up with 2 compiler errors saying that 'a and 'b should be not struct.
Your first thought is correct. You should be able to just write:
type ManyNavigationPropertyInfo<'a,'b>
(cfg:ManyNavigationPropertyConfiguration<'a, 'b>) =
// ...
The problem is probably somewhere later in the body of the type. From something that you wrote in the body, the compiler thinks that 'a and 'b must be of type obj (e.g. you're passing values of this type somewhere where obj is expected, or probably something more subtle).
You can try adding type annotations in the body of the class - this usually helps to find the issue, because the error message changes when you annotate the bit that F# compiler interprets differently than you expected.
To solve the immediate problem in your question - the syntax for specifying constraint is a bit different (first write all type variables and then constraints):
type ManyNavigationPropertyInfo<'a, 'b when 'a : not struct and 'b : not struct>( ... )
(But if you can post larger portion of code, maybe somebody can give a concrete advice.)