【问题标题】:Type mismatch error. F# type inference fail?类型不匹配错误。 F# 类型推断失败?
【发布时间】:2011-04-01 18:00:57
【问题描述】:

我正在尝试在 F# 中编写一个方法,该方法根据传递给该方法的值的类型返回一个泛型类型的新实例。在 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;;

这不会编译错误

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

我似乎无法弄清楚如何根据传递给函数的值参数的类型来获取具有类型参数的 Attribute 实例。我尝试了其他几个变体,但都导致相同的类型不匹配错误。任何帮助将不胜感激。谢谢!!

更新:

感谢您的回答。我现在明白了。所以现在我试图让我的 '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# 版本中,我将其限制为仅 ValueType),但是我得到了错误:

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

F# 真的不支持像 C# 那样将子类型转换为父类型吗?

【问题讨论】:

标签: .net f# functional-programming type-inference f#-interactive


【解决方案1】:

您无法为函数返回值提供通用参数这一事实是一个危险信号:

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

getNewIndexForValue 函数必须为'a 参数选择一个类型; 'a 类型参数可以是 string、int、float 或 bool,但不能同时为所有。

一种可能性是引入AttributeIndex&lt;'a&gt; 继承自的非泛型AttributeIndex 类,并从您的函数返回一个普通的AttributeIndex。 (使您的代码 sn-p 编译的最简单更改是返回 obj,尽管我认为这不会是永久修复。)

【讨论】:

  • 谢谢 Tim、JaredPar、Jason 和 Dario:我现在明白了。现在我在 C# 中做了并且必须使用基本 AttributeIndex,这绝对是有道理的。但是,当我再次尝试在 F# 中执行相同操作时,它仍然无法编译。我用我的发现和新例子更新了我的问题。谢谢大家的帮助!!
【解决方案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&lt;T&gt; 继承此值。

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

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

【讨论】:

    【解决方案3】:

    让我们看看这一行:

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

    AttributeIndex&lt;_&gt; 中的下划线对编译器意味着:嘿,找出要插入的类型。它基本上只是节省了一些击键。如果你返回一个AttributeIndex&lt;string&gt;,编译器会推断_string,或者bool或者int

    你所做的是在这里返回不同的类型。这不是关于某种类型,而是任何类型被允许。这与 Java 的通用通配符基本不同。

    • Java 的List&lt;?&gt;任何可能值的列表(就像object 一样)。

    • F# 的 _ list 只是一个类型的列表。

    您想要的是 Java 通配符,而在 .NET 下,您可以通过协/逆变类型来表达。

    因此,您需要AttributeIndex&lt;obj&gt;

    【讨论】:

    • 请注意,F# 不支持变体,即使支持,在 .NET 中变体也只能与接口和委托一起使用。
    【解决方案4】:

    我最初将此作为评论发布,但实际上它是“答案”:

    你打算做什么 'someIndexes' 稍后?

    在您指定之前,任何“答案”都只是空洞的猜测。无法就如何克服这种“类型错误”提供规范性建议,因为有许多可能的方法可以克服它,具体取决于您最终打算如何使用数据。我认为@JaredPar 的答案最有可能是您想要的,但这基本上是我试图成为通灵者(以及其他所有答案)。

    一旦你为每个索引指定了“你想要做什么”,那么这将意味着所有索引都需要支持的通用接口或基类,这就是你的答案。

    【讨论】:

    • 嗨,Brian:上面的代码只是从我已经编写的大量代码中创建的一个小示例。本质上,我正在创建的是一个通用的内存索引管理器。这个想法是您将对象层次结构传递给它,并要求对象层次结构中的每个对象都有一个“id”字段。然后,我为对象中的每个字符串或值类型创建索引。查询索引时,您会返回一个 int[] 列表(代表每个级别的“id”字段)。我已经能够在 C# 中使用 380 万个异构对象来做到这一点,每个对象都有 3 层深的层次结构。
    • 我很乐意分享我的整个代码,看看你的想法,以及我是否可以用一种完全不同的、更实用的方式来处理它。但是,* 可能不是这样做的地方。如果你给我一个方法把我的代码给你,我会把它发过来。
    【解决方案5】:

    其他人都是对的,但我想我可以再补充一点。

    F# 中的每个表达式都有一个类型。在 F# 中,“if a then b else c”是一个表达式,返回 b 或 c。为此,b 和 c 必须具有相同的类型。 "match ..." 也是一个表达式,返回其中一个子句的值。这意味着每个子句必须具有相同的类型。

    您的匹配表达式试图违反这一点。每个子句都有不同的类型。在您的情况下,这些是相同泛型类型的不同应用程序......但它们仍然是不同的类型。这与试图让一个子句返回一个 int 而另一个子句返回一个字符串没有什么不同......

    从概念上讲,这是问题的根源。在修复匹配表达式之前,您将无法定义返回其值的函数。

    正如其他人所指出的,解决此问题的方法之一是使用通用基本类型作为匹配表达式的类型,例如 obj 或非泛型 AttributeIndex。

    【讨论】:

      【解决方案6】:

      您的最新代码几乎可以工作,但在这种情况下,F# 要求您明确地向上转换为 AttributeIndexBase。至少有两种方法可以做到这一点:您可以使用upcast 关键字,也可以使用:&gt; 转换运算符。

      第一个选项如下所示:

      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"
      

      【讨论】:

      • Kvb,谢谢!!我爱你!!这两种解决方案都很有效。现在看来很明显了。
      • +1 表示我以前从未见过的两件事:向上转换或 :> 下划线。