Ошибка несоответствия типов. Ошибка вывода типа F#?

Я пытаюсь написать метод в 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'

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

ОБНОВИТЬ:

Спасибо за ответы. Я получаю это сейчас. Так что теперь я пытаюсь, чтобы мой 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#?

6 ответов

Решение

Your latest code will almost work, but F# requires you to explicitly upcast to AttributeIndexBase в этом случае. There are at least two ways to do this: you could use the upcast keyword or you could use the :> оператор преобразования.

Первый вариант будет выглядеть так:

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"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • в Java List<?> список возможных значений (например, object сделал бы).

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

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

Поэтому вам нужно AttributeIndex<obj>,

Другие вопросы по тегам