Ответ 1
В Scala 2.11 вы можете сделать val приватным, что устраняет эту проблему:
implicit class RichInt(private val i: Int) extends AnyVal {
def squared = i * i
}
Посмотрев на некоторые scala -docs моих библиотек, мне показалось, что из классов значений возникает какой-то нежелательный шум. Например:
implicit class RichInt(val i: Int) extends AnyVal {
def squared = i * i
}
Это вводит нежелательный символ i
:
4.i // arghh....
Этот материал появляется как в документах scala, так и в автоматическом завершении IDE, что действительно не хорошо.
Итак... любые идеи о том, как смягчить эту проблему? Я имею в виду, что вы можете использовать RichInt(val self: Int)
, но это не делает его лучше (4.self
, wth?)
ИЗМЕНИТЬ
В следующем примере компилятор удаляет промежуточный объект или нет?
import language.implicitConversions
object Definition {
trait IntOps extends Any { def squared: Int }
implicit private class IntOpsImpl(val i: Int) extends AnyVal with IntOps {
def squared = i * i
}
implicit def IntOps(i: Int): IntOps = new IntOpsImpl(i) // optimised or not?
}
object Application {
import Definition._
// 4.i -- forbidden
4.squared
}
В Scala 2.11 вы можете сделать val приватным, что устраняет эту проблему:
implicit class RichInt(private val i: Int) extends AnyVal {
def squared = i * i
}
Он вводит шум (примечание: в 2.10, в 2.11, а за пределами вас просто объявляет val приватным). Вы не всегда хотите. Но так, как сейчас.
Вы не можете обойти проблему, следуя шаблону private-value-class, потому что компилятор не может видеть, что это класс значений в конце его, поэтому он проходит через общий маршрут. Здесь байт-код:
12: invokevirtual #24;
//Method Definition$.IntOps:(I)LDefinition$IntOps;
15: invokeinterface #30, 1;
//InterfaceMethod Definition$IntOps.squared:()I
Посмотрите, как первая возвращает копию класса Definition$IntOps
? Он в коробке.
Но эти два шаблона работают, вроде:
implicit class RichInt(val repr: Int) extends AnyVal { ... }
implicit class RichInt(val underlying: Int) extends AnyVal { ... }
Используйте один из них. Добавление i
в качестве метода раздражает. Добавление underlying
, когда нет ничего основополагающего, не так уж плохо - вы попадете только в него, если вы все равно попытаетесь получить базовое значение. И если вы продолжаете использовать одно и то же имя снова и снова:
implicit class RicherInt(val repr: Int) extends AnyVal { def sq = repr * repr }
implicit class RichestInt(val repr: Int) extends AnyVal { def cu = repr * repr * repr }
scala> scala> 3.cu
res5: Int = 27
scala> 3.repr
<console>:10: error: type mismatch;
found : Int(3)
required: ?{def repr: ?}
Note that implicit conversions are not applicable because they are ambiguous:
both method RicherInt of type (repr: Int)RicherInt
and method RichestInt of type (repr: Int)RichestInt
имя collade sorta все равно заботится о вашей проблеме. Если вы действительно этого хотите, вы можете создать пустой класс значений, который существует только для столкновения с repr
.
Иногда вы внутренне хотите, чтобы ваше значение называлось чем-то более коротким или более мнемоническим, чем repr
или underlying
, не делая его доступным для исходного типа. Один из вариантов заключается в том, чтобы создать переадресацию как таковую:
class IntWithPowers(val i: Int) extends AnyVal {
def sq = i*i
def cu = i*i*i
}
implicit class EnableIntPowers(val repr: Int) extends AnyVal {
def pow = new IntWithPowers(repr)
}
Теперь вам нужно называть 3.pow.sq
вместо 3.sq
- это может быть хорошим способом вырезать пространство имен! - и вам не нужно беспокоиться о загрязнении пространства имен за пределами оригинала repr
.
Возможно, проблема заключается в разнородных сценариях, для которых были построены классы значений. Из SIP:
• Вложенные неявные обертки. Методы этих оберток будут переведены в методы расширения.
• Новые числовые классы, такие как unsigned ints. Для таких классов больше не нужно будет бокс-накладных расходов. Таким образом, это похоже на классы значений в .NET.
• Классы, представляющие единицы измерения. Опять же, для этих классов не было бы расходов на бокс.
Я думаю, что есть разница между первым и вторым. В первом случае класс значения должен быть прозрачным. Вы не ожидали бы какого-либо типа RichInt
, но на самом деле вы работаете только с Int
. Во втором случае, например, 4.meters
, я понимаю, что получение фактического "значения" имеет смысл, поэтому требуется val
.
Это разделение снова отражается в определении класса значений:
1. C должен иметь ровно один параметр, который помечен значком val и который имеет общедоступную доступность.
...
7. C должен быть эфемерным.
Последнее означает, что у него нет других полей и т.д., что противоречит № 1.
С
class C(val u: U) extends AnyVal
единственное место в SIP, где используется u
, - это примеры реализации (например, def extension$plus($this: Meter, other: Meter) = new Meter($this.underlying + other.underlying)
); а затем в промежуточных представлениях, только для окончательного удаления:
new C(e).u ⇒ e
Промежуточное представление, доступное для синтетических методов IMO, является тем, что также может быть выполнено компилятором, но не должно быть видимым в написанном пользователем коде. (I.e., вы можете использовать val
, если хотите получить доступ к одноранговым узлам, но не обязательно).
Возможность использовать имя, которое затенено:
implicit class IntOps(val toInt: Int) extends AnyVal {
def squared = toInt * toInt
}
или
implicit class IntOps(val toInt: Int) extends AnyVal { ops =>
import ops.{toInt => value}
def squared = value * value
}
Это все равно окажется в scala -docs, но, по крайней мере, вызов 4.toInt
не путает, фактически не запускает IntOps
.
Я не уверен, что это "нежелательный шум", поскольку, по-моему, почти всегда вам нужно получить доступ к базовым значениям при использовании RichInt
.
Рассмотрим это:
// writing ${r} we use a RichInt where an Int is required
scala> def squareMe(r: RichInt) = s"${r} squared is ${r.squared}"
squareMe: (r: RichInt)String
// results are not what we hoped, we wanted "2", not "[email protected]"
scala> squareMe(2)
res1: String = [email protected] squared is 4
// we actually need to access the underlying i
scala> def squareMeRight(r: RichInt) = s"${r.i} squared is ${r.squared}"
squareMe: (r: RichInt)String
Кроме того, если у вас есть метод, который добавляет два RichInt
, вам нужно будет снова получить доступ к базовому значению:
scala> implicit class ImplRichInt(val i: Int) extends AnyVal {
| def Add(that: ImplRichInt) = new ImplRichInt(i + that) // nope...
| }
<console>:12: error: overloaded method value + with alternatives:
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (ImplRichInt)
def Add(that: ImplRichInt) = new ImplRichInt(i + that)
^
scala> implicit class ImplRichInt(val i: Int) extends AnyVal {
| def Add(that: ImplRichInt) = new ImplRichInt(i + that.i)
| }
defined class ImplRichInt
scala> 2.Add(4)
res7: ImplRichInt = [email protected]