Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue resolving arity of function args to drive list processing, using Shapeless

The following gist has the code for an idea I am playing with

package com.test1

import scala.language.implicitConversions
import shapeless._
import FromTraversable._
import Traversables._
import Nat._
import Tuples._

trait ToArity[P, N <: Nat]

object ToArity {
  implicit def prod1[P <: Product1[_]] = new ToArity[P, _1] {}
  implicit def prod2[P <: Product2[_, _]] = new ToArity[P, _2] {}
  // ad nauseum...
}

trait SizedHListAux[A, N <: Nat, T <: HList]

object SizedHListAux {
  implicit def base[A, H <: HList] = new SizedHListAux[A, _0, HNil] {}
  implicit def induct[A, H <: HList, N <: Nat, P <: Nat](implicit r: PredAux[N,P], k: SizedHListAux[A, P, H]) = new SizedHListAux[A, N, A :: H] {}
}

trait SomeFun {
  type Result
  def apply(): Result
}

// I want to abstract over A, the contained type in the List
// over P the Product type which is the arg notably its arity
// This means we need to recover arity of the Product type and render it in value space
// and also means that we need to compute the type of the intermediate HList
object SomeFun {
  def produce(m: SomeFun): m.Result = m()

  implicit def fromF1[T, A, P <: Product, N <: Nat, H <: HList](f1: (P => T, List[A]))(implicit k: ToArity[P, N], toI: ToInt[N], l: SizedHListAux[A, N, H], toHL: FromTraversable[H], tp: TuplerAux[H, P]) =
    new SomeFun {
      type Result = (T, List[A])
      def apply(): Result = {
        val (f, as) = f1
        val (ts, rest) = (as.take(toI()), as.drop(toI()))
        f((toHL(ts).get).tupled) -> rest
      }
    }
  // Debug Arity checker
  def printArity[P <: Product, N <: Nat](p: P)(implicit k: ToArity[P, N], toI: ToInt[N]) = println("Arity: " + toI())
}

object Test {
  val thedata = List("foo", "bar", "baz", "bob")
  val tfn = (x: (String, String)) => println("%s and %s".format(x._1, x._2))
  def foo = SomeFun.printArity("a" -> "b")
  //def doit = SomeFun.produce((tfn, thedata)) // Adding this line does not compile
}

The idea is that you use a function's argument arity, in this case the arity of a Product type, to drive parsing of an associated List[A]. Kind of like using sticky tape to peel off layers of graphene from graphite, i.e. the type of the functions pull things out of the list. This is just an sketch using a single contained type, but I imagine it could be generalised. The important facet is that the functions themselves are unaware of the List processing.

However...the concept seems to fail when trying to resolve the ToArity[P,N] implicit. On its own ToArity is resolvable as evidenced by printArity().

Can someone shed some light on why this is not resolvable in the context of fromF1? Is it that it can't resolve all of the dependent implicits and then registers the error with the first, i.e. an N cannot be found to satisfy ToArity, ToInt and SizedHListAux?

like image 827
dconlon Avatar asked Dec 17 '12 01:12

dconlon


1 Answers

Update: I just saw your edit, which means you've already addressed the problem noted in the first couple of paragraphs here, but I hope the rest is useful.

The problem is that your SizedHListAux instance isn't being inferred:

scala> implicitly[SizedHListAux[String, _1, String :: HNil]]
<console>:25: error: could not find implicit value for parameter e...

Fortunately this is an easy fix:

object SizedHListAux {
  implicit def base[A] = new SizedHListAux[A, _0, HNil] {}
  implicit def induct[A, H <: HList, N <: Nat, P <: Nat](implicit
    r: PredAux[N, P],
    k: SizedHListAux[A, P, H]
  ) = new SizedHListAux[A, N, A :: H] {}
}

I've just removed the R <: PredAux[N, P] type parameter and typed r appropriately. I've also removed the unused type parameter H on base, even though it wasn't causing problems—it just wasn't doing anything.

That's almost all—now all the instances for fromF1 get inferred:

scala> SomeFun.fromF1((tfn, thedata))
res0: SomeFun{type Result = (Unit, List[String])} = SomeFun$$anon$1@7eacbeb

You're still not going to get a view from the type of (tfn, thedata) to SomeFun, though. Consider the following simplified example:

scala> trait Foo
defined trait Foo

scala> trait Bar[A, B]
defined trait Bar

scala> implicit def toInt[F <: Foo, X](f: F)(implicit ev: Bar[F, X]) = 42
toInt: [F <: Foo, X](f: F)(implicit ev: Bar[F,X])Int

scala> implicit object fooBar extends Bar[Foo, String]
defined module fooBar

scala> toInt(new Foo {})
res0: Int = 42

scala> implicitly[Foo => Int]
<console>:12: error: No implicit view available from Foo => Int.
              implicitly[Foo => Int]

So even though we have an implicit method in scope that will transform a Foo into an Int, that X causes problems for the compiler when it tries to find a view from Foo to Int.

In your case I'd avoid this limitation by skipping the SomeFun business and having a method that takes a (P => T, List[A]) and returns a (T, List[A]).

I'll also observe that both ToArity and SizedHListAux seem unnecessary, since you can gather the same evidence with TuplerAux, LengthAux, and LUBConstraint. For example:

import shapeless._

trait SomeFun {
  type Result
  def apply(): Result
}

implicit def fromF1[T, A, P <: Product, N <: Nat, H <: HList](
  f1: (P => T, List[A])
)(implicit
  tp: TuplerAux[H, P],
  hl: LengthAux[H, N],
  toHL: FromTraversable[H],
  allA: LUBConstraint[H, A],
  toI: ToInt[N]
) = new SomeFun {
  type Result = (T, List[A])
  def apply(): Result = {
    val (f, as) = f1
    val (ts, rest) = (as.take(toI()), as.drop(toI()))
    f((toHL(ts).get).tupled) -> rest
  }
}

And then:

val tfn = (x: (String, String)) => println("%s and %s".format(x._1, x._2))
val thedata = List("foo", "bar", "baz", "bob")
val sf = fromF1((tfn, thedata))

And finally:

scala> sf()
foo and bar
res2: (Unit, List[String]) = ((),List(baz, bob))

No annoying prodN boilerplate necessary.

like image 52
Travis Brown Avatar answered Oct 15 '22 18:10

Travis Brown