I'm trying to compare poker hands as shown below. I've been playing around with different type operators but would be interested in some guidance. My goal is to have an abstract parent class that declares Ordered (so that it doesn't need to be declared on each subclass), but the parameterization would be such that each subclass can only be compared with an instance of the same class.
For example, below, a HighCard can only be compared with another HighCard, TwoPair with another TwoPair, etc.
sealed abstract class HandValue(rank: Int) extends Ordered[?]
case class HighCard(high: Int) extends HandValue(0){
def compare(that: HighCard) = ...
}
case class TwoPair(high: Int, big: Int, sm: Int) extends HandValue(2) {
def compare(that: TwoPair) = ...
}
F-bounded polymorphism is one common way to accomplish this kind of thing:
sealed abstract class HandValue[A <: HandValue[A]](rank: Int) extends Ordered[A]
case class HighCard(high: Int) extends HandValue[HighCard](0){
def compare(that: HighCard) = ...
}
case class TwoPair(high: Int, big: Int, sm: Int) extends HandValue[TwoPair](2) {
def compare(that: TwoPair) = ...
}
It may feel a bit like boilerplate to have to hand yourself as a type parameter to the thing you're extending, but it's a very convenient way to be able to talk specifically about subclass types in the parent.
Related
Consider the following segment of code -
abstract class Vehicle {
val name: String
}
case class Car(name: String) extends Vehicle
case class Truck(name: String) extends Vehicle
abstract class VehicleContainer[T <: Vehicle] {
def compare(that: VehicleContainer[T]): Int
}
class CarContainer(wheels: Int) extends VehicleContainer[Car] {
override def compare(that: CarContainer): Int = ???
}
The intention here is to have a compare method on the VehicleContainer that can be defined for each specific instance of VehicleContainer. The compare method comparison clause needs to be unique for each instance, because it could be comparing using attributes specific to that instance and hence not defined in the abstract base class VehicleContainer.
The trouble is that this does not work in it's current form, i.e. the override for compare is illegal. What I am not able to understand is how to accomplish this - define a base class that indicates that the sub classes need to implement a compare method, where the method argument to that compare method is that sub class itself. Would appreciate a pointer to the right concept here if it's some straightforward generics concept that I am missing here.
Thanks!
One way to solve your problem could be use of F-bounded polymophism. You would just need one additional type parameter:
abstract class Vehicle {
val name: String
}
case class Car(name: String) extends Vehicle
case class Truck(name: String) extends Vehicle
abstract class VehicleContainer[T <: Vehicle, V <: VehicleContainer[T, V]] {
def compare(that: V): Int
}
class CarContainer(wheels: Int) extends VehicleContainer[Car, CarContainer] {
override def compare(that: CarContainer): Int = ???
}
After some discussions i would like to emphasise, that the question is related of using inheritance as in OOP over Product/Sum type as in FP or vice versa. What you prefer and what are your suggestions! So...
I have some doubts - what is the cleaner (clearer) way to define the following:
For example I prefer
sealed trait Shape
case class Square(length: Int) extends Shape
case class Rhombus(length: Int) extends Shape
instead of
sealed abstract class Shape(val length: Int)
case class Square(override val length: Int) extends Shape(length)
case class Rhombus(override val length: Int) extends Shape(length)
but then
def draw(shape: Shape): String = shape match {
case Square(length) => length.toString
case Rhombus(length) => length.toString
}
instead of
def draw(shape: Shape): String = shape.length.toString
Definition of the types looks in the first approach less verbose and more consistent but if i need to have a generic logic for all shapes, like draw method, second approach looks clearer.
This would be the prefered way:
sealed trait Shape {
def length: Int
}
case class Square(length: Int) extends Shape
case class Rhombus(length: Int) extends Shape
Here are the few non-idiomatic things in your code:
abstract class are primarely a java compatibility thing, use traits when possible
override a non implemented field feels wrong
val in interfaces should be avoided, always use defs for the abstract, these can be refined with something else
From this book https://leanpub.com/fpmortals/
We can also use a sealed trait in place of a sealed abstract class but there are binary compatibility advantages to using abstract class. A sealed trait is only needed if you need to create a complicated ADT with multiple inheritance.
It's not bad suggestion, but for me it's more inheritance over Product type. With trait it's maybe more a mixin, but still! length is then a part of all shapes even if you don't need it.
If you don't need it for all shapes, then you can't have
def draw(shape: Shape): String = shape.length.toString
in the first place, and not getting it isn't a drawback. But you can also have
sealed trait Shape
sealed trait HasLength extends Shape {
def length: Int
}
// Shape optional, but may be clearer for this case
case class Square(length: Int) extends Shape with HasLength
case class Rhombus(length: Int) extends Shape with HasLength
def draw(shape: Shape): String = shape match {
case shape: HasLength => shape.length.toString
case ... => ... // handle those shapes which don't have length
}
Given a superclass or trait, and assuming an open hierarchy, how can I enforce that all extending classes implement a particular type class?
For instance, assuming the type class Default
trait Default[T] { def default: T }
and some trait Super:
trait Super { }
I would like to enforce that the following (by itself) is not allowed:
class A(val i: Int) extends Super
...while the following is:
class B(val i: Int) extends Super
implicit val bHasDef = new Default[B] { def default = B(42) }
Assuming the above is possible, can I then access the type class evidence for the subtypes from a method within Super? I.e, something like:
trait Super {
def magic: Default[this.type] = ???
}
I hardly think you can enforce that, at least in a simple enough way, maybe it's possible with something more complex like shapeless.
What I would do is add some modifications to the super trait and make it take a self reference to Default
trait Default[T] { def default: T }
trait Super[T] {
self: Default[T] =>
}
class B(val i: Int) extends Super[Int] with Default[Int] {
override def default: Int = ???
}
class A(val i: Int) extends Super[Int] // doesn't compile, needs a Default
This should also solve the second part of your question, the disadvantage is that now one trait is bundled to the other.
I would like to do a generic function able to sort different nested case classes by one of their "sub" argument in common.
For the moment I have done this:
sealed trait SortableByGeoPoint {
val geographicPoint: Int
}
case class A(id: Int, geographicPoint: Int) extends SortableByGeoPoint
case class B(image: String, geographicPoint: Int) extends SortableByGeoPoint
case class C(a: A, other: String)
case class D(b: B, other: Int)
def sortByGeoPoint(sequence: Seq[SortableByGeoPoint]): Seq[SortableByGeoPoint] = sequence.sortBy(_.geographicPoint)
It works well to sort a sequence of class A or class B, but I would like to make it work for a case class C or D (that contain a SortableByGeoPoint sub-class) How can I accomplish this?
I took a look at shapeless but didn't find a way to do this, but any way to do it (knowing that I will add later other classes like the class C or D), would be perfect.
I think you are missing some form of type constraint. The most straight forward way to make your example code compile is by doing:
case class C(a: A, other: String) extends SortableByGeoPoint {
val geographicPoint: Int = a.geographicPoint
}
case class D(b: B, other: Int) extends SortableByGeoPoint {
val geographicPoint: Int = b.geographicPoint
}
This might or not meet your requirements. So, let's make yet another one:
case class Z[T](b: SortableByGeoPoint, other: T)
def sortByGeoPoint2(sequence: Seq[Z]): Seq[Z] = sequence.sortBy(_.b.geographicPoint)
In fact, I can think of many other ways to achieve this same outcome. Too many to enumerate. If you can illustrate a few other requirements I might be able to find a more suitable solution.
I have two case classes that inherit from an abstract base class. I want to define some methods on the abstract base class that use the copy methods on the inheriting case classes (and so return an instance of the child class.) Is there a way to do this using self types?
Example code:
abstract class BaseClass(a: String, b: Int) {
this: case class => //not legal, but I'm looking for something similar
def doubleB(newB: Int) = this.copy(b = b * 2) //doesn't work because BaseClass has no copy
}
case class HasC(a: String, b: Int, c: Boolean) extends BaseClass(a, b) {
def doesStuffWithC(newC: Boolean) = {
...
}
}
case class HasD(a: String, b: Int, D: Double) extends BaseClass(a, b) {
def doesStuffWithD(newD: Double) = {
...
}
}
I've figured out how to get the result I want thanks to this question:
How to use Scala's this typing, abstract types, etc. to implement a Self type?
but it involves adding a makeCopy method to BaseClass and overriding it with a call to copy in each of the child case classes, and the syntax (especially for the Self type) is fairly confusing. Is there a way to do this with Scala's built in self typing?
You can't do what you want because copy needs to know about all the possible parameters. So even if case classes inherited from Copyable, it wouldn't be the copy you needed. Also, if you're going to keep the types straight, you'll be thwarted by Scala's lack of a "MyType". So you can't just extend a base class. However, you could add an abstract method and type annotation:
abstract class BaseClass[C <: BaseClass[_]](a: String, b: Int) {
def setB(b0: Int): C
def doubleB(b0: Int) = setB(b0*2)
}
case class HasC(a: String, b: Int, c: Boolean) extends BaseClass[HasC](a,b) {
def setB(b0: Int) = this.copy(b = b0)
def doesStuffWithC(c0: Boolean) = doubleB(if (c0) b else -b).copy(c = c0)
}
And then you can:
scala> HasC("fish",1,false).doesStuffWithC(true)
res47: HasC = HasC(fish,2,true)
This extra work will be worth it if you have a lot of shared functionality that depends on the ability to copy just b (either many methods, or a small number of complicated methods)--that is, this solves the DRY issue. If instead you want to abstract over HasC and other derived classes, you can either use BaseClass[_] or add yet another level that defines setB(b0: Int): BaseBase or simply forget the type parameterization and use BaseClass as the return type (but recognize that HasC cannot use BaseClass methods and still retain its type identity).
I think you're out of luck. The copy methods on HasC and HasD have different signatures. It's a bit hidden because of the default arguments, but basically the definition in BaseClass wouldn't know which copy method to call.
You could define a makeCopy in the abstract class that takes a copier function that takes Unit and returns a BaseClass, then, in your methods that use it (like doubleB) override them in the case class bodies and make use of makeCopy by passing it an anonymous function that does the work of creating a new copy with the props changed, like so:
package delegatedcopy
abstract class BaseClass(a: String, b:Int){
def aField = a
def bField = b
def doubleB:BaseClass
def makeCopy(copier: () => BaseClass):BaseClass = copier()
}
case class HasC(override val aField: String, override val bField: Int, cField: Boolean) extends BaseClass(aField, bField){
override def doubleB:BaseClass = makeCopy( ()=> HasC(aField, bField * 2, cField) )
}
case class HasD(override val aField: String, override val bField: Int, dField:Double) extends BaseClass(aField, bField){
override def doubleB:BaseClass = makeCopy( ()=> HasD(aField, bField * 2, dField) )
}
A test app that demonstrates it:
import delegatedcopy._
object TestApp extends Application{
val hasC = HasC( "A C object", 5, true)
val hasD = HasD( "A D object", 2, 3.55)
val hasCDoubleB = hasC.doubleB
val hasDDoubleB = hasD.doubleB
println(hasC) // prints HasC(A C object,5,true)
println(hasCDoubleB) //prints HasC(A C object,10,true)
println( hasD ) // prints HasD(A D object,2,3.55)
println( hasDDoubleB ) // prints HasD(A D object,4,3.55)
}
In this way, you are able to keep the makeCopy method the same for all children classes as in the base class, and can probably implement or mix in quite a bit of functionality in the base and case classes while keeping common code in a safe place and being able to pass clients a BaseClass and pattern match on the specific case classes.