Классы значений вводят нежелательные публичные методы
Глядя на некоторые 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
что?)
РЕДАКТИРОВАТЬ:
В следующем примере компилятор стирает промежуточный объект или нет?
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
}
5 ответов
В 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
? Это в штучной упаковке.
Но эти две модели работают, вроде:
(1) Общий шаблон имени.
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
Сорт столкновения имен в любом случае решает вашу проблему. Если вы действительно хотите, вы можете создать пустой класс значений, который существует только для столкновения с repr
,
(2) Явная неявная картина
Иногда вы внутренне хотите, чтобы ваша ценность называлась чем-то более коротким или более мнемоническим, чем 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:
• Встроенные неявные обертки. Методы этих оболочек будут переведены в методы расширения.
• Новые числовые классы, такие как целые числа без знака. Для таких классов больше не нужно накладывать накладные расходы на бокс. Так что это похоже на классы значений в.NET.
• Классы, представляющие единицы измерения. Опять же, не будет никаких накладных расходов на бокс для этих классов.
Я думаю, что есть разница между первым и последним двумя. В первом случае сам класс значений должен быть прозрачным. Вы нигде не ожидаете типа RichInt
, но вы действительно только оперируете Int
, Во втором случае, например 4.meters
Я понимаю, что получение фактического "значения" имеет смысл, следовательно, требующий val
в порядке
Это разделение снова отражено в определении класса значений:
1. C должен иметь ровно один параметр, который отмечен значком val и который общедоступен.
...
7. С должен быть эфемерным.
Последнее означает, что у него нет других полей и т. Д., Что противоречит № 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, также может быть выполнено компилятором, но не должно быть видно в пользовательском коде. (Т.е. вы можете использовать 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 "RichInt@2"
scala> squareMe(2)
res1: String = RichInt@2 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 = ImplRichInt@6