1

I had a couple of case classes like that:

case class Size(id: Long, size: Int) case class Title(id: Long, title: String) . . . 

I had like 10 of these with pretty much the same functions. I decided to combine them under a common trait, which led to something like this:

trait Property[T]{ val value: T } trait PropertyPair[T]{ val id: Long val prop: Property[T] } case class Size(id: Long, prop: Property[Int]) extends PropertyPair[Int] { def size: Int = prop.value } case class Title(id: Long, prop: Property[String]) extends PropertyPair[String] { def title: String = prop.value } 

Though the code block above seems to be a good solution, and now I can actually define functions under the PropertyPair trait, but the code still smells.

  1. I need to include [T] three times for every property I add
  2. I need to explicitly add property name as an extra function to access it as though it is just a field in the case class.

Now to initialize Title i need to write

Title(101L, Property("Title")) 

instead of

Title(101L, "Title") 

For some reason I am sure there is a much more elegant and less error-prone solution then the one I provided.

1
  • 1
    What are you trying to get out of the inheritance hierarchy? Are you just doing it because you recognize a similarity or because it actually gives you some functionality somewhere you currently don't have / anticipating new use cases? Commented Mar 17, 2018 at 1:52

1 Answer 1

3

You don't need 2 levels and can replace trait with abstract class to make use of its constructor:

sealed abstract class Property[T](val prop: T) { val id: Long } case class Size(id: Long, size: Int) extends Property[Int](size) case class Title(id: Long, title: String) extends Property[String](title) 

Each of these cases has an id value which is required by the Property class, but as you want them to have different names for prop, you can just pass them to the Property constructor as the prop value.

Then it can be used as

val size = Size(101L, 42) val title = Title(202L, "Foo") 

This is a straightforward solution. For a more general case I would suggest you to do it like this:

sealed trait AnyProperty { val id: Long type Prop val prop: Prop } sealed abstract class Property[T]( val prop: T ) extends AnyProperty { type Prop = T } 

(the rest is the same)

Advantages of this approach are that you can use the top trait to refer to any property, for example

def foo[P <: AnyProperty](p: P): Long = p.id foo(size) // 101L 

Of you can refer to the Prop type member, for example

def buh[P <: AnyProperty](p: P): P#Prop = p.prop buh(title) // "Foo" 
Sign up to request clarification or add additional context in comments.

5 Comments

is it actually a hashtag sign in the last code block?
one more question: Would we need a type protection if buh method was placed inside the Property class?
Very interesting. I assume with this you could also take an array of AnyProperty and map it to their prop value by passing the buh method then without any trouble, correct?
@Martee Probably it's just a typo, but to avoid confusion: it's projection (to project/select, not to protect). The answer is no, when inside Property, you can refer to Prop type member directly.
@JordanCutler There will be a trouble with the return type. If you do List[AnyProperty](size, title).map(buh), you will get List(42, "Foo"). But List is a homogenuous collection, i.e. all elements have the same type and in this case it will be derived to Any (lowest common supertype of Int and String). This is probably fine for you, but then you don't need any type projections, just make buh return Any.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.