2

我想在F#中編寫一個方法,它根據傳入方法的值的類型返回一個泛型類型的新實例。在FSI:類型不匹配錯誤。 F#類型推斷失敗?

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;; 

這不會錯誤編譯

error FS0001: Type mismatch. Expecting a AttributeIndex<string>
but given a AttributeIndex<int>
The type 'string' does not match the type 'int'

我似乎無法弄清楚如何獲得屬性的一個實例,與基於的類型的Param類型傳遞給函數的值參數。我已經嘗試了其他一些變體,但都導致相同類型的不匹配錯誤。任何幫助將不勝感激。謝謝!!

UPDATE:

感謝您的答案。我現在明白了。所以現在我試圖讓我的'getNewIndexForValue'返回一個非泛型的基礎AttributeIndex類。我在C#中實現這一點,它編譯和運行如我所料:

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"); 
      } 
     } 
    } 
} 

然而,試圖端口這F#不工作:

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>() 
      | _ -> failwith "bad value type" 

    let someIndexes = [ 
     getNewIndexForValueType 9; 
     getNewIndexForValueType false; 
     getNewIndexForValueType 5.67; 
    ] 

在我看來這是一個很直口(除F#版本我約束它只是值類型),但我得到的錯誤:

error FS0001: This expression was expected to have type AttributeIndexBase
but here has type AttributeIndex<int>

不會F#真的只是不支持的投孩子到C#的父母類型呢?

+1

你有什麼打算與後來的「someIndexes」呢? – Brian 2010-08-20 16:10:23

+2

要回答最後一個問題,請閱讀MSDN中的「Casting Object Types/Upcasting」:http://msdn.microsoft.com/en-us/library/dd233220.aspx – Jason 2010-08-20 20:29:56

回答

5

你最近的代碼幾乎可以工作,但在這種情況下,F#要求你顯式上傳到AttributeIndexBase。至少有兩種方法可以實現這一點:您可以使用upcast關鍵字,也可以使用:>轉換運算符。

第一個方案是這樣的:

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" 

雖然第二是這樣的:

let getNewIndexForValueType (value: ValueType) : AttributeIndexBase = 
    match value with 
    | :? int -> new AttributeIndex<int>() :> _ 
    | :? float -> new AttributeIndex<float>() :> _ 
    | :? bool -> new AttributeIndex<bool>() :> _ 
    | _ -> failwith "bad value type" 
+0

Kvb,謝謝!我愛你!! 這兩種解決方案都像一個魅力。現在看起來很明顯。 – NathanD 2010-08-20 20:16:05

+1

對於我以前從未見過的兩件事:+1:upcast或:>突出顯示。 – Jason 2010-08-20 20:25:57

4

,你不能拿出爲函數的返回值的泛型參數的事實是紅旗:

let getNewIndexForValue (value: obj) : AttributeIndex< ?? what goes here > = 

getNewIndexForValue功能必須選擇爲'a參數類型; 'a類型參數可以是string,int,float或bool,但不能全部同時使用。

一種可能性是引入非通用類AttributeIndexAttributeIndex<'a>繼承,並從你的函數返回一個普通AttributeIndex。 (使你的代碼片段編譯的最簡單的改變是返回obj,雖然我認爲這不會是一個永久性的修復。)

+0

感謝Tim,JaredPar,Jason和Dario: 現在明白了。現在我在C#中完成了,而且必須使用基礎AttributeIndex。但是,當我嘗試再次在F#中執行相同的操作時,它仍然不能編譯。我用我的發現和新例子更新了我的問題。 感謝大家的幫助! – NathanD 2010-08-20 18:14:21

2

問題是你試圖得到一個非泛型的方法來返回不同的綁定的泛型類型。這是不可能的。這是試圖相當於在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); 
    } 
    ... 
} 

真的可以插入?WhatGoesHere?唯一有效的類型是object,因爲它必須給予具體類型。

有幾種方法可以使這種體驗更好。最直接的方法是添加一個非通用的AttributeIndex基類,並從此值繼承AttributeIndex<T>

type AttributeIndex = 
    member GetValue : object -> HashSet<int array> 

type AttributeIndex<'a> = 
    inherit AttributeIndex 
    inherit IDictionary<'a, HashSet<int array>> 
0

讓我們來看看這條線:

let getNewIndexForValue (value: obj) : AttributeIndex<_> = 

AttributeIndex<_>下劃線的意思編譯器:嘿,找出要插入的類型。它基本上只是節省了一些擊鍵。如果您返回AttributeIndex<string>,則編譯器將分別推斷出_stringboolint

你所做的是在這裏返回不同的類型。這不是關於某種類型而是任何類型被允許。這與Java的通用通配符基本不同。

  • Java的List<?>任何可能值(如object會做)的列表。

  • F#的_ list只是一種類型的列表。

你想要的是Java通配符,在.NET下,你可以通過co/contravariant類型表示。

因此,您需要AttributeIndex<obj>

+3

請注意,F#不支持方差,即使這樣做,在.NET中,方差也只能用於接口和委託。 – kvb 2010-08-20 16:26:13

2

我最初發布這個作爲一個評論,但實際上卻是「答案」:

What do you intend to do with 'someIndexes' later?

除非你指定,任何「答案」僅僅是遊資炒作。我們無法提供關於如何克服這種「類型錯誤」的說明性建議,因爲根據您最終打算如何使用這些數據,有很多可能的方式可以通過它。我認爲@ JaredPar的答案是最有可能成爲你想要的,但這基本上是我試圖成爲心靈(以及其他答案)。

一旦您爲每個索引指定了「想要做什麼」,那麼這將意味着所有索引都需要支持的通用接口或基類,這將是您的答案。

+0

嗨布萊恩: 上面的代碼只是一個從我已經編寫的更大的代碼集創建的一個小例子。從本質上講,我創建的是一個通用的內存索引管理器。這個想法是,你傳遞一個對象層次結構,要求對象層次結構中的每個對象都有一個「id」字段。然後,我爲對象中的每個字符串或值類型創建索引。查詢索引時,會返回一個int []列表(表示每個級別的「id」字段)。 我已經可以在C#中完成這項工作了,每年有380萬個異構對象,每個對象都有一個3層深的層次結構。 – NathanD 2010-08-20 18:32:10

+0

我很想分享我的整個代碼,看看您的想法,以及是否可以以完全不同的,更實用的方式進行討論。但是,Stackoverflow可能不是做這件事的地方。如果你給我一種方法讓我的代碼給你,我會發送它。 – NathanD 2010-08-20 18:38:07

2

其他人都是對的,但我想我可以多加一點解釋。

F#中的每個表達式都有一個類型。在F#中,「如果一個then b else c」是一個表達式,則返回b或c。爲此,b和c必須具有相同的類型。 「match ...」也是一個表達式,返回其中一個子句的值。這意味着每個子句必須具有相同的類型。

您的匹配表達式試圖違反此規定。每個子句都有不同的類型。在你的情況下,這些是同一泛型類型的不同應用程序......但這些仍然是不同的類型。這與嘗試讓一個子句返回一個int而另一個返回一個字符串沒有什麼不同......

從概念上講,這是問題的根源。在修復匹配表達式之前,您將無法定義返回其值的函數。

正如其他人已經指出的,解決這個問題的方法之一是爲匹配表達式的類型使用通用的基本類型,比如obj或非泛型的AttributeIndex。