Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pulling out shapeless polymorphic functions that have dependencies

New to shapeless and I have a question on using polymorphic functions that need some dependencies. I basically have this code and want to pull somePoly object out of the run method:

import shapeless._
object SomeObject {
    type SomeType = Int :+: String :+: (String, Int) :+: CNil

    def run( someList: List[SomeType], someInt:Int, someWord:String ) = {

       object somePoly extends Poly1 {
           implicit def doIt = at[Int]( i => i + someInt + someWord.length)
           implicit def doIt2 = at[String]( i => i.length + someWord.length)
           implicit def doIt3 = at[(String, Int)]( i => i._1.length + someWord.length)
       }

       someList.map( _.map(somePoly) )
    }
}

One way I thought of doing it was like this, but it seems messy:

object TypeContainer {
  type SomeType = Int :+: String :+: (String, Int) :+: CNil
}
case class SomePolyWrapper( someList: List[TypeContainer.SomeType], someInt:Int, someWord:String ){
    object somePoly extends Poly1 {
       implicit def doIt = at[Int]( i => i + someInt + someWord.length)
       implicit def doIt2 = at[String]( i => i.length + someWord.length)
      implicit def doIt3 = at[(String, Int)]( i => i._1.length + someWord.length)
   }
}  
object SomeObject {

    def run( someList: List[TypeContainer.SomeType], someInt:Int, someWord:String ) = {

       val somePolyWrapper = SomePolyWrapper(someList, someInt, someWord)

       someList.map( _.map(somePolyWrapper.somePoly) )
    }
}

Anyone have any advice?

like image 793
azuras Avatar asked Dec 03 '15 18:12

azuras


1 Answers

The limitations of Scala's implicit resolution system mean the Poly definition needs to be a stable identifier, which makes this kind of thing more painful than it should be. As I mentioned on Gitter, there are a couple of workarounds that I know of (there may be others).

One approach would be to make the Poly1 a PolyN, where the extra arguments are for the someInt and someWord values. If you were mapping over an HList, you'd then use mapConst and zip to make the input HList have the right shape. I've never done this for a coproduct, but something similar is likely to work.

Another approach is to use a custom type class. In your case that might look something like this:

import shapeless._

trait IntFolder[C <: Coproduct] {
  def apply(i: Int, w: String)(c: C): Int
}

object IntFolder {
  implicit val cnilIntFolder: IntFolder[CNil] = new IntFolder[CNil] {
    def apply(i: Int, w: String)(c: CNil): Int = sys.error("Impossible")
  }

  def instance[H, T <: Coproduct](f: (H, Int, String) => Int)(implicit
    tif: IntFolder[T]
  ): IntFolder[H :+: T] = new IntFolder[H :+: T] {
    def apply(i: Int, w: String)(c: H :+: T): Int = c match {
      case Inl(h) => f(h, i, w)
      case Inr(t) => tif(i, w)(t)
    }
  }

  implicit def iif[T <: Coproduct: IntFolder]: IntFolder[Int :+: T] =
    instance((h, i, w) => h + i + w.length)

  implicit def sif[T <: Coproduct: IntFolder]: IntFolder[String :+: T] =
    instance((h, i, w) => h.length + i + w.length)

  implicit def pif[T <: Coproduct: IntFolder]: IntFolder[(String, Int) :+: T] =
    instance((h, i, w) => h._1.length + i + w.length)
}

And then you could write a more generic version of your run:

def run[C <: Coproduct](
  someList: List[C],
  someInt: Int,
  someWord: String
)(implicit cif: IntFolder[C]): List[Int] = someList.map(cif(someInt, someWord))

And use it like this:

scala> run(List(Coproduct[SomeType](1)), 10, "foo")
res0: List[Int] = List(14)

scala> run(List(Coproduct[SomeType](("bar", 1))), 10, "foo")
res1: List[Int] = List(16)

The specificity of the operation makes this approach look a little weird, but if I really needed to do something like this for different coproducts, this is probably the solution I'd choose.

like image 123
Travis Brown Avatar answered Sep 30 '22 10:09

Travis Brown