Классы значений вводят нежелательные публичные методы

Глядя на некоторые 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
Другие вопросы по тегам