In the following, I'm trying to make a polymorphic function to convert a RawFeatureValue
into a RefinedFeatureValue
.
import shapeless._
object test {
type RawFeatureValue = Int :+: Double :+: String :+: CNil
type RefinedFeatureValue = Int :+: Double :+: CNil
private object convert extends Poly1 {
implicit def caseInt = at[Int](i => i)
implicit def caseDouble = at[Double](d => d)
implicit def caseString = at[String](s => s.hashCode)
}
val a = Coproduct[RawFeatureValue](12)
val b: RefinedFeatureValue = a map convert
}
However, the resulting type is Int :+: Double :+: Int :+: CNil
which is not compatible with RefinedFeatureValue
.
[error] found : shapeless.:+:[Int,shapeless.:+:[Double,shapeless.:+:[Int,shapeless.CNil]]]
[error] required: test.RefinedFeatureValue
[error] (which expands to) shapeless.:+:[Int,shapeless.:+:[Double,shapeless.CNil]]
[error] val b: RefinedFeatureValue = a map convert
[error] ^
How do I tell shapeless that the two Int
s should be treated as one?
As with any universal property, the coproduct can be understood as a universal morphism. Let . Then the coproduct . . . The coproduct of the family are known as the natural injections . (a product in Set, the category of sets, which is the Cartesian product, so it is a tuple of morphisms) to the morphism
The coproduct in the category of sets is simply the disjoint union with the maps ij being the inclusion maps.
In the category of pointed spaces, fundamental in homotopy theory, the coproduct is the wedge sum (which amounts to joining a collection of spaces with base points at a common base point).
Coproduct. The coproduct of a family of objects is essentially the "least specific" object to which each object in the family admits a morphism. It is the category-theoretic dual notion to the categorical product, which means the definition is the same as the product but with all arrows reversed.
Alternatively, the methods on Coproduct
s allow to do it without a Poly
(if your real use case allows it) - and less boilerplate. Let's define
def refine(v: RawFeatureValue): RefinedFeatureValue =
v.removeElem[String]
.left.map(s => Coproduct[RefinedFeatureValue](s.hashCode))
.merge
Then you can do
scala> val a = Coproduct[RawFeatureValue](12)
a: RawFeatureValue = 12
scala> refine(a)
res1: RefinedFeatureValue = 12
scala> val c = Coproduct[RawFeatureValue]("foo")
c: RawFeatureValue = foo
scala> refine(c)
res2: RefinedFeatureValue = 101574
This consists in:
RawFeatureValue
into a String
on the one hand, and the remaining elements on the other (which make a RefinedFeatureValue
), with removeElem
, returning a Either[String, RefinedFeatureValue]
,RefinedFeatureValue
,Either[RefinedFeatureValue, RefinedFeatureValue]
into a single RefinedFeatureValue
.The most straightforward way to do this I can think of would be to map each element into your target coproduct and then unify the result:
import shapeless._
type RawFeatureValue = Int :+: Double :+: String :+: CNil
type RefinedFeatureValue = Int :+: Double :+: CNil
object convert extends Poly1 {
implicit val caseInt = at[Int](Coproduct[RefinedFeatureValue](_))
implicit val caseDouble = at[Double](Coproduct[RefinedFeatureValue](_))
implicit val caseString = at[String](s =>
Coproduct[RefinedFeatureValue](s.hashCode))
}
This works as expected:
scala> val a = Coproduct[RawFeatureValue](12)
a: RawFeatureValue = 12
scala> val b: RefinedFeatureValue = a.map(convert).unify
b: RefinedFeatureValue = 12
scala> val c = Coproduct[RawFeatureValue]("foo")
c: RawFeatureValue = foo
scala> val d: RefinedFeatureValue = c.map(convert).unify
d: RefinedFeatureValue = 101574
This solution isn't bad, but it does seem like it might be useful enough to be a single operation.
Alternatively, if you aren't set on a polymorphic function you can use the convert type class in this library: https://github.com/xdotai/typeless/
Here is a convert type class that transforms one Coproduct to an Option of another Coproduct: https://github.com/xdotai/typeless/blob/master/src/main/scala/coproduct/convert.scala
Add to dependencies:
libraryDependencies += "ai.x" %% "typeless" % "0.2.5"
Code:
scala> import ai.x.typeless.coproduct.Convert.Ops
import ai.x.typeless.coproduct.Convert.Ops
scala> import shapeless._
import shapeless._
scala> type A = String :+: Double :+: CNil
defined type alias A
scala> type B = Double :+: String :+: List[Int] :+: CNil
defined type alias B
scala> Coproduct[A]("test").convert[B]
res0: Option[B] = Some(Inr(Inl(test)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With