Можно ли использовать Type в качестве словарного ключа в Swift?

Я делаю ферму, где все, что можно выращивать, соответствует протоколу Growable. Когда вы сажаете растение, вы называете эту функцию:

myFarm.planting<T: Growable>(qty: Int, of: T.Type) -> Farm

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

var crops = [Growable.Type: Int]

Проблема в том, что даже если я сделаю протокол Growable наследованием от Hashable, это не поможет растущему типу стать Hashable.

Другими словами, даже если я добавлю расширение к Growable вот так:

extension Growable {
    static func hashValue {
        // return some hash
    }
}

... все же Growable type не является Hashable, поскольку протокол Hashable касается только экземпляров типов, но не самих типов.

Что ж, обычно я сдаюсь и говорю: «Я глуп, не пытайся делать это дальше».

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

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

Примечание: я считаю, что сами типы должны иметь возможность статически придерживаться протоколов, функции которых не объявлены как статические, поскольку почему отправитель сообщения должен заботиться о том, является ли отвечающая сущность бессмертным Богом или эфемерным существом, созданным в некоторых Образ Бога?


person CommaToast    schedule 18.10.2017    source источник


Ответы (2)


Можно ли использовать Type в качестве словарного ключа в Swift?

Что ж, это возможно, вот один способ:

protocol Growable { ... }

struct S : Growable { ... }
class C : Growable { ... }

extension Dictionary where Key : LosslessStringConvertible
{
   subscript(index: Growable.Type) -> Value?
   {
      get
      {
         return self[String(describing: index) as! Key]
      }
      set(newValue)
      {
         self[String(describing: index) as! Key] = newValue
      }
   }
}

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
print(d)

печатает:

["C": 24, "S": 42]

Если вы измените определение subscript на:

subscript(index: Any.Type) -> Value?

вы, конечно, можете использовать любой тип в качестве ключа:

var d : [String:Int] = [:]
d[S.self] = 42
d[C.self] = 24
d[type(of:d)] = 18
print(d)

печатает:

["C": 24, "S": 42, "Dictionary<String, Int>": 18]

Я оставлю это вам решать, разумно, но очевидно, что это возможно.

[Примечание: вы не можете ограничить Key значением String, поэтому используется протокол LosslessStringConvertible; может быть лучший выбор, стандартная библиотека Swift - движущаяся цель ...]

HTH

person CRD    schedule 18.10.2017
comment
Да, это то, что я сделаю. На данный момент мне нравится расширять Dictionary, за исключением того, что я, вероятно, попытаюсь сделать более общий протокол StaticHashable и сделаю return index.hashValue в расширении. На самом деле я бы предпочел, чтобы типы были неотличимы от экземпляров на каком-то уровне ... может быть, это Swift 5, хех. - person CommaToast; 19.10.2017

Вы можете сделать шаг назад и пересмотреть свой дизайн. Вы можете моделировать свои Growable заводы, используя перечисления, еще одну мощную функцию Swift. Например:

protocol Growable {
    /* ... */
}

enum Vegetable: String, Hashable, Growable {
    case carrot, lettuce, potato /* ... */
}

enum Mushroom: String, Hashable, Growable {
    /* ... */
}

struct Farm {
    var crops = [AnyHashable: Int]()
    mutating func plant<T: Growable & Hashable>(qty: Int, of growable: T) {
        crops[growable] = qty
    }
}

Использование Hashable в качестве конкретного типа не поддерживается, поэтому нам нужно вместо этого использовать структуру стирание типа AnyHashable - протоколы с требованиями к типам Self или могут быть сложными для быть правым!

Использование:

var myFarm = Farm()
myFarm.plant(qty: 10, of: Vegetable.carrot)
myFarm.plant(qty: 20, of: Vegetable.lettuce)
myFarm.plant(qty: 30, of: Vegetable.potato)

print(myFarm.crops)

[AnyHashable (Овощ. Картофель): 30, AnyHashable (Овощ. Морковь): 10, AnyHashable (Овощ. Салат): 20]


Хэшируемые метатипы. Что касается вашего оригинального дизайна, правильным способом выразить свое намерение было бы:

extension Growable.Type: Hashable {
    /* make this meta thing hashable! */
}

т.е. создание соответствующего метатипа Hashable, но расширение метатипов еще не поддерживается Swift;)

person Paulo Mattos    schedule 18.10.2017
comment
Мне нравится идея перечисления. Другой вариант - разделить управление посевами на Field Тип. Это разделило бы логику и потенциально позволило бы каждому Field ограничивать то, Crops он может расти. - person GetSwifty; 18.10.2017
comment
Дело в том, что протокол Growable требует сохраненных свойств, таких как regrowTime, и наследует другой протокол, который также требует name и kind сохраненных свойств. Сейчас это статические значения, определенные по-разному в каждом struct, который поддерживает Growable. Если я сделал перечисление, поскольку перечисления не могут иметь сохраненных свойств, как я могу структурировать Growable так, чтобы я был уверен, что каждый случай перечисления даст мне время восстановления, имя и вид? - person CommaToast; 19.10.2017
comment
Также печально, что метатипы не могут быть расширены ... очень грустно ... и здесь я подумал, что Swift - это современный язык: S - person CommaToast; 19.10.2017
comment
@CommaToast Перечисление может иметь вычисляемые свойства, чего может быть достаточно, если все 3 свойства фиксированы (для каждого случая перечисления). Конечно, соответствие второму протоколу также вполне возможно. - person Paulo Mattos; 19.10.2017