Ковариация массива в F#

Поскольку массивы .NET являются ковариантными, в C# работает следующее:

var strArray = new string[0];
object[] objArray = strArray;

В F #, учитывая массив 'T[], как лучше всего преобразовать его в obj[] без повторного создания массива (например, Array.map box)? Я использую (box >> unbox), но он выглядит неаккуратно.


person Daniel    schedule 07.09.2011    source источник
comment
Интересно. Я не знал этого о C #. Разве это не ошибка, ставящая под угрозу безопасность статического типа?   -  person wmeyer    schedule 07.09.2011
comment
@wmeyer: stackoverflow.com/q/3516619/162396; blogs.msdn.com/b/ericlippert/archive/2007/10/17/   -  person Daniel    schedule 07.09.2011
comment
На самом деле я нахожу это очень полезным для универсальных классов, реализующих неуниверсальные интерфейсы.   -  person Daniel    schedule 07.09.2011


Ответы (3)


box >> unbox

кажется хорошей идеей; O(1) и, по-видимому, выполняет свою работу.

Подумайте также о том, чтобы не использовать эту неправильную функцию CLR. ;)

person Brian    schedule 07.09.2011
comment
Я полагаю, что мог бы использовать Array.map box, но мне кажется неправильным воссоздавать массив, когда доступна совершенно хорошая неправильная функция. ;-) - person Daniel; 07.09.2011

Как говорит Брайан, в box >> unbox нет ничего плохого, за исключением того факта, что ковариация массива изначально нарушена (например, ([| "test" |] |> box |> unbox<obj[]>).[0] <- obj() вызовет исключение ArrayTypeMismatchException при попытке выполнить присваивание).

Вместо этого вам, вероятно, лучше рассматривать string[] как obj seq, что совершенно безопасно (хотя для этого по-прежнему требуется упаковка и распаковка в F#, поскольку F# не поддерживает общую ко/противовариантность). К сожалению, вы потеряете произвольный доступ, если пойдете по этому пути.

person kvb    schedule 07.09.2011
comment
И к сожалению мне нужен произвольный доступ. Возможно, более важный вопрос заключается в том, как реализовать неуниверсальный член интерфейса массива типов. Мой интерфейс представляет собой постраничный список элементов (элементы плюс информация о подкачке). Таким образом, элемент массива может быть любого типа. В противном случае я бы изобрел еще один интерфейс для представления элементов массива. Думаю, это предпочтительное решение. - person Daniel; 07.09.2011
comment
@Daniel - в идеале BCL должен содержать тип ReadOnlyArray<'t>, который был бы ковариантным и производным от которого был бы 't[]. Поскольку это не так, вы ограничены некоторыми менее желательными вариантами. Использование ковариации массива прекрасно, если вы помните, что никогда не записываете в преобразованный массив... - person kvb; 07.09.2011
comment
Я зарегистрировался в ReadOnlyArray, пожалуйста, проголосуйте за него! visualstudio.uservoice.com/forums/121579-visual- студия/ - person Brian; 08.09.2011

Решение Брайана мне кажется прекрасным, но действительно ли вам нужна ковариация массива?

Если у вас есть функция, которая принимает ISomething[] и хочет передать ее SomeSomething[], то она вам нужна, но если функция читает только значения типа ISomething из массива (что позволяет ковариация), то вы можете использовать хеш-тип и написать функция, которая принимает #ISomething[]. (Конечно, при условии, что вы можете изменить функцию!)

Таким образом, функция может работать с массивом любых элементов, которые реализуют ISomething, и вам не нужна ковариация массива при ее вызове. Вот пример:

type A() = 
  interface IDisposable with 
    member x.Dispose() = printfn "gone"

// Disposes the first element from an array
let disposeFirst (a:#IDisposable[]) = a.[0].Dispose()

// Call 'disposeFirst' with 'A[]' as an argument
let aa = [| new A(); new A() |]
disposeFirst aa

Мне кажется, что основная причина ковариантности массивов в .NET заключается в том, что она была нужна в то время, когда дженериков не существовало, но кажется, что F# вполне может обходиться без этой возможности.

person Tomas Petricek    schedule 07.09.2011
comment
Как бы вы реализовали член интерфейса, возвращающий obj[], с универсальным типом, имеющим член типа 'T[]? Интерфейс ничего не знает о 'T, поэтому я не вижу, как хеш-типы помогут. - person Daniel; 07.09.2011
comment
@Daniel - я не уверен, что понимаю сценарий. Можете ли вы привести пример? Хэш-тип делает функцию универсальной и позволяет ей принимать не только obj[], но и foo[], где foo наследуется от obj, что то же самое, что и ковариация массива... - person Tomas Petricek; 08.09.2011
comment
Интерфейс (не универсальный) имеет член типа obj[]. Препятствие состоит в том, что использование хеш-типа делает объемлющий тип универсальным. Однако класс, реализующий интерфейс является универсальным (соответствующий член имеет тип 'T[]). - person Daniel; 08.09.2011
comment
@Daniel - о, я вижу - вы хотите установить значение поля. Я (ошибочно) предполагал, что вы просто хотите вызвать какую-то функцию, которая принимает obj[]. В таком случае я думаю, что решение Брайана — единственный разумный вариант. - person Tomas Petricek; 08.09.2011