تقليل الكود باستخدام السمات والكلمة الكلمة

StackOverflow https://stackoverflow.com/questions/4058530

  •  27-09-2019
  •  | 
  •  

سؤال

لدي بعض الفصول مع نفس النوع الفائق. لذلك يجب أن تتجاوز جميع هذه الفئات نفس الأساليب. الآن يمكنني استدعاء طريقة وارتكابها كائن من النوع الفائق المشترك. لكن ليس من المفيد دائمًا الرد على كل نوع ملتزم ، وبالتالي يتم طرح استثناء. أولاً حاولت حل هذا السلوك مثل هذا:

def operation(s: SuperType) = s match {
  case t: SubType1 => ...
  case t: SubType2 => ...
  case _ => ...
}

نظرًا لكثير من الأنواع الفرعية ، سيؤدي هذا إلى الكثير من التعليمات البرمجية (في كل طريقة وفي كل فصل) وحاولت حل هذه المشكلة traits. يجب أن يختبر كل سمة نوعًا واحدًا فقط ثم إعادة توجيه الكائن إلى الطريقة العليا على المكدس. يصف الرمز أدناه كيف أتخيل ذلك. لكن هذا لا يعمل لأن المترجم لا يمكنه حل الأنواع. المشكلة الأخرى هي أنه يجب علي إعلان كل سمة من السمات في كل فئة سلوك.

object TraitWithTest {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    println("e1 + e2: " + (e1 + e2))
    println("o1 + o2: " + (o1 + o2))
    try { println("e1 + o2: " + (e1 + o2)) } catch { case e => println(e) }
    println("o1 + e2: " + (o1 + e2))
    println("a1 + e1: " + (a1 + e2))
  }
}

abstract class Num {
  def +(n: Num): Num
}

trait OddBehaviour extends Num {
  val e1, e2: Int // here I don't want to declare all attributes
  val a1: Double
  abstract override def +(n: Num) = n match {
    case o: Odd => throw new UnsupportedOperationException("Even#+(Odd)")
    case _ => super.+(n)
  }
}

trait EvenBehaviour extends Num {
  val o1, o2: Double
  val a1: Double
  abstract override def +(n: Num) = n match {
    case e: Even => Odd(o1 + e.e1, o2 + e.e2)
    case _ => super.+(n)
  }
}

trait AllBehaviour extends Num {
  val o1, o2: Double
  val e1, e2: Int
  abstract override def +(n: Num) = n match {
    case a: All => Odd(o1 + a.a1, o2 + a.a1)
    case _ => super.+(n)
  }
}

object Even {
  def apply(e1: Int, e2: Int) = new Even(e1, e2) with OddBehaviour with AllBehaviour
}

abstract case class Even(e1: Int, e2: Int) extends Num {
  override def +(n: Num) = n match {
    case c: Even => Even(e1 + c.e1, e2 + c.e2)
    case _ => throw new IllegalArgumentException
  }
}

object Odd {
  def apply(o1: Double, o2: Double) = new Odd(o1, o2) with EvenBehaviour with AllBehaviour
}

abstract case class Odd(o1: Double, o2: Double) extends Num {
  override def +(n: Num) = n match {
    case o: Odd => Odd(o1 + o.o1, o2 + o.o2)
    case _ => throw new IllegalArgumentException
  }
}

object All {
  def apply(a1: Double) = new All(a1) with EvenBehaviour with OddBehaviour
}

abstract case class All(a1: Double) extends Num {
  override def +(n: Num) = n match {
    case a: All => All(a1 + a.a1)
    case _ => throw new IllegalArgumentException
  }
}

هل يمكن لأي شخص أن يقول لي ما إذا كان من الممكن تقليل خطوط الكود باستخدام السمات؟ أم أن المباراة الكل التي أستخدمها حاليًا الأفضل؟

تعديل:

بمساعدتك ، وجدت حل نصف العمل. كانت مشكلتي الرئيسية هي أنني حاولت تقليل خطوط الكود باستخدام ميزات Scala. لذلك أغفلت أسهل طريقة: الاستعانة بمصادر خارجية للرمز! لا بد لي فقط لإنشاء كائن جديد يتحقق من القبلية الكائنات. الكائنات نفسها تتعامل فقط مع أنواعها الخاصة.

هذا هو الرمز:

final object TraitWithTest {
  def main(args: Array[String]) {
    import traitwith.operations._
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    val n1 = NumHolder(o1)
    val n2 = NumHolder(a1)

    println("e1 + e2: " + add(e1, e2))
    println("o1 + o2: " + add(o1, o2))
    try { println("e1 + o2: " + add(e1, o2)) } catch { case e => println(e) }
    println("o1 + e2: " + add(o1, e2))
    try { println("a1 + e2: " + add(a1, e2)) } catch { case e => println(e) }
    println("n1 + n2: " + add(n1, n2))
  }
}

final object operations {
  def add(a: Num, b: Num) = a -> b match {
    case (a1: Odd, b1: Odd) => a1 + b1
    case (a1: Odd, b1: Even) => Odd(a1.x + b1.x, a1.y + b1.y)
    case (a1: Odd, b1: All) => Odd(a1.x + b1.x, a1.y + b1.x)
    case (a1: Even, b1: Even) => a1 + b1
    case (a1: All, b1: All) => a1 + b1
    case _ => error("can't add " + b + " to " + a)
  }
}

abstract class Num {
  type A <: Num
  def +(a: A): A
}

final case class Odd(x: Double, y: Double) extends Num {
  override type A = Odd
  override def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  override type A = Even
  override def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  override type A = All
  override def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  override type A = NumHolder
  override def +(a: NumHolder) = NumHolder(x + a.x)
}

لقد قمت بتوسيع الكود قليلاً وأدخلت الكائن NumHolder. الآن ، لا يوجد عيب صغير واحد فقط: في التكرار لا يمكنني ارتكاب النوع الفائق دون الحصول على خطأ في الترجمة في طريقة الإضافات. حاولت استخدام الأدوية الجيرية بدلاً من كلمة type-key ولكن هذا أمر غير متعمد لأنه يجب عليّ دائمًا تعيين نوع على NUM (أيضًا في الكائن عمليات).

كيف يمكنني حل هذا الخطأ التجميع الصغير؟

هل كانت مفيدة؟

المحلول 6

بناءً على كيفن رايت إجابه لقد حللت المشكلة الآن:

package de.traitwith

import de.traitwith.Operations._
import de.traitwith._

object TraitWithTest {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)
    val n1 = NumHolder(o1)
    val n2 = NumHolder(a1)
    println("e1 + e2: " + add(e1, e2))
    println("o1 + o2: " + add(o1, o2))
    try { println("e1 + o2: " + add(e1, o2)) } catch { case e => println(e) }
    println("o1 + e2: " + add(o1, e2))
    try { println("a1 + e2: " + add(a1, e2)) } catch { case e => println(e) }
    println("n1 + n2: " + add(n1, n2))
    println("o1 + n2: " + add(o1, n2))
  }
}

object Operations {
  def add(a: Num, b: Num): Num = a -> b match {
    case (a1: Odd, b1: Odd) => a1 + b1
    case (a1: Odd, b1: Even) => Odd(a1.x + b1.x, a1.y + b1.y)
    case (a1: Odd, b1: All) => Odd(a1.x + b1.x, a1.y + b1.x)
    case (a1: Odd, b1: NumHolder) => add(a1, b1.x)
    case (a1: Even, b1: Even) => a1 + b1
    case (a1: Even, b1: NumHolder) => add(a1, b1.x)
    case (a1: All, b1: All) => a1 + b1
    case (a1: All, b1: NumHolder) => add(a1, b1.x)
    case (a1: NumHolder, b1: NumHolder) => a1 + b1
    case (a1: NumHolder, b1: Odd)=> add(a1.x, b1)
    case (a1: NumHolder, b1: Even) => add(a1.x, b1)
    case (a1: NumHolder, b1: All) => add(a1.x, b1)
    case _ => error("can't add " + b + " to " + a)
  }
}

abstract class Num

final case class Odd(x: Double, y: Double) extends Num {
  def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  def +(a: NumHolder) = NumHolder(add(x, a.x))
}

ليس لدي المزيد من الطرق في النوع الفائق - آمل ، في نفس اليوم ، أن هذا لن يسبب مشاكل. من الممكن حذف جميع الطرق الإضافية في الفصول الدراسية ولكن في طلبي الحقيقي لدي فصول أكبر لذلك أحتاج إليها هناك.

نصائح أخرى

مشكلتك هي أنك تحاول استخدام الميزات الموجهة للكائن ، مثل الفئات والميراث ، مع تصميم غير موجه نحو الكائن.

الكل نقطة من OOP هو أنك لا تتوقف عن ماهية الفصل. استخدام ، بدلاً من ذلك ، تعدد الأشكال لتحقيق النتائج. أنا أحب بشكل خاص هذه الورقة في توضيح كيف من المفترض أن تعمل OO ، ولكن لا يوجد نقص في الموارد الموجودة في هذا الصدد.

تعديل

على سبيل المثال ، يترجم الرمز المقدم تقريبًا إلى ما يلي ، ناقص الأشياء التي لا تعمل (لا يتم تجميع الرمز المقدم على وجه التحديد بسببها).

abstract class Num {
  def +(n: Num): Num
  def plus(n1: Int, n2: Int): Num
  def plus(n1: Double, n2: Double): Num
  def plus(n: Double): Num
}

case class Even(e1: Int, e2: Int) extends Num {
  override def +(n: Num) = n.plus(e1, e2)
  override def plus(n1: Int, n2: Int) = Even(e1 + n1, e2 + n2)
  override def plus(n1: Double, n2: Double) = Odd(n1 + e1, n2 + e2)
  // the code provided references o1 and o2, which are not defined anywhere for Even
  // so I'm providing an alternate version
  override def plus(n: Double) = Odd(n + e1, n + e2)
}

case class Odd(o1: Double, o2: Double) extends Num {
  override def +(n: Num) = n.plus(o1, o2)
  override def plus(n1: Int, n2: Int) = throw new UnsupportedOperationException("Even#+(Odd)")
  override def plus(n1: Double, n2: Double) = Odd(o1 + n1, o2 + n2)
  override def plus(n: Double) = throw new UnsupportedOperationException("Even#+(Odd)")
}

case class All(a1: Double) extends Num {
  override def +(n: Num) = n.plus(a1)
  // the code provided references o1 and o2, which are not defined anywhere for All
  // so I'm providing an alternate version
  override def plus(n1: Int, n2: Int) = Odd(a1 + n1, a1 + n2)
  override def plus(n1: Double, n2: Double) = Odd(n1 + a1, n2 + a1)
  override def plus(n: Double) = All(a1 + n)
}

يبدو لي أنه يمكن تحسينه مع نمط الزائر ، وهو أمر منطقي ، بالنظر إلى أنه يستهدف نفس المشكلات التي يتوافق معها عادة.

لإخفاء إعلان معين: هناك فئة نوع لذلك ...

Scala تدعم بالفعل تعدد الأشكال المخصصة على "الأرقام" عبر Numeric, ، وهذا ما هو ما أنت حقًا يريد: http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/scala/math/numeric.html

ولكن إذا كان هذا المخطط متساويًا/غريبًا/كله هو ما تفعله بالفعل ، وليس مجرد مثال مفتعل ، فيمكنك دائمًا لف فئة النوع الخاصة بك!


دعنا نسميها Addable:

case class Even(x:Int, y:Int)
case class Odd(x:Double, y:Double)
case class All(x:Double)

abstract class Addable[A, B] {
  def add(a: A, b: B): A
}

implicit object EvenCanAddEven extends Addable[Even, Even] {
  def add(a:Even, b:Even) = Even(a.x+b.x, a.y+b.y)
}

implicit object OddCanAddOdd extends Addable[Odd, Odd] {
  def add(a:Odd, b:Odd) = Odd(a.x+b.x, a.y+b.y)
}

implicit object OddCanAddEven extends Addable[Odd, Even] {
  def add(a:Odd, b:Even) = Odd(a.x+b.x, a.y+b.y)
}

implicit object AllCanAddAll extends Addable[All, All] {
  def add(a:All, b:All) = All(a.x+b.x)
}

def add[A,B](a:A, b:B)(implicit tc: Addable[A,B]) =
  tc.add(a, b)


val e1 = Even(2, 4)
val e2 = Even(1, 3)
val o1 = Odd(1.25, 3.75)
val o2 = Odd(7.25, 9.25)
val a1 = All(5.5)
val a2 = All(3.5)

println("e1 + e2: " + add(e1, e2))
println("o1 + o2: " + add(o1, o2))
println("e1 + o2: " + add(e1, o2)) //compiler should fail this line
println("o1 + e2: " + add(o1, e2))
println("a1 + e1: " + add(a1, e2))

تنصل: لم أختبر الرمز بالفعل ، فإن هذا الجهاز لم يثبت (بعد) Scala

حل بديل ، عندما لا تكون الأنواع معروفة حتى وقت التشغيل:

sealed trait Num
case class Even(x:Int, y:Int) extends Num
case class Odd(x:Double, y:Double) extends Num
case class All(x:Double) extends Num

object operations {
  def add(a: Num, b: Num) : Num = (a,b) match {
    case (a1:Even, b1:Even) => Even(a1.x+b1.x, a1.y+b1.y)
    case (a1:Odd, b1:Odd) => Odd(a1.x+b1.x, a1.y+b1.y)
    case (a1:Odd, b1:Even) => Odd(a1.x+b1.x, a1.y+b1.y)
    case (a1:All, b1:All) => All(a1.x, b1.x)
    case _ => error("can't add " + a + " to " + b)
  }
}

الخدعة هنا هي أن يلفت كلتا المعلمة أولاً في tuple ، لذلك يكون لديك كائن واحد لتطابقه على النمط.

تحديث

بعد تحريرك ؛ لا يبدو أنك بحاجة إلى النوع التجريدي A في أي مكان ، لماذا لا تغادر فقط Num كسممة علامة وتحديد طريقة + بشكل منفصل في كل فئة فرعية؟

sealed abstract trait Num

case class Odd(x: Double, y: Double) extends Num {
  def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  def +(a: NumHolder) = NumHolder(x + a.x)
}

لا أعرف ما إذا كان بإمكاني حل مشكلتك ، لكن أثناء التفكير في الأمر ، حاولت على الأقل الحصول على مثالك على التجميع والعمل ، وهو أمر مفيد إلى حد ما على الأقل.

لقد أضفت بعض الضوضاء في شكل toString طرق لتكون قادرة على رؤية نتائج الاستئصال والتعبيرات.

abstract class Num(val a: Double, val b: Double) {
  def +(that: Num): Num
  override def toString = (<z>Num({a}, {b})</z> text)
}

فئة "الملاذ الأخير" ، All, ، يجب أن تكون فئة حالة لجعل المطابقة أكثر سلاسة ، ولكن بما أنه سيتم موروثه لأن فئة الحالة الحقيقية لن تعمل بشكل جيد. كائن مصاحب مع apply و unapply الطرق يحل ذلك.

و All يمكن التعامل مع مصطلحات مماثلة ، ولكن لا تحاول التعامل معها Even و Odd الشروط لأن هذه الشروط سرية AllS في نفس الوقت.

class All(override val a: Double, override val b: Double) extends Num(a, b) {
  def +(that: Num): Num = that match {
    case All(n) => All(this.a + n)
    case _      => error("I don't know this subtype")
  }
  override def toString = (<z>All({a})</z> text)
}
object All {
  def apply(num: Double) = new All(num, num)
  def unapply(num: All) = Some(num.a)
}

الآن ، الطريق Evenرمل Oddيمكن تقطير عمل S إلى سمات ، لكن ليس من الضروري لهذا المثال. عدم القيام بذلك يبسط الميراث ، ولكن ربما يتعارض مع نقطة المثال ، لا أعرف.

و Even يعرف كيفية التعامل Even و Odd المصطلحات ، ولكنها تمرر أي شخص آخر إلى الطبقة الفائقة. مرة أخرى ، إنها فئة حالة فو لأغراض مطابقة.

class Even(override val a: Double, override val b: Double) extends All(a, b) {
  override def +(that: Num): Num = that match {
    case Even(a, b) => Even(this.a + a, this.b + b)
    case Odd(a, b)  => Odd(this.a + a, this.b + b)
    case x => super.+(x)
  }
  override def toString = (<z>Even({a}, {b})</z> text)
}
object Even {
  def apply(a: Double, b: Double) = new Even(a, b)
  def unapply(num: Even) = Some((num.a, num.b))
}

و Odd يعرف كيفية التعامل Even شروط ، ولكنها ترفض التعامل معها Odd منها (لقد غيرت هذا من مثالك لجعل التورية الضعيفة ، اهلا وسهلا بك).

class Odd(override val a: Double, override val b: Double) extends All(a, b) {
  override def +(that: Num): Num = that match {
    case Even(a, b) => Odd(this.a + a, this.b + b)
    case Odd(a, b)  => error("Adding two Odds is an odd thing to do")
    case x => super.+(x)
  }
  override def toString = (<z>Odd({a}, {b})</z> text)
}
object Odd {
  def apply(a: Double, b: Double) = new Odd(a, b)
  def unapply(num: Odd) = Some((num.a, num.b))
}

حسنًا ، لنأخذها لتدور.

object Try {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    println("e1 + e2: " + (e1 + e2))
    println("e1 + o2: " + (e1 + o2))
    try { println("o1 + o2: " + (o1 + o2)) } catch { case e => println(e) }
    println("o1 + e2: " + (o1 + e2))
    println("a1 + e1: " + (a1 + e2))
  }
}

يبدو أن الأداء العام قد يساعدك. جرب شيئًا كهذا:

class Supertype[A <: Supertype] {
    def operation(s: A) {

    }
}

class Subtype extends SuperType[Subtype] {
    override def operation(s: Subtype) {

    }
}

وصف مشكلتك ليس واضحًا جدًا ، لذلك هذا تخمين ...

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top