|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "Common Declarations" |
| 4 | +--- |
| 5 | + |
| 6 | +`common` declarations and definitions are a way to specify members of the companion object of a class. Unlike `static` definitions in Java, `common` declarations can be inherited. |
| 7 | + |
| 8 | +As an example, consider the following trait `Text` with an implementation class `FlatText`. |
| 9 | + |
| 10 | +```scala |
| 11 | +trait Text { |
| 12 | + def length: Int |
| 13 | + def apply(idx: Int): Char |
| 14 | + def concat(txt: Text): Text |
| 15 | + def toStr: String |
| 16 | + |
| 17 | + common def fromString(str: String): Text |
| 18 | + common def fromStrings(strs: String*): Text = |
| 19 | + ("" :: strs).map(fromString).reduceLeft(_.concat) |
| 20 | +} |
| 21 | + |
| 22 | +class FlatText(str: String) extends Text { |
| 23 | + def length = str.length |
| 24 | + def apply(n: Int) = str.charAt(n) |
| 25 | + def concat(txt: Text): Text = new FlatText(str ++ txt.toStr) |
| 26 | + def toStr = str |
| 27 | + |
| 28 | + common def fromString(str: String) = new FlatText(str) |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +The `common` definition of `fromString` is abstract in trait `Text`. It is defined in the implementing companion object of `FlatText`. By contrast, the `fromStrings` method in trait Text is concrete, with an implementation referring to the abstract `fromString`. It is inherited by the companion object `FlatText`. So the following are legal: |
| 33 | + |
| 34 | +```scala |
| 35 | +val txt1 = FlatText.fromString("hello") |
| 36 | +val txt2 = FlatText.fromStrings("hello", ", world") |
| 37 | +``` |
| 38 | + |
| 39 | +`common` definitions are only members of the companion objectcs of classes, not traits. So the following would give a "member not found" error. |
| 40 | + |
| 41 | +```scala |
| 42 | +val erroneous = Text.fromStrings("hello", ", world") // error: not found |
| 43 | +``` |
| 44 | + |
| 45 | +## The `Instance` type |
| 46 | + |
| 47 | +In the previous example, the argument and result type of `concat` is just `Text`. So every implementation of `Text` has to be prepared to concatenate all possible implementatons of `Text`. Furthermore, we hide the concrete implementation type in the result type of `concat` and of the construction methods `fromString` and `fromStrings`. Sometimes we want a different design that specifyfies the actual implementation type instead of the base trait `Text`. We can refer to this type using the predefined type `Instance`: |
| 48 | + |
| 49 | +```scala |
| 50 | +trait Text { |
| 51 | + def length: Int |
| 52 | + def apply(idx: Int): Char |
| 53 | + def concat(txt: Instance): Instance |
| 54 | + def toStr: String |
| 55 | + |
| 56 | + common def fromString(str: String): Instance |
| 57 | + common def fromStrings(strs: String*): Instance = |
| 58 | + ("" :: strs).map(fromString).reduceLeft(_.concat) |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +In traits that define or inherit `common` definitions, the `Instance` type refers to the (as yet unknown) instance type whose |
| 63 | +companion object implements the trait. To see why `Instance` is useful, consider another possible implementation of `Text`, implemented as a tree of strings. The advantage of the new implementation is that `concat` is constant time. Both old and new implementations share the definition of the `common` method `fromStrings`. |
| 64 | + |
| 65 | +```scala |
| 66 | +enum ConcText extends Text { |
| 67 | + case Str(s: String) |
| 68 | + case Conc(t1: ConcText, t2: ConcText) |
| 69 | + |
| 70 | + lazy val length = this match { |
| 71 | + case Str(s) => s.length |
| 72 | + case Conc(t1, t2) => t1.length + t2.length |
| 73 | + } |
| 74 | + |
| 75 | + def apply(n: Int) = this match { |
| 76 | + case Str(s) => s.charAt(n) |
| 77 | + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) |
| 78 | + } |
| 79 | + |
| 80 | + def concat(txt: ConcText) = Conc(this, txt) |
| 81 | + |
| 82 | + def toStr: String = this match { |
| 83 | + case Str(s) => s |
| 84 | + case Conc(t1, t2) => t1.toStr ++ t2.toStr |
| 85 | + } |
| 86 | + common def fromString(str: String): ConcText = Str(str) |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +The `concat` method of `ConcText` with type `(txt: ConcText): ConcText` is a valid implementation of the |
| 91 | +abstract method in `Text` of type `(txt: Instance): Instance` because `ConcText` is a class implementing `Text` |
| 92 | +which means that it fixes `Instance` to be `ConcText`. |
| 93 | + |
| 94 | +Note: The `Instance` type is a useful abstraction for traits that are always implemented via `extends`. For type-class like traits that are intended to be implemented after the fact with extension clauses, there is another predefined type `This` that is generally more appropriate (more on `This` in the typeclass section). |
| 95 | + |
| 96 | +## The `common` Reference |
| 97 | + |
| 98 | +Let's add another method to `Text`: |
| 99 | + |
| 100 | + trait Text { |
| 101 | + ... |
| 102 | + def flatten: Instance = fromString(toStr) |
| 103 | + } |
| 104 | + |
| 105 | +Why does this work? The `fromString` method is abstract in `Text` so how to we find the correct implementation in `flatten`? |
| 106 | +Comparing with the `toStr` reference, this one is an instance method and therefore is expanded to `this.toStr`. But the same does not work for `fromString` because it is a `common` method, not an instance method. |
| 107 | +In fact, the application above is syntactic sugar for |
| 108 | + |
| 109 | + this.common.fromString(this.toStr) |
| 110 | + |
| 111 | +The `common` selector is defined in each trait that defines or inherits `common` definitions. It refers at runtime to |
| 112 | +the object that implements the `common` definitions. |
| 113 | + |
| 114 | +## Translation |
| 115 | + |
| 116 | +The translation of a trait `T` that defines `common` declarations `common D1, ..., common Dn` |
| 117 | +and extends traits with common declarations `P1`, ..., Pn` is as follows: |
| 118 | +All `common` definitions are put in a trait `T.Common` which is defined in `T`'s companion object: |
| 119 | + |
| 120 | + object T { |
| 121 | + trait Common extends P1.Common with ... with Pn.Common { self => |
| 122 | + type Instance <: T { val `common`: self.type } |
| 123 | + D1 |
| 124 | + ... |
| 125 | + Dn |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | +The trait inherits all `Common` traits associated with `T`'s parent traits. If no explicit definition of `Instance` is |
| 130 | +given, it declares the `Instance` type as shown above. The trait `T` itself is expanded as follows: |
| 131 | + |
| 132 | + trait T extends ... { |
| 133 | + val `common`: `T.Common` |
| 134 | + import `common`._ |
| 135 | + |
| 136 | + ... |
| 137 | + } |
| 138 | + |
| 139 | +Any direct reference to `x.common` in the body of `T` is simply translated to |
| 140 | + |
| 141 | + x.`common` |
| 142 | + |
| 143 | +The translation of a class `C` that defines `common` declarations `common D1, ..., common Dn` |
| 144 | +and extends traits with common declarations `P1`, ..., Pn` is as follows: |
| 145 | +All `common` definitions of the class itself are placed in `C`'s companion object, which also inherits all |
| 146 | +`Common` traits of `C`'s parents. If `C` already defines a companion object, the synthesized parents |
| 147 | +come after the explicitly declared ones, whereas the common definitions precede all explicitly given statements of the |
| 148 | +companion object. The companion object also defines the `Instance` type as |
| 149 | + |
| 150 | + type Instance = C |
| 151 | + |
| 152 | +unless an explicit definition of `Instance` is given in the same object. |
| 153 | + |
| 154 | +### Example: |
| 155 | + |
| 156 | +As an example, here is the translation of trait `Text` and its two implementations `FlatText` and `ConcText`: |
| 157 | + |
| 158 | +```scala |
| 159 | +trait Text { |
| 160 | + val `common`: Text.Common |
| 161 | + import `common`._ |
| 162 | + |
| 163 | + def length: Int |
| 164 | + def apply(idx: Int): Char |
| 165 | + def concat(txt: Instance): Instance |
| 166 | + def toStr: String |
| 167 | + def flatten = `common`.fromString(toStr) |
| 168 | +} |
| 169 | +object Text { |
| 170 | + trait Common { self => |
| 171 | + type Instance <: Text { val `common`: self.type } |
| 172 | + def fromString(str: String): Instance |
| 173 | + def fromStrings(strs: String*): Instance = |
| 174 | + ("" :: strs.toList).map(fromString).reduceLeft(_.concat(_)) |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +class FlatText(str: String) extends Text { |
| 179 | + val `common`: FlatText.type = FlatText |
| 180 | + import `common`._ |
| 181 | + |
| 182 | + def length = str.length |
| 183 | + def apply(n: Int) = str.charAt(n) |
| 184 | + def concat(txt: FlatText) = new FlatText(str ++ txt.toStr) |
| 185 | + def toStr = str |
| 186 | +} |
| 187 | +object FlatText extends Text.Common { |
| 188 | + type Instance = FlatText |
| 189 | + def fromString(str: String) = new FlatText(str) |
| 190 | +} |
| 191 | + |
| 192 | +enum ConcText extends Text { |
| 193 | + val `common`: ConcText.type = ConcText |
| 194 | + import `common`._ |
| 195 | + |
| 196 | + case Str(s: String) |
| 197 | + case Conc(t1: Text, t2: Text) |
| 198 | + |
| 199 | + lazy val length = this match { |
| 200 | + case Str(s) => s.length |
| 201 | + case Conc(t1, t2) => t1.length + t2.length |
| 202 | + } |
| 203 | + |
| 204 | + def apply(n: Int) = this match { |
| 205 | + case Str(s) => s.charAt(n) |
| 206 | + case Conc(t1, t2) => if (n < t1.length) t1(n) else t2(n - t1.length) |
| 207 | + } |
| 208 | + |
| 209 | + def concat(txt: ConcText): ConcText = Conc(this, txt) |
| 210 | + |
| 211 | + def toStr: String = this match { |
| 212 | + case Str(s) => s |
| 213 | + case Conc(t1, t2) => t1.toStr ++ t2.toStr |
| 214 | + } |
| 215 | +} |
| 216 | + |
| 217 | +object ConcText extends Text.Common { |
| 218 | + type Instance = ConcText |
| 219 | + def fromString(str: String) = Str(str) |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +### Relationship with Parameterization |
| 224 | + |
| 225 | +Common definitions do not see the type parameters of their enclosing class or trait. So the following is illegal: |
| 226 | + |
| 227 | +```scala |
| 228 | +trait T[A] { |
| 229 | + common def f: T[A] |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +The implicit `Instance` declaration of a trait or class follows in its parameters the parameters of the |
| 234 | +trait or class. For instance: |
| 235 | + |
| 236 | +```scala |
| 237 | +trait Sequence[+T <: AnyRef] { |
| 238 | + def map[U <: AnyRef](f: T => U): Instance[U] |
| 239 | + |
| 240 | + common def empty[T]: Instance[T] |
| 241 | +} |
| 242 | +``` |
| 243 | +The implicitly defined `Instance` declaration would be in this case: |
| 244 | + |
| 245 | +```scala |
| 246 | +object Sequence { |
| 247 | + trait Common { self => |
| 248 | + type Instance[+T <: AnyRef] <: Sequence[T] { val `common`: self.type } |
| 249 | + } |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +The rules for mixing `Instance` definitions of different kinds depend on the status of #4150. If #4150 is |
| 254 | +accepted, we permit `Instance` definitions to co-exist at different kinds. If #4150 is not accepted, we |
| 255 | +have to forbid this case, which means that a class must have the same parameter structure as all the traits |
| 256 | +with common members that it extends. |
0 commit comments