Ковариация на масива във 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

Решението на Brian ми изглежда добре, но наистина ли имате нужда от ковариация на масив?

Ако имате функция, която приема 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