Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shapeless: map from coproduct to different coproduct

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 Ints should be treated as one?

like image 957
Utaal Avatar asked Apr 27 '15 14:04

Utaal


People also ask

Is the coproduct a universal morphism?

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

What is the coproduct in the category of sets?

The coproduct in the category of sets is simply the disjoint union with the maps ij being the inclusion maps.

What is the coproduct of a pointed space?

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).

What is coproduct of a family of objects?

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.


3 Answers

Alternatively, the methods on Coproducts 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:

  • splitting 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],
  • mapping on the left of the result, to transform it and pack it into a RefinedFeatureValue,
  • and merge the resulting Either[RefinedFeatureValue, RefinedFeatureValue] into a single RefinedFeatureValue.
like image 153
Alex Archambault Avatar answered Oct 23 '22 18:10

Alex Archambault


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.

like image 41
Travis Brown Avatar answered Oct 23 '22 18:10

Travis Brown


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)))
like image 34
azuras Avatar answered Oct 23 '22 19:10

azuras