Skip to content

Instantly share code, notes, and snippets.

@axelGschaider
Last active January 3, 2016 04:28
Show Gist options
  • Save axelGschaider/8408802 to your computer and use it in GitHub Desktop.
Save axelGschaider/8408802 to your computer and use it in GitHub Desktop.
There has got to be a better way
trait Model[A <: Model[A]] {
val internalKey:String
def key:Key[A] = Key[A](internalKey)
// removed this function because it did not make my intent clear.
// def isThatYou(k:Key[A]) = key == k
}
case class Key[A <: Model[A]]( keyVal:String )
case class ClassOne(internalKey:String) extends Model[ClassOne] // <- need to retype the type . . . error prone
case class ClassTwo(internalKey:String) extends Model[ClassTwo]
case class ClassThree(internalKey:String) extends Model[ClassTwo] // See! Here I went wrong with retyping. Not all that
// bad though. Trying to define a DAO[ClassThree] will
// result in compile time errors.
// this is the use case I really need!
// Trying to use a Key[ClassOne] on a DAO[ClassTwo] will result in a compiletime error
trait DAO[A <: Model[A]] {
def get(key:Key[A]):Option[A] = None // dummy implementation
}
// this test
object Test {
// old demonstration code. Commented it since it didn't communicate what I was trying to do.
// def works = ClassOne("foo").isThatYou( ClassOne("bar").key )
// def fails = ClassOne("foo").isThatYou( ClassTwo("bar").key ) // <- compiler error. As expected.
// // ClassTwo key can't be used with ClassOne
val oneDAO = new DAO[ClassOne](){}
// usually we would get our keys somehow differently
// this is just for demonstration reasons
val oneKey = ClassOne("123").key
val twoKey = ClassTwo("456").key
val someOne:Option[ClassOne] = oneDAO.get( oneKey ) //works
oneDAO.get( twoKey ) //fails to compile . . . as expected
}
@izmailoff
Copy link

Here you don't parameterize classes and you don't get desired compiler error:

trait Model {
val internalKey: String
def key: Model
def isThatYou(k: Model) = key == k
}

case class ClassOne(internalKey:String) extends Model {
def key: Model = ClassOne(internalKey)
}

case class ClassTwo(internalKey:String) extends Model {
def key: Model = ClassTwo(internalKey)
}

val k1 = ClassOne("a")

val k2 = ClassTwo("a")

k1.isThatYou(k2) // false

k1.isThatYou(k1) // true

val l: List[Model] = List(k1, k2) // l: List[Model] = List(ClassOne(a), ClassTwo(a))

@izmailoff
Copy link

One more option - you get desired compiler error and don't have to parameterize classes but you have to define type A within subclasses:

trait Model {
type A <: Model
val internalKey: String
def isThatYou(k: A) = this == k
}

case class ClassOne(internalKey:String) extends Model {
type A = ClassOne
}

case class ClassTwo(internalKey:String) extends Model {
type A = ClassTwo
}

val k1 = ClassOne("a")

val k2 = ClassTwo("a")

k1.isThatYou(k2) // error: type mismatch; found : ClassTwo required: k1.A

k1.isThatYou(k1) // true

val l: List[Model] = List(k1, k2) // l: List[Model] = List(ClassOne(a), ClassTwo(a))

@izmailoff
Copy link

Now I think I understand what it was about:

trait Model {
self =>
val internalKey: String
def isThatYou(k: self.type) = this == k
}

case class ClassOne(internalKey:String) extends Model

case class ClassTwo(internalKey:String) extends Model

val k1 = ClassOne("a")

val k2 = ClassTwo("a")

k1.isThatYou(k2) // error: type mismatch; found : ClassTwo required: k1.A

k1.isThatYou(k1) // true

val l: List[Model] = List(k1, k2) // l: List[Model] = List(ClassOne(a), ClassTwo(a))

@axelGschaider
Copy link
Author

Hi izmailoff

many many thanks for your work and time. Really appreciate it!

But actually the point of the whole thing was to have that parameterized Key trait. I'm working in a domain where I will really need keys. But instead of having keys like pure Strings that I can (falsely) stuff everywhere, I want keys that only "fit certain locks".

so for example if there where Book and Car classes. The BookDAO would only accept keys of the type Key[Book] and of course not Key[Car]. So I can't accidentally misuse a key. In the haskell world I saw something like this in the yesod framework, fell in love with it and am now trying to mimic it in scala . . .

I'll be adding a DAO trait to the gist to make my intent more clear.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment