How can I avoid duplicating a type bound in an object class in Scala?

eancu

Yes I have checked the very similarly titled question but the answer given is not as helpful to me as I am new to Scala and am having trouble understanding it.

I'm writing some functions that check a list of cards and return a score based on the result of the list. Technically, it checks a list of groups of cards, however I am simplifying the code for the purposes of this question.

Now, I want these functions to be extensible to different types of scoring. For instance, if all the cards are Hearts, then we may give them 1 point. However, in another ruleset, it may give 3 points.

I have a points wrapper that translates to a final score. This means that a type of point may translate to a different final score than another type of point. The purpose here is to allow you to customise the scoring and play the card game in a slightly different way.

You will see in the sample code below, but I end up getting a lot of repetition in my method declarations, namely having to write [T <: HandPoints[T]] over and over again.

All of the def methods have been written in an object, so I cannot add the type parameter to the class.

I imagine there's probably a neat solution to extract these methods outside of the class, but I want the methods that check the cards to not be repeated, so it makes a lot of sense to me to have them declared statically in an object

Here is the HandPoints trait:

trait HandPoints[T] {
  def toHandScore: HandScore
  def zero: T
  def add(that: T): T
}

case class RegularPoint(points: Int) extends HandPoints[RegularPoint] {
  override def toHandScore: HandScore = HandScore(points)
  override def zero: RegularPoint = RegularPoint(0)
  override def add(that: RegularPoint): RegularPoint = RegularPoint(points + that.points)
}

case class DoublingPoints(points: Int) extends HandPoints[DoublingPoints] {
  override def toHandScore: HandScore = HandScore(points*2)
  override def zero: DoublingPoints = DoublingPoints(0)
  override def add(that: DoublingPoints): DoublingPoints = DoublingPoints(points + that.points)
}

case class HandScore(score: Int) {

}

Here are the functions I wrote to assess the cards

  
  trait Card {
    def getValue: Int
    def getSuit: String
  }


  def scored[T <: HandPoints[T]](score: T)(boolean: Boolean): T = {
    if (boolean) score else score.zero
  }

  def isAllEvens[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.forall(_.getValue % 2 == 0)
    }
  }

  def isAllReds[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.forall(List("HEARTS", "DIAMONDS").contains(_))
    }
  }

  def isAllNoDuplicates[T <: HandPoints[T]](score: T)(cards: List[Card]): T = {
    scored(score) {
      cards.distinct == cards
    }
  }
  
  val regularGameCriteria: List[List[Card] => RegularPoint] = List(
    isAllEvens(RegularPoint(1)),
    isAllReds(RegularPoint(3)),
    isAllNoDuplicates(RegularPoint(5))
  )
  
  val beginnerGameCriteria: List[List[Card] => RegularPoint] = List(
    isAllEvens(RegularPoint(1)),
    isAllReds(RegularPoint(1)),
    isAllNoDuplicates(RegularPoint(1))
  )

  val superGameCriteria: List[List[Card] => DoublingPoints] = List(
    isAllEvens(DoublingPoints(1)),
    isAllReds(DoublingPoints(3)),
    isAllNoDuplicates(DoublingPoints(5))
  )
  
  def countScore[T <: HandPoints[T]](scoreList: List[List[Card] => T])(melds: List[Card]): T = {
    scoreList.map(f => f(melds)).reduce((a, b) => a.add(b))
  }

  def regularGameScore(cards: List[Card]): RegularPoint = {
    countScore(regularGameCriteria)(cards)
  }

  def beginnerGameScore(cards: List[Card]): RegularPoint = {
    countScore(beginnerGameCriteria)(cards)
  }

  def superGameScore(cards: List[Card]): DoublingPoints = {
    countScore(superGameCriteria)(cards)
  }
Dmytro Mitin

Firstly, you can look how F-bounded polymorphism can be replaced with ad hoc polymorphism (type classes):

https://tpolecat.github.io/2015/04/29/f-bounds.html

Secondly, the bounds [T <: HandPoints[T]] in different methods are actually not code duplication. Different T in different methods are different type parameters. You just called them with the same letter. Bounds for one type parameter do not restrict another type parameter.

I'm curious why you consider a code duplication the [T <: HandPoints[T]] in different methods and not (score: T) or (cards: List[Card]). I guess because most people think about terms, considering types less important.

Thirdly, you should start to explore OOP (or FP with type classes or some their mix) i.e. organize your methods into classes/objects (or type classes) with some behavior. Now a bunch of (static) methods looks like procedural programming.

For example, for a start we can introduce two classes HandPointsHandler and Criteria (you can pick up better names):

case class HandScore(score: Int)

trait HandPoints[T] {
  def toHandScore: HandScore
  def zero: T
  def add(that: T): T
}

case class RegularPoint(points: Int) extends HandPoints[RegularPoint] {
  override def toHandScore: HandScore = HandScore(points)
  override def zero: RegularPoint = RegularPoint(0)
  override def add(that: RegularPoint): RegularPoint = RegularPoint(points + that.points)
}

case class DoublingPoints(points: Int) extends HandPoints[DoublingPoints] {
  override def toHandScore: HandScore = HandScore(points*2)
  override def zero: DoublingPoints = DoublingPoints(0)
  override def add(that: DoublingPoints): DoublingPoints = DoublingPoints(points + that.points)
}

trait Card {
  def getValue: Int
  def getSuit: String
}

// new class
class HandPointsHandler[T <: HandPoints[T]] {
  def scored(score: T)(boolean: Boolean): T =
    if (boolean) score else score.zero

  def isAllEvens(score: T)(cards: List[Card]): T =
    scored(score) {
      cards.forall(_.getValue % 2 == 0)
    }

  def isAllReds(score: T)(cards: List[Card]): T =
    scored(score) {
      cards.forall(List("HEARTS", "DIAMONDS").contains)
    }

  def isAllNoDuplicates(score: T)(cards: List[Card]): T =
    scored(score) {
      cards.distinct == cards
    }

  def countScore(scoreList: List[List[Card] => T])(melds: List[Card]): T =
    scoreList.map(_.apply(melds)).reduce(_ add _)
}

// new class
class Criteria[T <: HandPoints[T]](handler: HandPointsHandler[T], points: List[T]) {
  val gameCriteria: List[List[Card] => T] = {
    List(
      handler.isAllEvens _,
      handler.isAllReds _,
      handler.isAllNoDuplicates _
    ).zip(points).map { case (f, point) => f(point) }
  }
}

val points135 = List(1, 3, 5)
val points111 = List(1, 1, 1)

val regularPointsHandler = new HandPointsHandler[RegularPoint]

val regularGameCriteria: List[List[Card] => RegularPoint] =
  new Criteria[RegularPoint](regularPointsHandler, points135.map(RegularPoint)).gameCriteria

val beginnerGameCriteria: List[List[Card] => RegularPoint] =
  new Criteria[RegularPoint](regularPointsHandler, points111.map(RegularPoint)).gameCriteria

val doublingPointsHandler = new HandPointsHandler[DoublingPoints]

val superGameCriteria: List[List[Card] => DoublingPoints] =
  new Criteria[DoublingPoints](doublingPointsHandler, points135.map(DoublingPoints)).gameCriteria

def regularGameScore(cards: List[Card]): RegularPoint =
  regularPointsHandler.countScore(regularGameCriteria)(cards)

def beginnerGameScore(cards: List[Card]): RegularPoint =
  regularPointsHandler.countScore(beginnerGameCriteria)(cards)

def superGameScore(cards: List[Card]): DoublingPoints =
  doublingPointsHandler.countScore(superGameCriteria)(cards)

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How can I avoid duplicating the same object on the screen using ID?

How to avoid duplication of type bound in Scala

How can I determine the class of an object in Scala?

How can I avoid duplicating business logic in my Redux reducers?

How can I avoid duplicating code inside a for + if chain?

How can I avoid duplicating props across similar Vue components?

how can I avoid duplicating django sql queries when recursing

How can I parameterize a class with an object's type to get an instance of it in Scala?

How to avoid duplicating a property in a nested class structure?

Can I use a view bound in a Scala value class?

Avoid duplicate type alias in Scala class and companion object

How can I indicate the type of the context of a bound member function called from an object?

How can I avoid duplicating code while creating separate vectors from dataframe?

With python click, how can I avoid duplicating argument code used by multiple subcommands

how can I edit my code that has shown results separately base on inputs and avoid duplicating

How can I use a Class object as a parameterized type?

How to bound the class type of the subclass

How can I express a trait bound on a type parameter for another generic type's trait bound?

How can I solve a rigid type variable bound error?

How can I bound an associated type to work with the `?` operator?

How can I apply a macro annotation to a case class with a context bound?

How would I import tables into another class (/object??) so I can run queries on it in the other class/object? [slick 3.0][scala]

How can I store a string without duplicating it?

How can I avoid not-exhaustive-match warnings with Scala RegexParsers

In Scala, how can I avoid casting a function parameter?

How can I have a negation type in Scala?

How can I create A Class with Trait On Scala?

How can I create a new specific type of class when extending a trait in scala

How can link to class type create another object of class type