Ошибка несоответствия типов.Ошибка вывода типа F #? - PullRequest
2 голосов
/ 20 августа 2010

Я пытаюсь написать метод в 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><br> but given a AttributeIndex<int><br> The type 'string' does not match the type 'int'

Я не могу понять, как получить экземпляр Attribute с параметром типа, основанным на типе параметра значения, переданного в функцию. Я пробовал пару других вариантов, но все они приводят к одной и той же ошибке несоответствия типов. Любая помощь будет принята с благодарностью. Спасибо !!

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 #, я ограничиваю его только ValueType), однако я получаю ошибку:

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

Действительно ли F # просто не поддерживает приведение потомка к родительскому типу, как в C #?

Ответы [ 6 ]

6 голосов
/ 20 августа 2010

Ваш последний код будет почти работать, но 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"
4 голосов
/ 20 августа 2010

Тот факт, что вы не можете придумать универсальный параметр для возвращаемого значения функции, является красным флагом:

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

Функция getNewIndexForValue должна выбрать тип для параметра 'a;параметр типа 'a может быть либо string, int, float или bool, но не все одновременно.

Одна из возможностей - ввести неуниверсальный класс AttributeIndex, который AttributeIndex<'a> наследует оти верните простое значение AttributeIndex из вашей функции.(Самое простое изменение, которое сделает ваш фрагмент кода скомпилированным, это вернуть obj, хотя я предполагаю, что это не будет постоянным исправлением.)

2 голосов
/ 20 августа 2010

Все остальные правы, но я думаю, что могу добавить немного больше объяснений.

Каждое выражение в F # имеет тип.В F # выражение «если a, то b, иначе c» является выражением, возвращающим либо b, либо c.Чтобы это работало, b и c должны иметь одинаковый тип.«match ...» также является выражением, возвращающим значение одного из его предложений.Это означает, что каждое предложение должно иметь один и тот же тип.

Ваше выражение соответствия пытается это нарушить.Каждый пункт имеет свой тип.В вашем случае это разные приложения одного и того же универсального типа ... но это все еще разные типы.Это ничем не отличается от попытки заставить одно предложение вернуть int, а другое - строку ...

Концептуально, это корень вашей проблемы.Пока вы не исправите выражение соответствия, вы не сможете определить функцию, которая возвращает его значение.

Как указали другие, один из способов исправить это - использовать общий базовый тип длясоответствует типу выражения, например, obj или неуниверсальному AttributeIndex.

2 голосов
/ 20 августа 2010

Я изначально разместил это как комментарий, но на самом деле это «ответ»:

Что вы собираетесь делать 'SomeIndexes' позже?

Пока вы не укажете это, любой «ответ» - просто спекуляция. Невозможно дать предписывающий совет о том, как обойти эту «ошибку типа», потому что есть много возможных способов обойти ее, в зависимости от того, как вы в конечном итоге намереваетесь использовать данные. Я думаю, что ответ @ JaredPar, скорее всего, будет тем, что вы хотите, но в основном это я пытаюсь быть экстрасенсом (наряду с любым другим ответом).

Как только вы укажете «что вы хотите делать» с каждым индексом, это будет означать общий интерфейс или базовый класс, который должны поддерживать все индексы, и это будет ваш ответ.

2 голосов
/ 20 августа 2010

Проблема в том, что вы пытаетесь получить неуниверсальный метод для возврата различных связанных универсальных типов. Это просто невозможно. Это эквивалентно попытке сделать следующее в 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 голосов
/ 20 августа 2010

Давайте посмотрим на эту строку:

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

Подчеркивание в AttributeIndex<_> означает для компилятора: Эй, выясните, какой тип вставить . Это в основном просто экономит некоторые нажатия клавиш. Если вы вернете AttributeIndex<string>, компилятор выведет _, равное string, или bool или int соответственно.

То, что вы делаете, возвращает здесь различные типы. Речь идет не о определенного типа , а о любом типе . Это в основном отличается от универсальных подстановочных знаков Java.

  • Java * List<?> - это список любых возможных значений (как и object).

  • F # _ list - список ровно одного типа.

То, что вы хотите, это подстановочный знак Java, и в .NET вы выражаете это через ко / контравариантные типы.

Следовательно, вам нужно AttributeIndex<obj>.

...