Skip to content

Instantly share code, notes, and snippets.

@guersam
Last active October 19, 2015 17:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guersam/da7accb77044d4899030 to your computer and use it in GitHub Desktop.
Save guersam/da7accb77044d4899030 to your computer and use it in GitHub Desktop.
Generic update without `.copy`
sealed trait Character
case class Player(hp: Int) extends Character
case class Civilian(name: String, hp: Int) extends Character
case class Monster(hp: Int, weakness: String) extends Character
object HittableDemo extends App {
import Hittable.ops._
val c1: Character = Player(1)
assert(c1.hit == Player(0))
val c2 = Civilian("John", 3)
assert(c2.hit == Civilian("John", 1))
assert((c2: Civilian).hit == Civilian("John", 1))
val c3: Character = Monster(2, "Fire")
assert(c3.hit == Monster(1, "Fire"))
assert(Civilian("John", 3).hit.name == "John")
assert(Monster(2, "Fire").hit.weakness == "Fire")
}
trait Hittable[T] {
def apply(t: T): T
}
object Hittable {
object ops {
implicit class HitOps[T: Hittable](t: T) {
def hit: T = implicitly[Hittable[T]] apply t
}
}
// 'Override' default behavior by providing more specific instance
implicit val civilianInstance: Hittable[Civilian] =
new Hittable[Civilian] {
def apply(c: Civilian): Civilian = c.copy(hp = c.hp - 2)
}
// Default instances derivation
def defaultHitFunc(hp: Int) = hp - 1
import shapeless._
import shapeless.ops.record._
private val wHp = Witness('hp)
implicit def genProdHittable[T, Repr <: HList]
(implicit
prod: HasProductGeneric[T],
gen: LabelledGeneric.Aux[T, Repr],
modifier: Modifier.Aux[Repr, wHp.T, Int, Int, Repr]
): Hittable[T] =
new Hittable[T] {
def apply(t: T): T = gen.from(modifier(gen.to(t), defaultHitFunc))
}
implicit def cnilHittable: Hittable[CNil] =
new Hittable[CNil] {
def apply(t: CNil): CNil = t
}
implicit def cconsHittable[H, T <: Coproduct, F, A]
(implicit
uh: Lazy[Hittable[H]],
ut: Lazy[Hittable[T]]
): Hittable[H :+: T] =
new Hittable[H :+: T] {
def apply(t: H :+: T): H :+: T = t match {
case Inl(h) => Inl(uh.value(h))
case Inr(t) => Inr(ut.value(t))
}
}
implicit def genCoprodHittable[T, Repr <: Coproduct]
(implicit
coprod: HasCoproductGeneric[T],
gen: Generic.Aux[T, Repr],
hittable: Lazy[Hittable[Repr]]
): Hittable[T] =
new Hittable[T] {
def apply(t: T): T = gen.from(hittable.value(gen.to(t)))
}
}
@guersam
Copy link
Author

guersam commented Oct 16, 2015

Not works yet when the base type is given, like:

val c: Character = Player(1)
c.hit // cannot compile

Need to derive for coproducts.

@guersam
Copy link
Author

guersam commented Oct 19, 2015

Now working with the base type, most of the code is copied from the corresponding shapeless example. Let's boil it down to the essence for the next.

@guersam
Copy link
Author

guersam commented Oct 19, 2015

Using Updater which looks more efficient than Merger, because we're updating only one field.

@guersam
Copy link
Author

guersam commented Oct 19, 2015

Now uses Modifier so that trait Character doesn't even need def hp in itself

@guersam
Copy link
Author

guersam commented Oct 19, 2015

Removed ModifyRepr and allow overriding default behavior, even if the base type is given in compile time

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