Last active
October 19, 2015 17:56
-
-
Save guersam/da7accb77044d4899030 to your computer and use it in GitHub Desktop.
Generic update without `.copy`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | |
} | |
} |
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.
Using Updater
which looks more efficient than Merger
, because we're updating only one field.
Now uses Modifier
so that trait Character
doesn't even need def hp
in itself
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
Not works yet when the base type is given, like:
Need to derive for coproducts.