0

Short Story:

I was trying to make a class whose subclass can have a more specialized member (a dictionary), then I hit the following error:

error: property 'dict' with type '[Int : B]' cannot override a property with type '[Int : A]'

Here is my code:

class A {}
class B: A {}               // subclass of A

let dict = [Int:B]()
print(dict is [Int:A])      // true

class MyDict {
  var dict = [Int:A]()
}

class MyDict2: MyDict {

  /*
    ⛔ error: 
        property 'dict' with type '[Int : B]' cannot override a property with type '[Int : A]'
  */
  override var dict = [Int:B]()
  
}

My question is that since an instance of [Int:B] is a [Int:A], why can't we override a dictionary of type [Int:A] with [Int:B] ?

Long Story:

Actually, I was trying to design types for both weighted and unweighted graphs. The following is my implementation:

// unweighted edge
public class Edge<Vertex:Hashable> {
  public let start: Vertex
  public let end  : Vertex
  init(from start: Vertex, to end: Vertex) {
    self.start = start
    self.end   = end
  }
}

// weighted edge
public class WeightedEdge<Vertex:Hashable, Weight:Comparable> : Edge<Vertex> {
  public let weight: Weight
  init(from start:Vertex, to end:Vertex, weight:Weight) {
    self.weight = weight
    super.init(from:start, to:end)
  }
}

// unweighted graph (definition)
public class Graph<Vertex:Hashable> {

  // edges dictionary
  var edgesOf = [Vertex: [Edge<Vertex>]]()
  
  // g.addEdge(from:to:bidirectional:)
  public func addEdge(
    from start: Vertex, 
    to end:Vertex, 
    bidirectional:Bool = false
  ) {
    edgesOf[start, default:[]].append(Edge(from: start, to:end))
    if bidirectional {
      edgesOf[end, default:[]].append(Edge(from: end, to:start))
    }
  }
  
}

// weighted graph (definition)
public class WeightedGraph<Vertex:Hashable, Weight:Comparable> : Graph<Vertex> {

  public override func addEdge(from start:Vertex, to end:Vertex, bidirectional:Bool = false) {
    fatalError("Can't add an unweighted edge to a weighted graph❗")
  }

  // g.addEdge(from:to:weight:bidirectional:)
  public func addEdge(
    from    start: Vertex, 
    to        end: Vertex, 
    weight       : Weight,
    bidirectional: Bool = false
  ) {
    edgesOf[start, default:[]].append(
      WeightedEdge(from: start, to:end, weight:weight)
    )
    if bidirectional {
      edgesOf[end, default:[]].append(
        WeightedEdge(from: end, to:start, weight:weight)
      )
    }
  }
  
}

let g = WeightedGraph<Int,Int>()
g.addEdge(from: 3, to: 4, weight:7, bidirectional: true)
g.addEdge(from: 1, to: 3)  // fatalError

It works fine for me, but I'm bothered by the ugly part of it:

public override func addEdge(from start:Vertex, to end:Vertex, bidirectional:Bool = false) {
  fatalError("Can't add an unweighted edge to a weighted graph❗")
}

What can I do to prevent a weighted graph from adding an unweighted edge to itself?

Community
  • 1
  • 1
lochiwei
  • 1,240
  • 9
  • 16
  • 1
    Swift does not allow you to change the class type of any variables or properties. https://stackoverflow.com/questions/24094158/overriding-superclass-property-with-different-type-in-swift – stevenpcurtis Apr 16 '19 at 02:09
  • @stevenpcurtis, honestly my question is: why does Swift prevent us from subclassing the type of a property? what bad things can happen if we were allowed to? – lochiwei Apr 16 '19 at 02:45
  • This is not a swift specific question really but also applies to other languages as well. – Joakim Danielson Apr 16 '19 at 06:26

1 Answers1

0

When you inherit a class you are including the super class in it so you will get conflicting definitions of the same property, somewhat simplified the compiled version of MyDict2 would be

class MyDict2 {
    var dict = [Int:A]()
    var dict = [Int:B]() 
}

and as you can see this will not work. Alternatively the dict property would be replaced but then you have the problem that any function that accepts a parameter of type MyDict can also take a instance of MyDict2 but if that function accessed the dict property it would find values of type B and the function would have no idea what is since it is defined to take MyDict and therefore only knows about A

I don't think inheritance is the way forward here, alternatives is to have two separate classes that conforms to the same protocol for accessing the underlying storage (dict) or to implement this using generics

class MyDict<T> {
    var dict = [Int:T]()
}

let c1 = MyDict<A>()
let c2 = MyDict<B>()
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52