Вопрос по inheritance, class, scala, enumeration – экземпляр класса case «метод» с суперклассом

26

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

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 для правильной работы. Или есть?

Если я попытаюсь определить 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 классы будут падежными в их реализации? Некоторая черта, которая означает, что «обладает свойствами класса дел»?

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

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

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

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

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

UPDATE Я изменил базовый класс, чтобы он содержал атрибут, и сделал так, чтобы классы дел использовали & quot; переопределить & quot; ключевое слово. Это лучше отражает реальную проблему и делает ее более реалистичной с учетом ответа Edmondo1984.

Похоже, что это может быть возможно с помощью отражения (или, возможно, скамеета):gist.github.com/rbuckland/11229137 ... это может быть немного, если это не было как-то доступно в виде библиотеки;) bbarker
Можете ли вы объяснить, что вы хотите получить, переопределив val в классе case? Edmondo1984
ну, у него не будет значения в абстрактном суперклассе. Может быть, если посмотреть на реальную программу, этот вопрос станет более понятным, в самом коде «Базовая». класс является абстрактным запечатанным «куском» класс, определенный в этом файле:github.com/nairbv/scalachess/blob/master/src/main/scala/…  ... как временный хакер, я бросил ужасный актерский состав, который я упомянул как возможность в первоначальном вопросе. nairbv

Ваш Ответ

7   ответов
1

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)
2

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

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

Интересно спросить, почему нет никакой черты прецедентности: она не будет много говорить о том, как вызывать копию, за исключением того, что она всегда может быть вызвана без аргументов,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]
  }
13

и у них будут разные поля, то общий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)
Почему у вас есть разные классы case, которые имеют одинаковые аргументы? Не обязательно плохо, просто пытаюсь понять.
хм .... у меня примерно 6 из этих простых классов case, хотя, в идеале, с каким-то методом копирования, я бы сэкономил много печатания вместо того, чтобы реализовывать withMyParam в каждом классе case: - / nairbv
Как насчетcase class MyEnum(etype: Etype, value: String); sealed trait Etype; object etype1 extends Etype; ... ?
Зачем? Сопоставление с шаблоном может быть вложенным, напримерcase MyEnum(`etype1`, x) => ..., Обратите внимание, что вам нужно использовать обратные метки, иначе Scala будет рассматривать имя в нижнем регистре как переменную свободного совпадения. Вы можете использовать случайные объекты, чтобы предотвратить стрельбу в ногу.
1

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

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

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

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

12

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

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

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

Учитывая, что все ваши классы дел имеют одинаковые поля, автоматически сгенерированные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 в Copyable. Подойдет простое приведение, так как Base гарантированно будет копируемым:

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's selftype Base with Base.Copyable

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

2

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

22

This is old answer, before the question was changed.

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

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

def getIt( a:Base ) : Unit

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

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

Ok I have a class Base, I inherit it in two case classes and I add a property with the same name, and then I try to access the property on the instance of 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")

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

This is the new answer. It is a little long because few points are needed before getting to the conclusion

К сожалению, вы не можете полагаться на механизм case-классовcopy реализовать то, что вы предлагаете. Метод копирования работает просто как конструктор копирования, который вы можете реализовать самостоятельно в не-case-классе. Давайте создадим класс 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 метод copy принимает три параметра и может в конечном итоге использовать один из текущего экземпляра для того, который вы не указали (язык 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)  MyType что уважать следующий договор:

Anything that has a parameter myparam and maybe other parameters which have default value

Все эти методы подойдут:

  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 в Скале. На самом деле классы - это как бы кортежи с дополнительным поведением и именованными свойствами.

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

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

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

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

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

"Abstracting over arity"

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

Conclusion

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

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

If you have a limited number of fields you require to change, use an approach such as the one suggested by @Ron, which is having a copy method. If you want to do it without losing type information, I would go for generifying the base class

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

If you really want to abstract over arity, a solution is to use a library developed by Miles Sabin called Shapeless. There is a question here which has been asked after a discussion : Are HLists nothing more than a convoluted way of writing tuples? but I tell you this is going to give you some headache

Я ответил на ваш вопрос. Если вы измените свой вопрос, я изменю свой ответ :)
Можете ли вы опубликовать свой ответ и объяснить, почему исключено только наследование case-class-from-case-class?
Что было признано устаревшим, так это наследование case-class-case-case-class.
хаха, ок, я редактировал вопрос :-) nairbv
хм ... как насчет параметров, которые переопределяются в Foo и Bar от родителя? Когда я придумал свой пример кода, основанный на моей реальной программе, похоже, что я допустил ошибку; в моей настоящей программе все атрибуты Foo и Bar переопределяют атрибуты Base. nairbv

Похожие вопросы