Created
April 21, 2014 12:23
-
-
Save milessabin/11141310 to your computer and use it in GitHub Desktop.
Old wine, new bottles: safely migrating/permuting/extending field values between case classes using shapeless 2.0.0's LabelledGeneric.
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
// See http://stackoverflow.com/questions/23192760/using-a-variable-as-an-argument-in-copy-on-a-case-class | |
import shapeless._, record._, syntax.singleton._, ops.hlist.Remove | |
/** | |
* This will be in shapeless 2.1.0 ... | |
* | |
* Permute the elements of the supplied `HList` of type `L` into the same order as the elements of | |
* the `HList` of type `M`. | |
*/ | |
trait Align[L <: HList, M <: HList] extends (L => M) { | |
def apply(l: L): M | |
} | |
object Align { | |
import ops.hlist.Remove | |
def apply[L <: HList, M <: HList](implicit alm: Align[L, M]): Align[L, M] = alm | |
implicit val hnilAlign: Align[HNil, HNil] = new Align[HNil, HNil] { | |
def apply(l: HNil): HNil = l | |
} | |
implicit def hlistAlign[L <: HList, MH, MT <: HList, R <: HList] | |
(implicit select: Remove.Aux[L, MH, (MH, R)], alignTail: Align[R, MT]): Align[L, MH :: MT] = new Align[L, MH :: MT] { | |
def apply(l: L): MH :: MT = { | |
val (h, t) = l.removeElem[MH] | |
h :: alignTail(t) | |
} | |
} | |
} | |
/** | |
* This, or something like it, will be in shapeless 2.1.0 ... | |
* | |
* Utility trait intended for inferring a field type from a sample value and unpacking it into its | |
* key and value types. | |
*/ | |
trait Field { | |
type K | |
type V | |
type F = FieldType[K, V] | |
} | |
object Field { | |
def apply[K0, V0](sample: FieldType[K0, V0]) = new Field { type K = K0; type V = V0 } | |
} | |
object OldWineNewBottle { | |
case class From(s1: String, s2: String) | |
case class To(s2: String, i: Int, s1: String) | |
val from = From("foo", "bar") | |
val fromGen = LabelledGeneric[From] | |
val toGen = LabelledGeneric[To] | |
// Define the type of the i field by example | |
val iField = Field('i ->> 0) | |
val align = Align[iField.F :: fromGen.Repr, toGen.Repr] | |
// extend the fields of From with a field for 'i', permute into | |
// the correct order for To and create a new instance ... | |
val to = toGen.from(align('i ->> 23 :: fromGen.to(from))) | |
assert(to == To("bar", 23, "foo")) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment