Подтвердить что ты не робот

Класс класса "метод" класса с суперклассом

Я хочу сделать что-то вроде этого:

sealed abstract class Base(val myparam:String)

case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)

def getIt( a:Base ) = a.copy(myparam="changed")

Я не могу, потому что в контексте getIt я не сказал компилятору, что каждая Base имеет метод "copy", но копия на самом деле не является методом, поэтому я не думаю, что есть черта или абстрактный метод, который я могу поместить в базу, чтобы сделать эту работу должным образом. Или, есть?

Если я попытаюсь определить Base как abstract class Base{ def copy(myparam:String):Base }, то case class Foo(myparam:String) extends Base приведет к class Foo needs to be abstract, since method copy in class Base of type (myparam: String)Base is not defined

Есть ли другой способ сообщить компилятору, что все классы Base будут классами case в их реализации? Некоторая черта, которая означает "имеет свойства класса case"?

Я мог бы сделать Base классом case, но тогда я получаю предупреждения компилятора, говорящие, что наследование от классов case устарело?

Я знаю, что могу также:

def getIt(f:Base)={ 
  (f.getClass.getConstructors.head).newInstance("yeah").asInstanceOf[Base]
}

но... это кажется очень уродливым.

Мысли? Является ли весь мой подход "неправильным"?

ОБНОВЛЕНИЕ Я изменил базовый класс, чтобы содержать атрибут, и сделал классы case использующими ключевое слово "переопределить". Это лучше отражает реальную проблему и делает проблему более реалистичной с учетом ответа Edmondo1984.

4b9b3361

Ответ 1

Это старый ответ, прежде чем вопрос был изменен.

Сильно типизированные языки программирования предотвращают то, что вы пытаетесь сделать. Посмотрим, почему.

Идея метода со следующей сигнатурой:

def getIt( a:Base ) : Unit

Является ли тело метода доступным для свойств, видимых через базовый класс или интерфейс, т.е. свойства и методы, определенные только для базового класса/интерфейса или его родителей. Во время выполнения кода каждый конкретный экземпляр, переданный методу getIt, может иметь другой подкласс, но тип компиляции a всегда будет Base

Можно рассуждать таким образом:

Хорошо У меня есть класс Base, я наследую его в двух классах классов, и я добавляю свойство с тем же именем, а затем я попытаюсь получить доступ к экземпляр базы.

Простой пример показывает, почему это небезопасно:

sealed abstract class Base
case class Foo(myparam:String) extends Base
case class Bar(myparam:String) extends Base
case class Evil(myEvilParam:String) extends Base

def getIt( a:Base ) = a.copy(myparam="changed")

В следующем случае, если компилятор не выдавал ошибку во время компиляции, это означает, что код попытается получить доступ к свойству, которое не существует во время выполнения. Это невозможно в строго типизированных языках программирования: у вас есть ограничения на код, который вы можете написать для более сильной проверки вашего кода компилятором, зная, что это значительно сокращает количество ошибок, которые может содержать ваш код


Это новый ответ. Это немного долго, потому что перед тем, как прийти к заключению, нужно несколько пунктов

К несчастью, вы не можете полагаться на механизм классов case copy, чтобы реализовать то, что вы предлагаете. Способ работы метода копирования - это просто конструктор копирования, который вы можете реализовать самостоятельно в классе, отличном от случая. Позвольте создать класс case и дизассемблировать его в REPL:

scala>  case class MyClass(name:String, surname:String, myJob:String)
defined class MyClass

scala>  :javap MyClass
Compiled from "<console>"
public class MyClass extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{
    public scala.collection.Iterator productIterator();
    public scala.collection.Iterator productElements();
    public java.lang.String name();
    public java.lang.String surname();
    public java.lang.String myJob();
    public MyClass copy(java.lang.String, java.lang.String, java.lang.String);
    public java.lang.String copy$default$3();
    public java.lang.String copy$default$2();
    public java.lang.String copy$default$1();
    public int hashCode();
    public java.lang.String toString();
    public boolean equals(java.lang.Object);
    public java.lang.String productPrefix();
    public int productArity();
    public java.lang.Object productElement(int);
    public boolean canEqual(java.lang.Object);
    public MyClass(java.lang.String, java.lang.String, java.lang.String);
}

В Scala метод копирования принимает три параметра и может в конечном итоге использовать один из текущего экземпляра для того, который вы не указали (язык Scala предоставляет среди своих функций значения по умолчанию для параметров в вызовах методов)

Переходим в наш анализ и снова обновляем код:

sealed abstract class Base(val myparam:String)

case class Foo(override val myparam:String) extends Base(myparam)
case class Bar(override val myparam:String) extends Base(myparam)

def getIt( a:Base ) = a.copy(myparam="changed")

Теперь, чтобы выполнить эту компиляцию, нам нужно будет использовать в сигнатуре getIt(a:MyType) a MyType, которые соблюдают следующий контракт:

Все, что имеет параметр myparam и, возможно, другие параметры, которые имеют значение по умолчанию

Все эти методы подходят:

  def copy(myParam:String) = null
  def copy(myParam:String, myParam2:String="hello") = null
  def copy(myParam:String,myParam2:Option[Option[Option[Double]]]=None) = null

Невозможно выразить этот контракт в Scala, однако есть дополнительные методы, которые могут быть полезны.

Первое замечание, которое мы можем сделать, состоит в том, что существует строгое соотношение между case classes и tuples в Scala. На самом деле классы case - это как-то кортежи с дополнительным поведением и именованными свойствами.

Второе замечание состоит в том, что, поскольку количество свойств иерархии ваших классов не гарантируется одинаково, подпись метода copy не гарантируется.

На практике, если AnyTuple[Int] описывает любой Tuple любого размера, где первое значение имеет тип Int, мы хотим сделать что-то вроде этого:

def copyTupleChangingFirstElement(myParam:AnyTuple[Int], newValue:Int) = myParam.copy(_1=newValue)

Это не было бы сложно, если бы все элементы были Int. Кортеж со всем элементом того же типа является List, и мы знаем, как заменить первый элемент a List. Нам нужно преобразовать любой TupleX в List, заменить первый элемент и преобразовать List обратно в TupleX. Да, нам нужно будет написать все конвертеры для всех значений, которые может принять X. Раздражает, но не сложно.

Однако в нашем случае не все элементы Int. Мы хотим рассматривать Tuple, где элементы имеют различный тип, как если бы они были одинаковыми, если первый элемент является Int. Это называется

"Абстрагирование по arity"

то есть. обрабатывая кортежи разного размера общим способом, независимо от их размера. Для этого нам нужно преобразовать их в специальный список, который поддерживает гетерогенные типы с именем HList


Заключение

Наследование классов классов устарело, потому что вы можете узнать из нескольких сообщений в списке рассылки: http://www.scala-lang.org/node/3289

У вас есть две стратегии для решения вашей проблемы:

  • Если у вас есть ограниченное количество полей, которые вам нужно изменить, используйте такой подход, как предложенный @Ron, который имеет метод копирования. Если вы хотите сделать это без потери информации о типе, я бы пошел на генерацию базового класса

    sealed abstract class Base[T](val param:String){
      def copy(param:String):T
    }
    
    class Foo(param:String) extends Base[Foo](param){
      def copy(param: String) = new Foo(param)
    }
    
    def getIt[T](a:Base[T]) : T = a.copy("hello")
    
    scala>  new Foo("Pippo")
    res0: Foo = [email protected]
    
    scala>  getIt(res0)
    res1: Foo = [email protected]
    
    scala>  res1.param
    res2: String = hello
    
  • Если вы действительно хотите абстрагироваться от arity, решение должно использовать библиотеку, разработанную Майлсом Сабином под названием Shapeless. Здесь возникает вопрос, который был задан после обсуждения: Являются ли HLists не более чем запутанным способом написания кортежей?, но я говорю вам, что это даст вам немного головная боль

Ответ 2

Если два класса case будут расходиться со временем, так что они будут иметь разные поля, тогда общий подход copy перестанет работать.

Лучше определить абстрактный def withMyParam(newParam: X): Base. Еще лучше, вы можете ввести абстрактный тип, чтобы сохранить тип класса case при возврате:

scala> trait T {
     |   type Sub <: T
     |   def myParam: String
     |   def withMyParam(newParam: String): Sub
     | }
defined trait T

scala> case class Foo(myParam: String) extends T {
     |   type Sub = Foo
     |   override def withMyParam(newParam: String) = this.copy(myParam = newParam)
     | }
defined class Foo

scala>

scala> case class Bar(myParam: String) extends T {
     |   type Sub = Bar
     |   override def withMyParam(newParam: String) = this.copy(myParam = newParam)
     | }
defined class Bar

scala> Bar("hello").withMyParam("dolly")
res0: Bar = Bar(dolly)

Ответ 3

TL; DR: Мне удалось объявить метод копирования на Base, все еще позволяя компилятору автоматически генерировать свои реализации в производных классах case. Это включает в себя небольшой трюк (и на самом деле я бы просто перепроектировал иерархию типов), но, по крайней мере, он показывает, что вы действительно можете заставить его работать без написания кода плиты котла в любом из производных классов случаев.

Во-первых, и как уже упоминалось ron и Edmondo1984, вы столкнетесь с проблемами, если ваши классы case имеют разные поля.

Я строго придерживаюсь вашего примера, и предположим, что все ваши классы case имеют одинаковые поля (смотря на вашу ссылку github, это, похоже, также относится к вашему фактическому коду).

Учитывая, что все ваши классы case имеют одинаковые поля, автогенерируемые методы copy будут иметь такую ​​же подпись, что и хорошее начало. Кажется разумным тогда просто добавить общее определение в Base, как вы это делали: abstract class Base{ def copy(myparam: String):Base } Проблема в том, что scala не будет генерировать методы copy, потому что в базовом классе уже есть один.

Оказывается, существует еще один способ статически убедиться, что Base имеет правильный метод copy, и это связано с структурной типизацией и аннотацией самонастройки:

type Copyable = { def copy(myParam: String): Base }
sealed abstract class Base(val myParam: String) { this : Copyable => }

И в отличие от нашей предыдущей попытки это не помешает scala автоматически генерировать методы copy. Существует одна последняя проблема: аннотация самонастройки гарантирует, что подклассы Base имеют метод copy, но это не делает его общедоступным на Base:

val foo: Base = Foo("hello")
foo.copy()
scala> error: value copy is not a member of Base

Чтобы обойти это, мы можем добавить неявное преобразование из Base to Copyable. Простое выполнение будет выполнено, так как база гарантирована для копирования:

implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]

Обертывание, это дает нам:

object Base {
  type Copyable = { def copy(myParam: String): Base }
  implicit def toCopyable( base: Base ): Base with Copyable = base.asInstanceOf[Base with Copyable]
}
sealed abstract class Base(val myParam: String) { this : Base. Copyable => }

case class Foo(override val myParam: String) extends Base( myParam )
case class Bar(override val myParam: String) extends Base( myParam )

def getIt( a:Base ) = a.copy(myParam="changed")

Бонусный эффект: если мы попытаемся определить класс case с другой сигнатурой, мы получим ошибку компиляции:

case class Baz(override val myParam: String, truc: Int) extends Base( myParam ) 
scala> error: illegal inheritance; self-type Baz does not conform to Base selftype Base with Base.Copyable

Чтобы закончить, одно предупреждение: вы, вероятно, должны просто пересмотреть свой дизайн, чтобы не прибегать к вышеупомянутому трюку. В вашем случае предложение ron использовать один класс case с дополнительным полем etype представляется более чем разумным.

Ответ 4

Я думаю, что для этого нужны методы расширения. Сделайте свой выбор стратегий реализации для самого метода копирования.

Мне нравится здесь, что проблема решена в одном месте.

Интересно спросить, почему нет черты для случая: он не сказал бы много о том, как вызывать копию, за исключением того, что ее всегда можно вызвать без аргументов, copy().

sealed trait Base { def p1: String }

case class Foo(val p1: String) extends Base
case class Bar(val p1: String, p2: String) extends Base
case class Rab(val p2: String, p1: String) extends Base
case class Baz(val p1: String)(val p3: String = p1.reverse) extends Base

object CopyCase extends App {

  implicit class Copy(val b: Base) extends AnyVal {
    def copy(p1: String): Base = b match {
      case foo: Foo => foo.copy(p1 = p1)
      case bar: Bar => bar.copy(p1 = p1)
      case rab: Rab => rab.copy(p1 = p1)
      case baz: Baz => baz.copy(p1 = p1)(p1.reverse)
    }
    //def copy(p1: String): Base = reflect invoke
    //def copy(p1: String): Base = macro xcopy
  }

  val f = Foo("param1")
  val g = f.copy(p1="param2") // normal
  val h: Base = Bar("A", "B")
  val j = h.copy("basic")     // enhanced
  println(List(f,g,h,j) mkString ", ")

  val bs = List(Foo("param1"), Bar("A","B"), Rab("A","B"), Baz("param3")())
  val vs = bs map (b => b copy (p1 = b.p1 * 2))
  println(vs)
}

Просто для удовольствия, отражающая копия:

  // finger exercise in the api
  def copy(p1: String): Base = {
    import scala.reflect.runtime.{ currentMirror => cm }
    import scala.reflect.runtime.universe._
    val im = cm.reflect(b)
    val ts = im.symbol.typeSignature
    val copySym = ts.member(newTermName("copy")).asMethod
    def element(p: Symbol): Any = (im reflectMethod ts.member(p.name).asMethod)()
    val args = for (ps <- copySym.params; p <- ps) yield {
      if (p.name.toString == "p1") p1 else element(p)
    }
    (im reflectMethod copySym)(args: _*).asInstanceOf[Base]
  }

Ответ 5

Существует очень подробное объяснение того, как это сделать, используя бесформенный в http://www.cakesolutions.net/teamblogs/copying-sealed-trait-instances-a-journey-through-generic-programming-and-shapeless; в случае разрыва связи подход использует утилиты copySyntax из бесформенного, что должно быть достаточным для поиска более подробной информации.

Ответ 6

Это отлично работает для меня:

sealed abstract class Base { def copy(myparam: String): Base }

case class Foo(myparam:String) extends Base {
  override def copy(x: String = myparam) = Foo(x)
}

def copyBase(x: Base) = x.copy("changed")

copyBase(Foo("abc")) //Foo(changed)

Ответ 7

Его старая проблема со старым решением

https://code.google.com/p/scala-scales/wiki/VirtualConstructorPreSIP

сделанный до того, как существовал метод копирования класса case.

Итак, в связи с этой проблемой каждый класс case ДОЛЖЕН быть листом node в любом случае, поэтому определите копию и MyType/thisType плюс функцию newThis, и вы настроены, каждый класс case фиксирует тип. Если вы хотите расширить функцию tree/newThis и использовать параметры по умолчанию, вам нужно будет изменить имя.

как в сторону - я ждал, когда волшебство плагина компилятора улучшится до его реализации, но макросы типа могут быть волшебным соком. Найдите в списках для Kevin AutoProxy более подробное объяснение того, почему мой код никогда не уходил.