I'm trying to deal with an ambiguous implicits problem, and (relatedly) figure out what best practise should be for parameterizing typeclasses. I have a situation where I am using a typeclass to implement a polymorphic method. I initially tried the approach below:
abstract class IsValidTypeForContainer[A]
object IsValidTypeForContainer {
implicit val IntIsValid = new IsValidTypeForContainer[Int] {}
implicit val DoubleIsValid = new IsValidTypeForContainer[Double] {}
}
abstract class IsContainer[A, B: IsValidTypeForContainer] {
def getElement(self: A, i: Int): B
def get[R](self: A, ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = gets.get(self, ref)
def fromList(self: A, other: List[B]): A = ???
}
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
) {
def getElement(i: Int) = isCont.getElement(self, i)
def get[R](ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = isCont.get(self, ref)
def fromList(other: List[B]): A = isCont.fromList(self, other)
}
abstract class GetsFromContainerMax[A, R, B: IsValidTypeForContainer] {
type Out
def get(self: A, ref: R): Out
}
object GetsFromContainerMax {
type Aux[A, R, B, O] = GetsFromContainerMax[A, R, B] { type Out = O }
def instance[A, R, B: IsValidTypeForContainer, O](func: (A, R) => O): Aux[A, R, B, O] = new GetsFromContainerMax[A, R, B] {
type Out = O
def get(self: A, ref: R): Out = func(self, ref)
}
implicit def getsForListInt[A, B: IsValidTypeForContainer](implicit
isCons: IsContainer[A, B],
): Aux[A, List[Int], B, A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst)
}
)
}
Where I have given the GetsContainerMax typeclass three parameters - one for the IsContainer object, one for the reference and one for the data type of the IsContainer object.
When I then try to use this, I get a compile error:
case class List1[B: IsValidTypeForContainer] (
data: List[B]
)
implicit def list1IsContainer[B: IsValidTypeForContainer] = new IsContainer[List1[B], B] {
def getElement(self: List1[B], i: Int): B = self.data(i)
def fromList(self: List1[B], other: List[B]): List1[B] = ???
}
val l1 = List1[Int](List(1,2,3))
implicitly[IsContainer[List1[Int], Int]].get(l1, List(1,2)) // Works
implicitly[List1[Int] => IsContainerOps[List1[Int], Int]] // Works
l1.get(List(1,2)) // Does not work
If I use the -Xlog-implicits build parameter, it tells me that
ambiguous implicit values: both value IntIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Int] and value DoubleIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Double] match expected type example.Test.IsValidTypeForContainer[B]
Which seems to make sense; presumably I am bringing both of these implicits into scope by parameterizing the typeclass with the generic B.
My next thought was therefore to try to reduce the number of generic parameters for IsValidTypeForContainer to the minimum, in order to have only one typeclass in scope per type of R, likeso:
abstract class GetsFromContainerMin[R] {
type Out
def get[A](self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out
}
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
def instance[A, R, O](func: (A, R) => O): Aux[R, O] = new GetsFromContainerMin[R] {
type Out = O
def get(self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out = func(self, ref)
}
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst) // type mismatch - found: List[Any], required: List[_$3]
}
)
}
But this seems to not only not solve the problem, but to generate an additional error in that the compiler can no longer type-check that type B implements IsValidTypeForContainer.
Any help gratefully received.
So I've messed around with this a bit and seem to have found the solution. The typeclass-with-three-parameters approach works, if I use
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
)
instead of
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
)
I am not exactly sure why this is and if anyone would care to respond I'd be interested to know. Are multiple typeclasses created if you use a context bound like in the original example, one for each implementation of IsValidTypeForContainer?
Regarding GetsFromContainerMin, only type classes without polymorphic methods can have constructor method instance (in companion object) because of the lack of polymorphic functions in Scala 2. In Scala 3 you'll be able to write
def instance[R, O](func: [A] => (A, R) => O): Aux[R, O] = ...
So far you have to write
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = new GetsFromContainerMin[List[Int]] {
override type Out = A
override def get[A1](self: A1, ref: List[Int])(implicit isCont: IsContainer[A1, _]): Out = {
val lst = ref.map(isCons.getElement(self, _)).toList
// ^^^^
isCons.fromList(self, lst)
// ^^^^
}
}
}
I guess compile errors are pretty clear
type mismatch;
found : self.type (with underlying type A1)
required: A
Regarding your first question,
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B]
)
is desugared to
implicit class IsContainerOps[A, B](self: A)(implicit
ev: IsValidTypeForContainer[B],
isCont: IsContainer[A, B]
)
but order of implicits is significant. If you modify IsContainerOps to
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
ev: IsValidTypeForContainer[B],
)
then l1.get(List(1,2)) will compile.
Implicits are resolved from left to right. You want firstly A to be inferred (from self), then IsContainer[A, B] to be resolved, so B to be inferred and IsValidTypeForContainer[B] to be resolved and not vice versa firstly A to be inferred, then IsValidTypeForContainer[B] to be resolved, and at this step B is not restricted, there is no connection between A and B, so possibly B can be not inferred or inferred to be Nothing, so IsContainer[A, B] is not resolved. I'm a little simplifying (not every time when you swap implicits you break resolution) but general strategy of implicit resolution and type inference is as I described.
Related
I'm using Shapeless to accumulate materialized values in Akka as an HList and convert that to a case class.
(You don't have to know Akka much for this question, but the default approach accumulates materialized values as recursively nested 2-tuples, which isn't much fun, so Shapeless HLists seemed a more sensible approach -- and works pretty well. But I don't know how to properly re-use that approach. Here, I'll simplify the kinds of values Akka produces.)
For example, let's say we've got two materialized types, "A" and "B":
case class Result(b: B, a: A)
createA
.mapMaterialized((a: A) => a :: HNil)
.viaMat(flowCreatingB)((list1, b: B) => b :: list1)
.mapMaterialized(list2 => Generic[Result].from(list2))
// list1 = A :: HNil
// list2 = B :: A :: HNil
... and that produces Result just fine. But it requires that your case class be written backwards -- first value last, etc -- which is kind of dorky and hard to follow.
So the sensible thing is to reverse the list before converting to the case class, like this:
case class Result(a: A, b: B)
// ...
.mapMaterialized(list2 => Generic[Result].from(list2.reverse))
Now we can think about Result properties in the same order they were built. Yay.
But how to simplify and reuse this line of code?
The problem is that implicits don't work on multiple type parameters. For example:
def toCaseClass[A, R <: HList](implicit g: Generic.Aux[A, R], r: Reverse.Aux[L, R]): R => A =
l => g.from(l.reverse)
I'd need to specify both A (Result, above) and the HList being built:
.mapMaterialized(toCaseClass[Result, B :: A :: HNil])
Obviously, that invocation is going to be absurd with long lists (and Akka tends to build up really ugly-looking materialized types, not merely "A" and "B"). It'd be nicer to write something like:
.mapMaterialized(toCaseClass[Result])
I've tried to solve this using implicits, like this:
implicit class GraphOps[Mat <: HList](g: RunnableGraph[Mat]) {
implicit def createConverter[A, RL <: HList](implicit
r: Reverse.Aux[Mat, RL],
gen: Generic.Aux[A, RL]): Lazy[Mat => A] =
Lazy { l =>
val x: RL = l.reverse
val y: A = gen.from(x)
gen.from(l.reverse)
}
def toCaseClass[A](implicit convert: Lazy[Mat => A]): RunnableGraph[A] = {
g.mapMaterializedValue(convert.value)
}
But the compiler complains "No implicit view available".
The deeper problem is that I don't quite understand how to properly infer...
// R = Reversed order (e.g. B :: A :: NHNil)
// T = Type to create (e.g. Result(a, b))
// H = HList of T (e.g. A :: B :: HNil)
gen: Generic.Aux[T, H] // Generic[T] { type Repr = H }
rev: Reverse.Aux[R, H] // Reverse[R] { type Out = H }
This is sort of backwards from how Shapeless likes to infer things; I can't quite chain the abstract type members properly.
Profound thanks if you have insight here.
My bad: the example above, of course, requires Akka to compile. A simpler way of putting it is this (with thanks to Dymtro):
import shapeless._
import shapeless.ops.hlist.Reverse
case class Result(one: String, two: Int)
val results = 2 :: "one" :: HNil
println(Generic[Result].from(results.reverse))
// this works: prints "Result(one,2)"
case class Converter[A, B](value: A => B)
implicit class Ops[L <: HList](list: L) {
implicit def createConverter[A, RL <: HList](implicit
r: Reverse.Aux[L, RL],
gen: Generic.Aux[A, RL]): Converter[L, A] =
Converter(l => gen.from(l.reverse))
def toClass[A](implicit converter: Converter[L, A]): A =
converter.value(list)
}
println(results.toClass[Result])
// error: could not find implicit value for parameter converter:
// Converter[Int :: String :: shapeless.HNil,Result]
Dymtro's final example, below...
implicit class GraphOps[Mat <: HList, R <: HList](g: RunnableGraph[Mat]) {
def toCaseClass[A](implicit
r: Reverse.Aux[Mat, R],
gen: Generic.Aux[A, R]
): RunnableGraph[A] = g.mapMaterializedValue(l => gen.from(l.reverse))
}
... does seem to do what I'd been hoping for. Thank you very much Dmytro!
(Note: I had been somewhat misled in analyzing it earlier: it seems IntelliJ's presentation compiler incorrectly insists it won't compile (missing implicits). Moral: Don't trust IJ's presentation compiler.)
If I understood correctly you wish that in
def toCaseClass[A, R <: HList, L <: HList](implicit
g: Generic.Aux[A, R],
r: Reverse.Aux[L, R]
): L => A = l => g.from(l.reverse)
you could specify only A and then R, L be inferred.
You can do this with PartiallyApplied pattern
import shapeless.ops.hlist.Reverse
import shapeless.{Generic, HList, HNil}
def toCaseClass[A] = new {
def apply[R <: HList, L <: HList]()(implicit
g: Generic.Aux[A, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[L, R]
): L => A = l => g.from(l.reverse)
}
class A
class B
val a = new A
val b = new B
case class Result(a: A, b: B)
toCaseClass[Result]().apply(b :: a :: HNil)
(without implicit r0 type parameter L can't be inferred upon call of .apply() because L becomes known only upon call .apply().apply(...))
or better
def toCaseClass[A] = new {
def apply[R <: HList, L <: HList](l: L)(implicit
g: Generic.Aux[A, R],
r: Reverse.Aux[L, R]
): A = g.from(l.reverse)
}
toCaseClass[Result](b :: a :: HNil)
(here we don't need r0 because L becomes known already upon call .apply(...)).
If you want you can replace anonymous class with named one
def toCaseClass[A] = new PartiallyApplied[A]
class PartiallyApplied[A] {
def apply...
}
Alternatively you can define a type class (although this is a little more wordy)
trait ToCaseClass[A] {
type L
def toCaseClass(l: L): A
}
object ToCaseClass {
type Aux[A, L0] = ToCaseClass[A] { type L = L0 }
def instance[A, L0](f: L0 => A): Aux[A, L0] = new ToCaseClass[A] {
type L = L0
override def toCaseClass(l: L0): A = f(l)
}
implicit def mkToCaseClass[A, R <: HList, L <: HList](implicit
g: Generic.Aux[A, R],
r0: Reverse.Aux[R, L],
r: Reverse.Aux[L, R]
): Aux[A, L] = instance(l => g.from(l.reverse))
}
def toCaseClass[A](implicit tcc: ToCaseClass[A]): tcc.L => A = tcc.toCaseClass
toCaseClass[Result].apply(b :: a :: HNil)
Hiding several implicits with a type class: How to wrap a method having implicits with another method in Scala?
You could find an answer to your question in Type Astronaut:
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration (6.3 Case study: case class migrations)
Notice that IceCreamV1("Sundae", 1, true).migrateTo[IceCreamV2a] takes a single type parameter.
Your code with GraphOps doesn't work for several reasons.
Firstly, shapeless.Lazy is not just a wrapper. It's a macro-based type class to handle "diverging implicit expansion" (in Scala 2.13 there are by-name => implicits for that, although they are not equivalent to Lazy). You should use Lazy when you understand why you need it.
Secondly, you seem to define some implicit conversion (implicit view, Mat => A) but resolution of implicit conversions is trickier than resolution of other implicits (1 2 3 4 5).
Thirdly, you seem to assume that when you define
implicit def foo: Foo = ???
def useImplicitFoo(implicit foo1: Foo) = ???
foo1 is foo. But generally this is not true. foo is defined in current scope and foo1 will be resolved in the scope of useImplicitFoo call site:
Setting abstract type based on typeclass
When doing implicit resolution with type parameters, why does val placement matter? (difference between implicit x: X and implicitly[X])
So implicit createConverter is just not in scope when you call toCaseClass.
Fixed version of your code is
trait RunnableGraph[Mat]{
def mapMaterializedValue[A](a: Mat => A): RunnableGraph[A]
}
case class Wrapper[A, B](value: A => B)
implicit class GraphOps[Mat <: HList](g: RunnableGraph[Mat]) {
val ops = this
implicit def createConverter[A, RL <: HList](implicit
r: Reverse.Aux[Mat, RL],
gen: Generic.Aux[A, RL],
): Wrapper[Mat, A] =
Wrapper { l =>
val x: RL = l.reverse
val y: A = gen.from(x)
gen.from(l.reverse)
}
def toCaseClass[A](implicit convert: Wrapper[Mat, A]): RunnableGraph[A] = {
g.mapMaterializedValue(convert.value)
}
}
val g: RunnableGraph[B :: A :: HNil] = ???
val ops = g.ops
import ops._
g.toCaseClass[Result]
Try
import akka.stream.scaladsl.RunnableGraph
import shapeless.{::, Generic, HList, HNil}
import shapeless.ops.hlist.Reverse
implicit class GraphOps[Mat <: HList, R <: HList](g: RunnableGraph[Mat]) {
def toCaseClass[A](implicit
r: Reverse.Aux[Mat, R],
gen: Generic.Aux[A, R]
): RunnableGraph[A] = g.mapMaterializedValue(l => gen.from(l.reverse))
}
case class Result(one: String, two: Int)
val g: RunnableGraph[Int :: String :: HNil] = ???
g.toCaseClass[Result]
I'd like to make a method that take a vector or a seq of of some type, let's say Int, and calls filter on it.
For example:
implicit class SharedOps[F](xs: F)(implicit ev: OneOf[F, Seq[Int] ::: Vector[Int] ::: HSNil]) {
def filter(x: Int):F = xs.filter({a:Int => a == x})
}
OneOf basically checks that F is either a Seq[Int] or a Vector[Int]
The tricky part is that I want the filter to return the same type as the input (e.g. Seq[Int] or Vector[Int]) but compiler is complaining
error: type mismatch;
found : scala.this.Function1[scala.this.Int,scala.this.Boolean]
required: scala.this.Int
def filter(x: Int):F = xs.filter({a:Int => a == x})
Somehow compiler forgot I started with a collection of sorts and thinks xs is a single thing.
So I changed the design:
implicit class SharedOps2[A,F[A]<:TraversableLike[A,A]](xs: F[A])(implicit ev: OneOf[F[A], Seq[Int] ::: Vector[Int] ::: HSNil]) {
def filter(x: A): F[A] = xs.filter({ a: Int => a == x })
}
Now compiler is complaining that: Expression of type A doesn't conform to expected type F[A]
Not sure how to take it from here. I'd like to avoid shapeless coproducts at this point.
For completeness here's the OneOf code:
sealed trait HSubset // HList
#implicitNotFound("No member of type class HSubset in scope for ${H}")
trait :::[+H, +T <: HSubset] extends HSubset // HCons ::
sealed trait HSNil extends HSubset // HNil
#implicitNotFound("No member of type class BelongsTo in scope for ${T}")
trait BelongsTo[T, U <: HSubset]
object BelongsTo {
implicit def baseCase[H, U <: HSubset]: BelongsTo[H, H ::: U] = null
implicit def recursiveCase[H, U <: HSubset, T](implicit ev: T BelongsTo U): T BelongsTo (H ::: U) = null
}
#implicitNotFound("No member of type class SubsetOf in scope for ${U} ${T}")
trait SubsetOf[U <: HSubset, T <: HSubset]
object SubsetOf {
implicit def baseCase[U1, U <: HSubset](implicit s: U1 BelongsTo U): SubsetOf[U1 ::: HSNil, U] = null
implicit def recursiveCase[U <: HSubset, T1, T <: HSubset](implicit ev1: T1 BelongsTo U, ev2: T SubsetOf U): (T1 ::: T) SubsetOf U = null
}
trait OneOf[T, U <: HSubset]
object OneOf {
implicit def baseCase[U <: HSubset, T](implicit s: T BelongsTo U): T OneOf U = null
implicit def recursiveCase[T, Ev <: HSubset, Target <: HSubset](implicit ev1: T OneOf Ev, ev2: Ev SubsetOf Target): T OneOf Target = null
}
This is the proposed typeclas.
My advice would be to use specific types like List & Vector instead of Seq.
trait Filter[F[_]] {
def filter[A](fa: F[A])(p: A => Boolean): F[A]
}
object Filter {
implicit final val VectorFilter: Filter[Vector] =
new Filter[Vector] {
override final def filter[A](vector: Vector[A])(p: A => Boolean): Vector[A] =
vector.filter(p)
}
implicit final val SeqFilter: Filter[Seq] =
new Filter[Seq] {
override final def filter[A](seq: Seq[A])(p: A => Boolean): Seq[A] =
seq.filter(p)
}
}
object syntax {
object filter {
implicit class FilterOps[F[_], A](private val fa: F[A]) extends AnyVal {
#inline
final def filter(p: A => Boolean)(implicit ev: Filter[F]): F[A] =
ev.filter(fa)(p)
}
}
}
import syntax.filter._
def foo[F[_] : Filter](xs: F[Int]): F[Int] =
xs.filter(i => (i % 2) == 0)
Which you can use like:
foo(Vector(1, 2, 3))
// res: Vector[Int] = Vector(2)
foo(List(1, 2, 3))
// could not find implicit value for evidence parameter of type ammonite.$sess.cmd0.Filter[List]
foo(Seq(1, 2, 3))
// res: Seq[Int] = List(2)
BTW, it is worth mentioning that such typeclass already exists in cats
TraversableLike.filter returns Repr, which is type passed as second type parameter to TraversableLike. You need F[A] there instead of A. This compiles for me:
implicit class SharedOps2[A,F[A]<:TraversableLike[A,F[A]]](xs: F[A])(implicit ev: OneOf[F[A], Seq[A] ::: Vector[A] ::: HSNil]) {
def filter(x: A): F[A] = xs.filter({ a: A => a == x })
}
Note also type of a changed to A, because this is the type inside F[A] collection.
fiddle
I often find myself having to perform what is pretty much the same operation on both a value and also a functor of that value. I usually achieve this with two implicit classes, like this:
implicit class Apimped(a: A) {
def doSomething: B = ???
}
implicit class FApimped[F[_]: Functor](fa: F[A]) {
def doSomething: F[B] = Functor[F].map(fa)(a => a.doSomething)
}
So then I can do this, for example:
a.doSomething //B
Option(a).doSomething //Option[B]
However, it seems a bit unwieldy having to write two implicit classes (often for each value type) to do this. My question is, is there anyway to achieve the above with only a single implicit class? That is, the map operation would be implicit in cases when you call doSomething on a functor of the value. Thanks.
I don't know whether it's in Scalaz/Cats (maybe, cannot guarantee that it's not there), but in principle, it does work. Here is a little demo without any dependencies that demonstrates the principle.
Assume you have these typeclasses from either Scalaz or Cats:
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A, B](a: F[A])(f: A => B): F[B]
}
type Id[X] = X
implicit object IdFunctor extends Functor[Id] {
def map[A, B](a: A)(f: A => B): B = f(a)
}
implicit object OptionFunctor extends Functor[Option] {
def map[A, B](a: Option[A])(f: A => B) = a map f
}
you can then either write or find in the library a typeclass that works like the following contraption:
trait EverythingIsAlwaysAFunctor[A, B, F[_]] {
def apply(a: A): F[B]
def functor: Functor[F]
}
object EverythingIsAlwaysAFunctor {
implicit def functorIsFunctor[A, F[_]](implicit f: Functor[F])
: EverythingIsAlwaysAFunctor[F[A], A, F] = {
new EverythingIsAlwaysAFunctor[F[A], A, F] {
def apply(fa: F[A]): F[A] = fa
def functor: Functor[F] = f
}
}
implicit def idIsAlsoAFunctor[A]
: EverythingIsAlwaysAFunctor[A, A, Id] = {
new EverythingIsAlwaysAFunctor[A, A, Id] {
def apply(a: A): Id[A] = a
def functor: Functor[Id] = implicitly[Functor[Id]]
}
}
}
This thing does the following:
If the value A is already of shape F[B] for some functor F, then uses this functor
In all other cases, it pretends that A is actually Id[A]
Now you can write your DoSomething-pimp-my-library-syntax thing with a single doSomething method:
implicit class DoSomething[A, F[_]](a: A)(
implicit eiaaf: EverythingIsAlwaysAFunctor[A, Int, F]
) {
def doSomething: F[String] = eiaaf.functor.map(eiaaf(a))("*" * _)
}
and then it just works in all cases:
val x = Option(42).doSomething
val y = 42.doSomething
println(x)
println(y)
prints:
Some(******************************************)
******************************************
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?
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.
I would like to exploit Scala's type system to constrain operations in a system where there are versioned references to some values. This is all happening in some transactional context Ctx which has a version type V attached to it. Now there is a Factory to create reference variables. They get created with a creation version attached them (type parameter V1), corresponding to the version of the context in which the factory was called.
Now imagine that some code tries to access that reference in a later version, that is using a different Ctx. What I want to achieve is that it is prohibited to call access on that Ref in any version (Ctx's V type field) that doesn't match the creation version, but that you are allowed to resolve the reference by some substitution mechanism that returns a new view of the Ref which can be accessed in the current version. (it's ok if substitute is called with an invalid context, e.g. one that is older than the Ref's V1 -- in that case a runtime exception could be thrown)
Here is my attempt:
trait Version
trait Ctx {
type V <: Version
}
object Ref {
implicit def access[C <: Ctx, R, T](r: R)(implicit c: C, view: R => Ref[C#V, T]): T =
view(r).access(c)
implicit def substitute[C <: Ctx, T](r: Ref[_ <: Version, T])
(implicit c: C): Ref[C#V, T] = r.substitute(c)
}
trait Ref[V1 <: Version, T] {
def access(implicit c: { type V = V1 }): T // ???
def substitute[C <: Ctx](implicit c: C): Ref[C#V, T]
}
trait Factory {
def makeRef[C <: Ctx, T](init: T)(implicit c: C): Ref[C#V, T]
}
And the problem is to define class method access in a way that the whole thing compiles, i.e. the compound object's access should compile, but at the same time that I cannot call this class method access with any Ctx, only with one whose version matches the reference's version.
Preferably without structural typing or anything that imposes performance issues.
FYI, and to close the question, here is another idea that I like because the client code is fairly clutter free:
trait System[A <: Access[_]] {
def in[T](v: Version)(fun: A => T): T
}
trait Access[Repr] {
def version: Version
def meld[R[_]](v: Version)(fun: Repr => Ref[_, R]): R[this.type]
}
trait Version
trait Ref[A, Repr[_]] {
def sub[B](b: B): Repr[B]
}
object MyRef {
def apply[A <: MyAccess](implicit a: A): MyRef[A] = new Impl[A](a)
private class Impl[A](a: A) extends MyRef[A] {
def sub[B](b: B) = new Impl[B](b)
def schnuppi(implicit ev: A <:< MyAccess) = a.gagaism
}
}
trait MyRef[A] extends Ref[A, MyRef] {
// this is how we get MyAccess specific functionality
// in here without getting trapped in more type parameters
// in all the traits
def schnuppi(implicit ev: A <:< MyAccess): Int
}
trait MyAccess extends Access[MyAccess] {
var head: MyRef[this.type]
var tail: MyRef[this.type]
def gagaism: Int
}
def test(sys: System[MyAccess], v0: Version, v1: Version): Unit = {
val v2 = sys.in(v0) { a => a.tail = a.meld(v1)(_.head); a.version }
val a3 = sys.in(v2) { a => a }
val (v4, a4) = sys.in(v1) { a =>
a.head = a.head
println(a.head.schnuppi) // yes!
(a.version, a)
}
// a3.head = a4.head // forbidden
}
The following seems to work:
trait Version
trait Ctx[+V1 <: Version] {
type V = V1
}
type AnyCtx = Ctx[_ <: Version]
type AnyRf[T] = Ref[_ <: Version, T]
object Ref {
implicit def access[C <: AnyCtx, R, T](r: R)(
implicit c: C, view: R => Ref[C#V, T]): T = view(r).access(c)
implicit def substitute[C <: AnyCtx, T](r: AnyRf[T])(implicit c: C): Ref[C#V, T] =
r.substitute( c )
}
trait Ref[V1 <: Version, T] {
def access(implicit c: Ctx[V1]): T
def substitute[C <: AnyCtx](implicit c: C): Ref[C#V, T]
}
trait Factory {
def makeVar[C <: AnyCtx, T](init: T)(implicit c: C): Ref[C#V, T]
}
// def shouldCompile1(r: AnyRf[String])(implicit c: AnyCtx): String = r
def shouldCompile2(r: AnyRf[String])(implicit c: AnyCtx): String = {
val r1 = Ref.substitute(r)
r1.access(c)
}
// def shouldFail(r: AnyRf[String])(implicit c: AnyCtx): String = r.access(c)
So the follow-up questions are
why I need a redundancy of the type
parameter for Ctx to achieve this. I hate that these type
parameters accumulate like rabbits in my code.
why shouldCompile1 doesn't compile
—can i get the implicits to work as planned?
EDIT:
This is wrong, too. The variance annotation is wrong. Because now the following compiles although it shouldn't:
def versionStep(c: AnyCtx): AnyCtx = c // no importa
def shouldFail3[C <: AnyCtx](f: Factory, c: C): String = {
val r = f.makeVar("Hallo")(c)
val c2 = versionStep(c)
r.access(c2)
}