I'm trying to write a method in F# that returns a new instance of a generic type based upon the type of a value passed into the method. In FSI:
open System.Collections.Generic
type AttributeIndex<'a>() =
inherit SortedDictionary<'a, HashSet<int array>>()
let getNewIndexForValue (value: obj) : AttributeIndex<_> =
match value with
| :? string -> new AttributeIndex<string>()
| :? int -> new AttributeIndex<int>()
| :? float -> new AttributeIndex<float>()
| :? bool -> new AttributeIndex<bool>()
| _ -> failwith "bad value type"
let someIndexes = [
getNewIndexForValue 9;
getNewIndexForValue "testString";
getNewIndexForValue false;
getNewIndexForValue 5.67;
]
someIndexes;;
This does not compile with error
error FS0001: Type mismatch. Expecting a
AttributeIndex<string>
but given a
AttributeIndex<int>
The type 'string' does not match the type 'int'
I can't seem to figure out how to get an instance of Attribute with the type param based upon the type of the value parameter passed into the function. I've tried a couple other variation, but all result in the same type mismatch error. Any help would be greatly appreciated. Thanks!!
UPDATE:
Thanks for the answers. I get it now. So now I'm trying to have my 'getNewIndexForValue' return a non-generic base AttributeIndex class. I've implemented this in C# and it compiles and runs as I expect:
using System;
using System.Collections.Generic;
namespace Example {
public class AttributeIndexBase : SortedDictionary<object, HashSet<int[]>> { }
public class AttributeIndex<T> : AttributeIndexBase {
public void AddToIndex(T indexValue, int[] recordKey) {
if (!this.ContainsKey(indexValue)) {
this.Add(indexValue, new HashSet<int[]> { recordKey });
}
else {
this[indexValue].Add(recordKey);
}
}
}
class Program {
static int Main(string[] args) {
var intIdx = GetIndexForValue(32);
var boolIdx = GetIndexForValue(true);
var doubleIdx = GetIndexForValue(45.67);
var someIndexes = new List<AttributeIndexBase> {
intIdx,
boolIdx,
doubleIdx
};
return 0;
}
static AttributeIndexBase GetIndexForValue(object value) {
switch (value.GetType().Name.ToLower()) {
case "int32" :
return new AttributeIndex<int>();
case "single" :
return new AttributeIndex<float>();
case "double" :
return new AttributeIndex<double>();
case "boolean" :
return new AttributeIndex<bool>();
default :
throw new ArgumentException("The type of the value param is not allowed", "value");
}
}
}
}
However, trying to port this to F# does not work:
module example
open System
open System.Collections.Generic
type AttributeIndexBase() =
inherit SortedDictionary<obj, HashSet<int array>>()
type AttributeIndex<'a>() =
inherit AttributeIndexBase()
let getNewIndexForValueType (value: ValueType) : AttributeIndexBase =
match value with
| :? int -> new AttributeIndex<int>()
| :? float -> new AttributeIndex<float>()
| :? bool -> new AttributeIndex<bool>()
| _开发者_StackOverflow中文版 -> failwith "bad value type"
let someIndexes = [
getNewIndexForValueType 9;
getNewIndexForValueType false;
getNewIndexForValueType 5.67;
]
This seems to me to be a pretty straight port (except in the F# version I'm constraining it to just ValueType), however I get error:
error FS0001: This expression was expected to have type
AttributeIndexBase
but here has type
AttributeIndex<int>
Does F# really just not support a cast of child to parent type like C# does?
Your latest code will almost work, but F# requires you to explicitly upcast to AttributeIndexBase
in this case. There are at least two ways to do this: you could use the upcast
keyword or you could use the :>
conversion operator.
The first option would look like this:
let getNewIndexForValueType (value: ValueType) : AttributeIndexBase =
match value with
| :? int -> upcast new AttributeIndex<int>()
| :? float -> upcast new AttributeIndex<float>()
| :? bool -> upcast AttributeIndex<bool>()
| _ -> failwith "bad value type"
While the second would look like this:
let getNewIndexForValueType (value: ValueType) : AttributeIndexBase =
match value with
| :? int -> new AttributeIndex<int>() :> _
| :? float -> new AttributeIndex<float>() :> _
| :? bool -> new AttributeIndex<bool>() :> _
| _ -> failwith "bad value type"
The fact that you can't come up with a generic parameter for the function return value is a red flag:
let getNewIndexForValue (value: obj) : AttributeIndex< ?? what goes here > =
The getNewIndexForValue
function must choose a type for the 'a
parameter; the 'a
type parameter can be either string, int, float or bool, but not all at the same time.
One possibility is to introduce a non-generic AttributeIndex
class that AttributeIndex<'a>
inherits from, and return a plain AttributeIndex
from your function. (The simplest change that make your code snippet compile would be to return obj
, although I assume that's not going to be a permanent fix.)
The problem is that you are trying to get a non-generic method to return different bound generic types. This is just not possible. It's the equivalent of trying the following in C#
?WhatGoesHere? Create(object source) {
if ( source is int) {
return new AttributeIndex<int>((int)source);
} else if ( source is string ) {
return new AttributeIndex<string>((string)source);
}
...
}
Really the only valid type which can be inserted for ?WhatGoesHere?
is object
because it must be given a concrete type.
There are a couple of ways to make this experience better. The most straight forward is to add a non-generic AttributeIndex
base class and have AttributeIndex<T>
inherit from this value.
type AttributeIndex =
member GetValue : object -> HashSet<int array>
type AttributeIndex<'a> =
inherit AttributeIndex
inherit IDictionary<'a, HashSet<int array>>
I originally posted this as a comment, but in fact it is 'the answer':
What do you intend to do with 'someIndexes' later?
Until you specify that, any 'answer' is just idle speculation. One can't provide prescriptive advice about how to get past this 'type error' because there are many possible ways to get past it depending on how you eventually intend to consume the data. I think @JaredPar's answer is the most likely to be what you want, but that's basically me trying to be psychic (along with every other answer).
Once you specify 'what you want to do' with each index, then that will imply a common interface or base class that all indexes need to support, and that will be your answer.
Everyone else is right, but I think I can add a little more explanation.
Every expression in F# has a type. In F#, "if a then b else c" is an expression, returning either b or c. For this to work, b and c must have the same type. "match ..." is also an expression, returning the value of one of its clauses. That implies that every clause must have the same type.
Your match expression tries to violate this. Each clause has a different type. In your case, these are different applications of the same generic type... but those are still different types. This is no different than trying to have one clause return an int and another return a string...
Conceptually, that is the root of your problem. Until you fix the match expression, you won't be able to define a function that returns its value.
As others have pointed out, one of the ways to fix this is to use a common base type for the match expression's type, such as obj or a non-generic AttributeIndex.
Let's take a look at this line:
let getNewIndexForValue (value: obj) : AttributeIndex<_> =
The underscore in AttributeIndex<_>
means to the compiler: Hey, figure out which type to insert. It basically just saves some keystrokes. If you return an AttributeIndex<string>
, the compiler will infer _
is string
, or bool
or int
respectively.
What you do is returning different types here. It's not about a certain type but any type to be allowed. This basically differs from Java's generic wildcards.
Java's
List<?>
is a list of any possible values (likeobject
would do).F#'s
_ list
is a list of exactly one type.
What you want is the Java wildcard, and under .NET, you express there through co/contravariant types.
Therefore, you need AttributeIndex<obj>
.
精彩评论