Shapeless3 and annotations - scala

This is a followup on Shapeless and annotations. The original question was asked in the context of Scala 2 and Shapeless2.
Some features from Shapeless2 were migrated to Shapeless3, such as annotations. The question is, how to migrate the solution to Shapeless3? especially the code around Poly2?
Here is a copy/paste of the solution to be migrated to Shapeless3:
import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation
object App {
case class MyAnnotation(func: String) extends StaticAnnotation
object Collector extends Poly2 {
// implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
// case ((e, None), acc) => e :: acc
// case ((e, Some(MyAnnotation(func))), acc) => {
// println(func)
// e :: acc
// }
// }
implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
case ((e, Some(MyAnnotation(func))), acc) =>
println(func)
e :: acc
}
implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
case ((e, None), acc) => e :: acc
}
}
trait Modifier[T] {
def modify(t: T): T
}
implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
println(modified)
val typed = gen.from(modified)
typed
}
}
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly[Modifier[Test]]
def main(args: Array[String]): Unit = {
val test1 = modifier.modify(test) // prints "sha1"
println(test1) // Test(A,B)
}
}

In Scala 3 Tuple is for HList, Mirror is for Generic/LabelledGeneric. There are polymorphic functions but they are parametric-polymorphism polymorphic, not ad-hoc-polymorphism polymorphic like Poly.
Shapeless 3 has Annotations, Typeable and deriving tools (wrapping Mirror).
It's not hard to implement missing pieces (Generic, Coproduct, Poly, type classes etc.)
Scala 3 collection partitioning with subtypes
import shapeless3.deriving.Annotations
import scala.deriving.Mirror
import scala.util.NotGiven
import scala.annotation.StaticAnnotation
//================= GENERIC ====================
trait Generic[T] {
type Repr
def to(t: T): Repr
def from(r: Repr): T
}
object Generic {
type Aux[T, Repr0] = Generic[T] {type Repr = Repr0}
def instance[T, Repr0](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] =
new Generic[T] {
override type Repr = Repr0
override def to(t: T): Repr0 = f(t)
override def from(r: Repr0): T = g(r)
}
object ops {
extension[A] (a: A) {
def toRepr(using g: Generic[A]): g.Repr = g.to(a)
}
extension[Repr] (a: Repr) {
def to[A](using g: Generic.Aux[A, Repr]): A = g.from(a)
}
}
given [T <: Product](using
// ev: NotGiven[T <:< Tuple],
// ev1: NotGiven[T <:< Coproduct],
m: Mirror.ProductOf[T],
m1: Mirror.ProductOf[m.MirroredElemTypes]
): Aux[T, m.MirroredElemTypes] = instance(
m1.fromProduct(_),
m.fromProduct(_)
)
// given[T, C <: Coproduct](using
// // ev: NotGiven[T <:< Tuple],
// // ev1: NotGiven[T <:< Coproduct],
// m: Mirror.SumOf[T],
// ev2: Coproduct.ToCoproduct[m.MirroredElemTypes] =:= C
// ): Generic.Aux[T, C/*Coproduct.ToCoproduct[m.MirroredElemTypes]*/] = {
// instance(
// t => Coproduct.unsafeToCoproduct(m.ordinal(t), t).asInstanceOf[C],
// Coproduct.unsafeFromCoproduct(_).asInstanceOf[T]
// )
// }
}
//================= COPRODUCT ====================
//sealed trait Coproduct extends Product with Serializable
//sealed trait +:[+H, +T <: Coproduct] extends Coproduct
//final case class Inl[+H, +T <: Coproduct](head: H) extends (H +: T)
//final case class Inr[+H, +T <: Coproduct](tail: T) extends (H +: T)
//sealed trait CNil extends Coproduct
//
//object Coproduct {
// def unsafeToCoproduct(length: Int, value: Any): Coproduct =
// (0 until length).foldLeft[Coproduct](Inl(value))((c, _) => Inr(c))
//
// #scala.annotation.tailrec
// def unsafeFromCoproduct(c: Coproduct): Any = c match {
// case Inl(h) => h
// case Inr(c) => unsafeFromCoproduct(c)
// case _: CNil => sys.error("impossible")
// }
//
// type ToCoproduct[T <: Tuple] <: Coproduct = T match {
// case EmptyTuple => CNil
// case h *: t => h +: ToCoproduct[t]
// }
//
// type ToTuple[C <: Coproduct] <: Tuple = C match {
// case CNil => EmptyTuple
// case h +: t => h *: ToTuple[t]
// }
//}
//================= POLY ====================
trait Cases {
type Case1[Fn, A] = poly.Case[Fn, A *: EmptyTuple]
object Case1 {
type Aux[Fn, A, Result] = poly.Case.Aux[Fn, A *: EmptyTuple, Result]
def apply[Fn, A, Result](fn: A => Result): Case1.Aux[Fn, A, Result] =
poly.Case { case a *: EmptyTuple => fn(a) }
}
type Case2[Fn, A, B] = poly.Case[Fn, A *: B *: EmptyTuple]
object Case2 {
type Aux[Fn, A, B, Result] = poly.Case.Aux[Fn, A *: B *: EmptyTuple, Result]
def apply[Fn, A, B, Result](fn: (A, B) => Result): Case2.Aux[Fn, A, B, Result] =
poly.Case { case a *: b *: EmptyTuple => fn(a, b) }
}
}
trait CaseInst {
given inst1[Fn <: Poly, A, Res]: Conversion[poly.Case.Aux[Fn, A *: EmptyTuple, Res], A => Res] =
cse => a => cse.value(a *: EmptyTuple)
given inst2[Fn <: Poly, A, B, Res]: Conversion[poly.Case.Aux[Fn, A *: B *: EmptyTuple, Res], (A, B) => Res] =
cse => (a, b) => cse.value(a *: b *: EmptyTuple)
}
object poly extends Cases {
trait Case[P, L <: Tuple] {
type Result
val value: L => Result
def apply(t: L): Result = value(t)
def apply()(using ev: EmptyTuple =:= L): Result = value(EmptyTuple)
def apply[T](t: T)(using ev: (T *: EmptyTuple) =:= L): Result = value(t *: EmptyTuple)
def apply[T, U](t: T, u: U)(using ev: (T *: U *: EmptyTuple) =:= L): Result = value(t *: u *: EmptyTuple)
}
object Case extends CaseInst {
type Aux[P, L <: Tuple, Result0] = Case[P, L] {type Result = Result0}
def apply[P, L <: Tuple, R](v: L => R): Aux[P, L, R] = new Case[P, L] {
type Result = R
val value = v
}
}
}
trait PolyApply {
type λ <: Singleton
def apply[A](a: A)(using cse: poly.Case[λ, A *: EmptyTuple]): cse.Result = cse(a *: EmptyTuple)
def apply[A, B](a: A, b: B)(using cse: poly.Case[λ, A *: B *: EmptyTuple]): cse.Result = cse(a *: b *: EmptyTuple)
}
trait Poly extends PolyApply {
type λ = this.type
type ProductCase[L <: Tuple] = poly.Case[this.type, L]
object ProductCase extends Serializable {
type Aux[L <: Tuple, Result0] = ProductCase[L] {type Result = Result0}
def apply[L <: Tuple, R](v: L => R) = new ProductCase[L] {
type Result = R
val value = v
}
}
def apply[R](using c: ProductCase.Aux[EmptyTuple, R]): R = c()
}
trait PolyInst {
implicit def inst0(p: Poly)(implicit cse: p.ProductCase[EmptyTuple]): cse.Result = cse()
implicit def inst1[A](fn: Poly)(implicit cse: fn.ProductCase[A *: EmptyTuple]): A => cse.Result =
a => cse(a *: EmptyTuple)
implicit def inst2[A, B](fn: Poly)(implicit cse: fn.ProductCase[A *: B *: EmptyTuple]): (A, B) => cse.Result =
(a, b) => cse(a *: b *: EmptyTuple)
}
object Poly extends PolyInst
trait Poly0 extends Poly {
type Case0[T] = ProductCase.Aux[EmptyTuple, T]
def at[T](t: T) = new ProductCase[EmptyTuple] {
type Result = T
val value = _ => t
}
}
trait Poly1 extends Poly { self =>
type Case[A] = poly.Case[self.type, A *: EmptyTuple]
object Case {
type Aux[A, Result0] = poly.Case.Aux[self.type, A *: EmptyTuple, Result0]
}
class CaseBuilder1[A] {
def apply[Res](fn: A => Res): Case.Aux[A, Res] = poly.Case { case a *: EmptyTuple => fn(a) }
}
def at[A]: CaseBuilder1[A] = new CaseBuilder1[A]
}
trait Poly2 extends Poly { self =>
type Case[A, B] = poly.Case[self.type, A *: B *: EmptyTuple]
object Case {
type Aux[A, B, Result0] = poly.Case.Aux[self.type, A *: B *: EmptyTuple, Result0]
}
class CaseBuilder2[A, B] {
def apply[Res](fn: (A, B) => Res): Case.Aux[A, B, Res] = poly.Case { case a *: b *: EmptyTuple => fn(a, b) }
}
def at[A, B]: CaseBuilder2[A, B] = new CaseBuilder2[A, B]
}
//================= TYPE CLASSES ====================
trait DepFn0 {
type Out
def apply(): Out
}
trait DepFn1[T] {
type Out
def apply(t: T): Out
}
trait DepFn2[T, U] {
type Out
def apply(t: T, u: U): Out
}
trait ConstMapper[C, L <: Tuple] extends DepFn2[C, L] {
type Out <: Tuple
}
object ConstMapper {
def apply[C, L <: Tuple](using mapper: ConstMapper[C, L]): Aux[C, L, mapper.Out] = mapper
type Aux[C, L <: Tuple, Out0 <: Tuple] = ConstMapper[C, L] {type Out = Out0}
given hnilConstMapper[C]: Aux[C, EmptyTuple, EmptyTuple] =
new ConstMapper[C, EmptyTuple] {
type Out = EmptyTuple
def apply(c: C, l: EmptyTuple): Out = l
}
given hlistConstMapper[H, T <: Tuple, C, OutT <: Tuple]
(using mct: ConstMapper.Aux[C, T, OutT]): Aux[C, H *: T, C *: OutT] =
new ConstMapper[C, H *: T] {
type Out = C *: OutT
def apply(c: C, l: H *: T): Out = c *: mct(c, l.tail)
}
}
trait ZipOne[H <: Tuple, T <: Tuple] extends DepFn2[H, T] {
type Out <: Tuple
}
object ZipOne extends LowPriorityZipOne {
given zipOne0: Aux[EmptyTuple, EmptyTuple, EmptyTuple] =
new ZipOne[EmptyTuple, EmptyTuple] {
type Out = EmptyTuple
def apply(h: EmptyTuple, t: EmptyTuple): Out = EmptyTuple
}
given zipOne3[H, T <: Tuple]: Aux[H *: EmptyTuple, T *: EmptyTuple, (H *: T) *: EmptyTuple] =
new ZipOne[H *: EmptyTuple, T *: EmptyTuple] {
type Out = (H *: T) *: EmptyTuple
def apply(h: H *: EmptyTuple, t: T *: EmptyTuple): Out = (h.head *: t.head) *: EmptyTuple
}
}
trait LowPriorityZipOne {
def apply[H <: Tuple, T <: Tuple](using zip: ZipOne[H, T]): Aux[H, T, zip.Out] = zip
type Aux[H <: Tuple, T <: Tuple, Out0 <: Tuple] = ZipOne[H, T] {type Out = Out0}
given zipOne1[H <: Tuple]: Aux[H, EmptyTuple, EmptyTuple] =
new ZipOne[H, EmptyTuple] {
type Out = EmptyTuple
def apply(h: H, t: EmptyTuple): Out = EmptyTuple
}
given zipOne2[T <: Tuple]: Aux[EmptyTuple, T, EmptyTuple] =
new ZipOne[EmptyTuple, T] {
type Out = EmptyTuple
def apply(h: EmptyTuple, t: T): Out = EmptyTuple
}
given zipOne4[HH, HT <: Tuple, TH <: Tuple, TT <: Tuple, ZotOut <: Tuple]
(using zot: ZipOne.Aux[HT, TT, ZotOut], ev: Tuple.Head[TH *: TT] =:= TH /*???*/): Aux[HH *: HT, TH *: TT, (HH *: TH) *: ZotOut] =
new ZipOne[HH *: HT, TH *: TT] {
type Out = (HH *: TH) *: ZotOut
def apply(h: HH *: HT, t: TH *: TT): Out = (h.head *: ev(t.head)) *: zot(h.tail, t.tail)
}
}
trait Transposer[L <: Tuple] extends DepFn1[L] {
type Out <: Tuple
}
object Transposer {
def apply[L <: Tuple](using transposer: Transposer[L]): Aux[L, transposer.Out] = transposer
type Aux[L <: Tuple, Out0 <: Tuple] = Transposer[L] {type Out = Out0}
given hnilTransposer: Aux[EmptyTuple, EmptyTuple] =
new Transposer[EmptyTuple] {
type Out = EmptyTuple
def apply(l: EmptyTuple): Out = l
}
given hlistTransposer1[H <: Tuple, MC <: Tuple, Out0 <: Tuple]
(using mc: ConstMapper.Aux[EmptyTuple, H, MC], zo: ZipOne.Aux[H, MC, Out0]): Aux[H *: EmptyTuple, Out0] =
new Transposer[H *: EmptyTuple] {
type Out = Out0
def apply(l: H *: EmptyTuple): Out = zo(l.head, mc(EmptyTuple, l.head))
}
given hlistTransposer2[H <: Tuple, TH <: Tuple, TT <: Tuple, OutT <: Tuple, Out0 <: Tuple]
(using tt: Aux[TH *: TT, OutT], zo: ZipOne.Aux[H, OutT, Out0]): Aux[H *: TH *: TT, Out0] =
new Transposer[H *: TH *: TT] {
type Out = Out0
def apply(l: H *: TH *: TT): Out = zo(l.head, tt(l.tail))
}
}
trait Zip[L <: Tuple] extends DepFn1[L] {
type Out <: Tuple
}
object Zip {
def apply[L <: Tuple](using zip: Zip[L]): Aux[L, zip.Out] = zip
type Aux[L <: Tuple, Out0 <: Tuple] = Zip[L] {type Out = Out0}
given zipper[L <: Tuple, OutT <: Tuple]
(using
transposer: Transposer.Aux[L, OutT]
): Aux[L, OutT] =
new Zip[L] {
type Out = OutT
def apply(l: L): Out = l.transpose
}
}
extension [L <: Tuple](l: L) {
def transpose(using transpose: Transposer[L]): transpose.Out = transpose(l)
def foldRight[R](z : R)(op : Poly)(using folder: RightFolder[L, R, op.type]): folder.Out = folder(l, z)
}
trait RightFolder[L <: Tuple, In, HF] extends DepFn2[L, In]
object RightFolder {
def apply[L <: Tuple, In, F](using folder: RightFolder[L, In, F]): Aux[L, In, F, folder.Out] = folder
type Aux[L <: Tuple, In, HF, Out0] = RightFolder[L, In, HF] {type Out = Out0}
given hnilRightFolder[In, HF]: Aux[EmptyTuple, In, HF, In] =
new RightFolder[EmptyTuple, In, HF] {
type Out = In
def apply(l: EmptyTuple, in: In): Out = in
}
given hlistRightFolder[H, T <: Tuple, In, HF, OutT]
(using ft: RightFolder.Aux[T, In, HF, OutT], f: poly.Case2[HF, H, OutT]): Aux[H *: T, In, HF, f.Result] =
new RightFolder[H *: T, In, HF] {
type Out = f.Result
def apply(l: H *: T, in: In): Out = f(l.head, ft(l.tail, in))
}
}
//================= YOUR SETTING ====================
case class MyAnnotation(func: String) extends StaticAnnotation
object Collector extends Poly2 {
given [ACC <: Tuple, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E *: ACC] = at {
case ((e, Some(MyAnnotation(func))), acc) =>
println(func)
e *: acc
}
given [ACC <: Tuple, E]: Case.Aux[(E, None.type), ACC, E *: ACC] = at {
case ((e, None), acc) => e *: acc
}
}
trait Modifier[T] {
def modify(t: T): T
}
given hListModifier[HL <: Tuple]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList
given genericModifier[T, HL <: Tuple, AL <: Tuple, ZL <: Tuple](using
gen: Generic.Aux[T, HL],
ser: /*Lazy[*/Modifier[HL]/*]*/,
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL *: AL *: EmptyTuple, ZL],
rightFolder: RightFolder.Aux[ZL, EmptyTuple, Collector.type, HL]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic *: annotations *: EmptyTuple)
println(zipped)
val modified = zipped.foldRight(EmptyTuple)(Collector)
println(modified)
val typed = gen.from(modified)
typed
}
}
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = summon[Modifier[Test]]
#main def run = {
val test1 = modifier.modify(test) // prints "sha1"
println(test1) // Test(A,B)
}
Maybe some of type classes can be replaced with match types or compile-time calculations.
It can be tricky to implement Lazy. It's not clear whether it's needed. There are by-name implicits but they are not equivalent to Lazy (1 2). In principle, Lazy can be implemented in Scala 3 since compiler internals for implicits in Scala 3 are similar to those in Scala 2 (1 2 3).
Shapeless and annotations
I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T.
Here is simpler solution for Scala 3
import shapeless3.deriving.Annotations
import scala.annotation.StaticAnnotation
import scala.deriving.Mirror
case class MyAnnotation(func: String) extends StaticAnnotation
case class Test(a: String, #MyAnnotation("sha1") b: String)
def fold[Tup <: Tuple, Z, F[_, _]](tup: Tup, z: Z, f: [A, B] => (A, B) => F[A, B]): Tuple.Fold[Tup, Z, F] = tup match {
case _: EmptyTuple => z
case tup: (h *: t) => f[h, Tuple.Fold[t, Z, F]](tup.head, fold[t, Z, F](tup.tail, z, f))
}
type Collector[A, B <: Tuple] = A match {
case (a, Some[MyAnnotation]) => a *: B
case (a, None.type) => a *: B
}
transparent inline def foo[T <: Product](t: T)(using
m: Mirror.ProductOf[T],
m1: Mirror.ProductOf[m.MirroredElemTypes] {type MirroredElemTypes = m.MirroredElemTypes},
ann: Annotations[MyAnnotation, T]
): Any = {
val tuple: m.MirroredElemTypes = m1.fromProduct(t)
println(s"tuple=$tuple")
val annotations: ann.Out = ann()
println(s"annotations=$annotations")
type Zipped = Tuple.Zip[m.MirroredElemTypes, ann.Out]
val zipped: Zipped = tuple.zip(annotations)
println(s"zipped=$zipped")
def collector[A, B <: Tuple](x: A, y: B): Collector[A, B] = (x match {
case (a, Some(annot)) =>
println(s"annot=$annot")
a *: y
case (a, None) =>
a *: y
}).asInstanceOf[Collector[A, B]]
type Folded = Tuple.Fold[Zipped, EmptyTuple, [a, b] =>> Collector[a, b & Tuple]]
val folded: Folded = fold[Zipped, EmptyTuple, [a, b] =>> Collector[a, b & Tuple]](
zipped,
EmptyTuple,
[a, b] => (x: a, y: b) => collector(x, y.asInstanceOf[b & Tuple])
)
m.fromProduct(folded.asInstanceOf[Folded & Product])
}
val res: Test = foo(Test("aa", "bb")) // Test(aa,bb)
// tuple=(aa,bb)
// annotations=(None,Some(MyAnnotation(sha1)))
// zipped=((aa,None),(bb,Some(MyAnnotation(sha1))))
// annot=MyAnnotation(sha1)

Related

Express function of arbitrary arity in vanilla Scala 3

Trying to grasp Scala 3 type system.
Question:
Is it possible to write a single universal def curry(f: ???) = ... function that accepts f of any arity and returns a curried fn? No compiler plugins, no external fancy libs, just a function of N-arity expressed in plain Scala 3?
I look at this Haskell example https://riptutorial.com/haskell/example/18470/an-n-arity-curry that does smth similar to what is needed.
(purpose of this question is not to use any external lib - purpose is to learn functional programming concepts with Scala 3 as a tool. Got a feeling that this might be related to dealing with args as tuples or some conversion of fn to tupled fn ? i feel there is some symmetry between fn args and a concept of tuple ?)
On contrary to Haskell, in Scala there are different functional types (X1, ..., Xn) => Y (aka FunctionN[X1, ..., Xn, Y]) and ((X1, ..., Xn)) => Y (aka Function1[TupleN[X1, ..., Xn], Y]). For the latter (in order to transform them into X1 => ... => Xn => Y aka Function1[X1, Function1[..., Function1[Xn, Y]...]]) you can use match types, inline methods, and compile-time operations
import scala.compiletime.{erasedValue, summonFrom}
type Reverse[T <: Tuple] = ReverseLoop[T, EmptyTuple]
inline def reverse[T <: Tuple](t: T): Reverse[T] = reverseLoop(t, EmptyTuple)
type ReverseLoop[T <: Tuple, S <: Tuple] <: Tuple = T match
case EmptyTuple => S
case h *: t => ReverseLoop[t, h *: S]
inline def reverseLoop[T <: Tuple, S <: Tuple](x: T, acc: S): ReverseLoop[T, S] =
inline x match
case _: EmptyTuple => acc
case x: (_ *: _) => x match
case h *: t => reverseLoop(t, h *: acc)
type Curry[T <: Tuple, Y] = CurryLoop[T, T, EmptyTuple, Y]
inline def curry[T <: Tuple, Y](f: T => Y): Curry[T, Y] =
curryLoop[T, T, EmptyTuple, Y](f, EmptyTuple)
type CurryLoop[T1 <: Tuple, T <: Tuple, S <: Tuple, Y] = T1 match
case EmptyTuple => Y
case h *: t => h => CurryLoop[t, T, h *: S, Y]
inline def curryLoop[T1 <: Tuple, T <: Tuple, S <: Tuple, Y](
f: T => Y,
acc: S
): CurryLoop[T1, T, S, Y] = inline erasedValue[T1] match
case _: EmptyTuple => summonFrom {
case _: (Reverse[S] =:= T) => f(reverse(acc))
}
case _: (h *: t) => (h: h) => curryLoop[t, T, h *: S, Y](f, h *: acc)
Testing:
// compiles
summon[Curry[(Int, String, Boolean), String] =:= (Int => String => Boolean => String)]
val f: ((Int, String, Boolean)) => String = t => s"${t._1}, ${t._2}, ${t._3}"
val g = curry(f)
g: (Int => String => Boolean => String) // checking the type
g(1)("a")(true) // 1, a, true
Scala 3: typed tuple zipping
Alternatively, you can still use good old type classes
trait Reverse[T <: Tuple]:
type Out <: Tuple
def apply(t: T): Out
object Reverse:
type Aux[T <: Tuple, Out0 <: Tuple] = Reverse[T] {type Out = Out0}
def instance[T <: Tuple, Out0 <: Tuple](f: T => Out0): Aux[T, Out0] =
new Reverse[T]:
override type Out = Out0
override def apply(t: T): Out = f(t)
given [T <: Tuple](using
reverseLoop: ReverseLoop[T, EmptyTuple]
): Aux[T, reverseLoop.Out] = instance(t => reverseLoop(t, EmptyTuple))
trait ReverseLoop[T <: Tuple, S <: Tuple]:
type Out <: Tuple
def apply(t: T, acc: S): Out
object ReverseLoop:
type Aux[T <: Tuple, S <: Tuple, Out0 <: Tuple] =
ReverseLoop[T, S] {type Out = Out0}
def instance[T <: Tuple, S <: Tuple, Out0 <: Tuple](
f: (T, S) => Out0
): Aux[T, S, Out0] = new ReverseLoop[T, S]:
override type Out = Out0
override def apply(t: T, acc: S): Out = f(t, acc)
given [S <: Tuple]: Aux[EmptyTuple, S, S] = instance((_, acc) => acc)
given [H, T <: Tuple, S <: Tuple](using
reverseLoop: ReverseLoop[T, H *: S]
): Aux[H *: T, S, reverseLoop.Out] =
instance((l, acc) => reverseLoop(l.tail, l.head *: acc))
trait Curry[T <: Tuple, Y]:
type Out
def apply(f: T => Y): Out
object Curry:
type Aux[T <: Tuple, Y, Out0] = Curry[T, Y] {type Out = Out0}
def instance[T <: Tuple, Y, Out0](g: (T => Y) => Out0): Aux[T, Y, Out0] =
new Curry[T, Y]:
override type Out = Out0
override def apply(f: T => Y): Out = g(f)
given [T <: Tuple, Y](using
curryLoop: CurryLoop[T, T, EmptyTuple, Y]
): Aux[T, Y, curryLoop.Out] = instance(f => curryLoop(f, EmptyTuple))
trait CurryLoop[T1 <: Tuple, T <: Tuple, S <: Tuple, Y]:
type Out
def apply(f: T => Y, acc: S): Out
object CurryLoop:
type Aux[T1 <: Tuple, T <: Tuple, S <: Tuple, Y, Out0] =
CurryLoop[T1, T, S, Y] {type Out = Out0}
def instance[T1 <: Tuple, T <: Tuple, S <: Tuple, Y, Out0](
g: (T => Y, S) => Out0
): Aux[T1, T, S, Y, Out0] = new CurryLoop[T1, T, S, Y]:
override type Out = Out0
override def apply(f: T => Y, acc: S): Out = g(f, acc)
given [S <: Tuple, Y](using
reverse: Reverse[S]
): Aux[EmptyTuple, reverse.Out, S, Y, Y] =
instance((f, acc) => f(reverse(acc)))
given [H1, T1 <: Tuple, T <: Tuple, S <: Tuple, Y](using
curryLoop: CurryLoop[T1, T, H1 *: S, Y]
): Aux[H1 *: T1, T, S, Y, H1 => curryLoop.Out] =
instance((f, acc) => h1 => curryLoop(f, h1 *: acc))
def curry[T <: Tuple, Y](f: T => Y)(using
curryInst: Curry[T, Y]
): curryInst.Out = curryInst(f)
Testing:
// compiles
summon[Curry.Aux[(Int, String, Boolean), String, Int => String => Boolean => String]]
val c = summon[Curry[(Int, String, Boolean), String]] // compiles
summon[c.Out =:= (Int => String => Boolean => String)] // compiles
val f: ((Int, String, Boolean)) => String = t => s"${t._1}, ${t._2}, ${t._3}"
val g = curry(f)
g: (Int => String => Boolean => String) // checking the type
g(1)("a")(true) // 1, a, true
A method tupled transforming (X1, ..., Xn) => Y into ((X1, ..., Xn)) => Y can be implemented as a transparent macro. A macro being transparent (this corresponds to whitebox in Scala 2) means that it can return a type more precise than declared.
import scala.quoted.*
transparent inline def tupled[F](f: F): Any = ${tupledImpl('f)}
def tupledImpl[F: Type](f: Expr[F])(using Quotes): Expr[Any] =
import quotes.reflect.*
val allTypeArgs = TypeRepr.of[F].typeArgs
val argTypes = allTypeArgs.init
val argCount = argTypes.length
val returnType = allTypeArgs.last
val tupleType = AppliedType(
TypeRepr.typeConstructorOf(Class.forName(s"scala.Tuple$argCount")),
argTypes
)
(tupleType.asType, returnType.asType) match
case ('[t], '[b]) => '{
(a: t) => ${
Apply(
Select.unique(f.asTerm, "apply"),
(1 to argCount).toList.map(i => Select.unique('a.asTerm, s"_$i"))
).asExprOf[b]
}
}
Testing:
val f: (Int, String, Boolean) => String = (i, s, b) => s"$i, $s, $b"
val g = tupled(f)
g: (((Int, String, Boolean)) => String) // checking the type
g((1, "a", true)) // 1, a, true
This gives us curry for types (X1, ..., Xn) => Y
curry(tupled(f))(1)("a")(true) // 1, a, true
Although curry(tupled(f)) works for a specific f it's not easy to specify the signature of a method (composing curry and tupled)
// for match-type implementation of curry
transparent inline def curry1[F](f: F): Any = curry(tupled(f))
curry1(f)(1)("a")(true)
// doesn't compile: method curry1 ... does not take more parameters
// for type-class implementation of curry
transparent inline def curry1[F](f: F): Any = curry(tupled(f))
// doesn't compile: No given instance of type Curry[Nothing, Any] was found...
// (and what types to specify in (using Curry[???, ???]) ?)
I thought that Recovering precise types using patterns should help if I make curry1 a macro too
transparent inline def curry1[F](f: F): Any = ${curry1Impl[F]('f)}
def curry1Impl[F: Type](f: Expr[F])(using Quotes): Expr[Any] =
import quotes.reflect.*
'{ tupled[F]($f) } match
case
'{
type t <: Tuple
$x: (`t` => y)
} =>
Expr.summon[Curry[t, y]] match
case Some(c) => '{curry[t, y]($x)(using $c)}
but it doesn't. If transparent inline def tupled[F](f: F): Any = ... then '{ tupled[F]($f) } doesn't match '{...; $x: (`t` => y)}. If transparent inline def tupled[F](f: F): Function1[?, ?] = ... then t is Nothing, y is Any.
So let's make tupled an implicit macro (type class) in order to control better the return type of tupled
import scala.quoted.*
trait Tupled[F]:
type Out
def apply(f: F): Out
object Tupled:
type Aux[F, Out0] = Tupled[F] { type Out = Out0 }
def instance[F, Out0](g: F => Out0): Aux[F, Out0] = new Tupled[F]:
type Out = Out0
def apply(f: F): Out = g(f)
transparent inline given [F]: Tupled[F] = ${mkTupledImpl[F]}
def mkTupledImpl[F: Type](using Quotes): Expr[Tupled[F]] =
import quotes.reflect.*
val allTypeArgs = TypeRepr.of[F].typeArgs
val argTypes = allTypeArgs.init
val argCount = argTypes.length
val returnType = allTypeArgs.last
val tupleType = AppliedType(
TypeRepr.typeConstructorOf(Class.forName(s"scala.Tuple$argCount")),
argTypes
)
(tupleType.asType, returnType.asType) match
case ('[t], '[b]) => '{
instance[F, t => b]((f: F) => (a: t) => ${
Apply(
Select.unique('f.asTerm, "apply"),
(1 to argCount).toList.map(i => Select.unique('a.asTerm, s"_$i"))
).asExprOf[b]
})
}
def tupled[F](f: F)(using tupledInst: Tupled[F]): tupledInst.Out = tupledInst(f)
// for match-type implementation of curry
inline def curry1[F, T <: Tuple, Y](f: F)(using
tupledInst: Tupled[F],
ev: tupledInst.Out <:< (T => Y),
): Curry[T, Y] = curry(tupled(f))
Testing:
val f: (Int, String, Boolean) => String = (i, s, b) => s"$i, $s, $b"
val g = curry1(f)
g : (Int => String => Boolean => String) // checking the type
g(1)("a")(true) // 1, a, true
Alternatively to tupled, you can try built-in type class scala.util.TupledFunction https://docs.scala-lang.org/scala3/reference/experimental/tupled-function.html (thanks to #MartinHH
for pointing this out in the comments)
// for match-type implementation of curry
inline def curry1[F, T <: Tuple, Y](f: F)(using
tf: TupledFunction[F, T => Y]
): Curry[T, Y] = curry(tf.tupled(f))
// for type-class implementation of curry
def curry1[F, T <: Tuple, Y](f: F)(using
tf: TupledFunction[F, T => Y],
c: Curry[T, Y]
): c.Out = curry(tf.tupled(f))
TupledFunction is similar to type classes shapeless.ops.function.{FnToProduct, FnFromProduct} in Scala 2
https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#facilities-for-abstracting-over-arity
Partial function application in Scala for arbitrary input arguments
Function taking another function of arbitrary arity as argument
Scala's type system and the input to FunctionN

Scala 3 collection partitioning with subtypes

In Scala 3, let's say I have a List[Try[String]]. Can I split it up into success and failures, such that each list has the appropriate subtype?
If I do the following:
import scala.util.{Try, Success, Failure}
val tries = List(Success("1"), Failure(Exception("2")))
val (successes, failures) = tries.partition(_.isSuccess)
then successes and failures are still of type List[Try[String]]. The same goes if I filter based on the type:
val successes = tries.filter(_.isInstanceOf[Success[String]])
I could of course cast to Success and Failure respectively, but is there a type-safe way to achieve this?
#Luis Miguel Mejía Suárez:
Use tries.partitionMap(_.toEither)
#mitchus:
#LuisMiguelMejíaSuárez ok the trick here is that Try has a toEither method which splits to the proper type. What if we have a regular sealed trait?
In Scala 2 I would do something like
import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil, Inl, Inr, Poly0}
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.{FillWith, Mapped, Tupler}
trait Loop[C <: Coproduct, L <: HList] {
def apply(c: C, l: L): L
}
object Loop {
implicit def recur[H, CT <: Coproduct, HT <: HList](implicit
loop: Loop[CT, HT]
): Loop[H :+: CT, List[H] :: HT] = {
case (Inl(h), hs :: ht) => (h :: hs) :: ht
case (Inr(ct), hs :: ht) => hs :: loop(ct, ht)
}
implicit val base: Loop[CNil, HNil] = (_, l) => l
}
object nilPoly extends Poly0 {
implicit def cse[A]: Case0[List[A]] = at(Nil)
}
def partition[A, C <: Coproduct, L <: HList, L1 <: HList](as: List[A])(implicit
generic: Generic.Aux[A, C],
toHList: ToHList.Aux[C, L],
mapped: Mapped.Aux[L, List, L1],
loop: Loop[C, L1],
fillWith: FillWith[nilPoly.type, L1],
tupler: Tupler[L1]
): tupler.Out = {
val partitionHList: L1 = as.foldRight(fillWith())((a, l1) =>
loop(generic.to(a), l1)
)
tupler(partitionHList)
}
sealed trait A
case class B(i: Int) extends A
case class C(i: Int) extends A
case class D(i: Int) extends A
partition(List[A](B(1), B(2), C(1), C(2), D(1), D(2), B(3), C(3)))
// (List(B(1), B(2), B(3)),List(C(1), C(2), C(3)),List(D(1), D(2))): (List[B], List[C], List[D])
https://scastie.scala-lang.org/DmytroMitin/uQp603sXT7WFYmYntDXmIw/1
I managed to translate this code into Scala 3 although the translation turned to be wordy (I remplemented Generic and Coproduct)
import scala.annotation.tailrec
import scala.deriving.Mirror
object App1 {
// ============= Generic =====================
trait Generic[T] {
type Repr
def to(t: T): Repr
def from(r: Repr): T
}
object Generic {
type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
def instance[T, Repr0](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] =
new Generic[T] {
override type Repr = Repr0
override def to(t: T): Repr0 = f(t)
override def from(r: Repr0): T = g(r)
}
object ops {
extension [A](a: A) {
def toRepr(using g: Generic[A]): g.Repr = g.to(a)
}
extension [Repr](a: Repr) {
def to[A](using g: Generic.Aux[A, Repr]): A = g.from(a)
}
}
given [T <: Product](using
m: Mirror.ProductOf[T]
): Aux[T, m.MirroredElemTypes] = instance(
_.productIterator
.foldRight[Tuple](EmptyTuple)(_ *: _)
.asInstanceOf[m.MirroredElemTypes],
m.fromProduct(_).asInstanceOf[T]
)
inline given [T, C <: Coproduct](using
m: Mirror.SumOf[T],
ev: Coproduct.ToCoproduct[m.MirroredElemTypes] =:= C
): Generic.Aux[T, C] =
instance(
matchExpr[T, C](_).asInstanceOf[C],
Coproduct.unsafeFromCoproduct(_).asInstanceOf[T]
)
import scala.quoted.*
inline def matchExpr[T, C <: Coproduct](ident: T): Coproduct =
${matchExprImpl[T, C]('ident)}
def matchExprImpl[T: Type, C <: Coproduct : Type](
ident: Expr[T]
)(using Quotes): Expr[Coproduct] = {
import quotes.reflect.*
def unwrapCoproduct(typeRepr: TypeRepr): List[TypeRepr] = typeRepr match {
case AppliedType(_, List(typ1, typ2)) => typ1 :: unwrapCoproduct(typ2)
case _ => Nil
}
val typeReprs = unwrapCoproduct(TypeRepr.of[C])
val methodIdent =
Ident(TermRef(TypeRepr.of[Coproduct.type], "unsafeToCoproduct"))
def caseDefs(ident: Term): List[CaseDef] =
typeReprs.zipWithIndex.map { (typeRepr, i) =>
CaseDef(
Typed(ident, Inferred(typeRepr) /*TypeIdent(typeRepr.typeSymbol)*/),
None,
Block(
Nil,
Apply(
methodIdent,
List(Literal(IntConstant(i)), ident)
)
)
)
}
def matchTerm(ident: Term): Term = Match(ident, caseDefs(ident))
matchTerm(ident.asTerm).asExprOf[Coproduct]
}
}
// ============= Coproduct =====================
sealed trait Coproduct extends Product with Serializable
sealed trait +:[+H, +T <: Coproduct] extends Coproduct
final case class Inl[+H, +T <: Coproduct](head: H) extends (H +: T)
final case class Inr[+H, +T <: Coproduct](tail: T) extends (H +: T)
sealed trait CNil extends Coproduct
object Coproduct {
def unsafeToCoproduct(length: Int, value: Any): Coproduct =
(0 until length).foldLeft[Coproduct](Inl(value))((c, _) => Inr(c))
#tailrec
def unsafeFromCoproduct(c: Coproduct): Any = c match {
case Inl(h) => h
case Inr(c) => unsafeFromCoproduct(c)
case _: CNil => sys.error("impossible")
}
type ToCoproduct[T <: Tuple] <: Coproduct = T match {
case EmptyTuple => CNil
case h *: t => h +: ToCoproduct[t]
}
// type ToTuple[C <: Coproduct] <: Tuple = C match {
// case CNil => EmptyTuple
// case h +: t => h *: ToTuple[t]
// }
trait ToTuple[C <: Coproduct] {
type Out <: Tuple
}
object ToTuple {
type Aux[C <: Coproduct, Out0 <: Tuple] = ToTuple[C] { type Out = Out0 }
def instance[C <: Coproduct, Out0 <: Tuple]: Aux[C, Out0] =
new ToTuple[C] { override type Out = Out0 }
given [H, T <: Coproduct](using
toTuple: ToTuple[T]
): Aux[H +: T, H *: toTuple.Out] = instance
given Aux[CNil, EmptyTuple] = instance
}
}
}
// different file
import App1.{+:, CNil, Coproduct, Generic, Inl, Inr}
object App2 {
trait Loop[C <: Coproduct, L <: Tuple] {
def apply(c: C, l: L): L
}
object Loop {
given [H, CT <: Coproduct, HT <: Tuple](using
loop: Loop[CT, HT]
): Loop[H +: CT, List[H] *: HT] = {
case (Inl(h), hs *: ht) => (h :: hs) *: ht
case (Inr(ct), hs *: ht) => hs *: loop(ct, ht)
}
given Loop[CNil, EmptyTuple] = (_, l) => l
}
trait FillWithNil[L <: Tuple] {
def apply(): L
}
object FillWithNil {
given [H, T <: Tuple](using
fillWithNil: FillWithNil[T]
): FillWithNil[List[H] *: T] = () => Nil *: fillWithNil()
given FillWithNil[EmptyTuple] = () => EmptyTuple
}
def partition[A, /*L <: Tuple,*/ L1 <: Tuple](as: List[A])(using
generic: Generic.Aux[A, _ <: Coproduct],
toTuple: Coproduct.ToTuple[generic.Repr],
//ev0: Coproduct.ToTuple[generic.Repr] =:= L, // compile-time NPE
ev: Tuple.Map[toTuple.Out/*L*/, List] =:= L1,
loop: Loop[generic.Repr, L1],
fillWith: FillWithNil[L1]
): L1 = as.foldRight(fillWith())((a, l1) => loop(generic.to(a), l1))
sealed trait A
case class B(i: Int) extends A
case class C(i: Int) extends A
case class D(i: Int) extends A
def main(args: Array[String]): Unit = {
println(partition(List[A](B(1), B(2), C(1), C(2), D(1), D(2), B(3), C(3))))
// (List(B(1), B(2), B(3)),List(C(1), C(2), C(3)),List(D(1), D(2)))
}
}
Scala 3.0.2
In the macro (generating pattern matching) Inferred(typeRepr) should be instead of TypeIdent(typeRepr.typeSymbol), otherwise this doesn't work for parametric case classes. Actually the macro can be removed at all if we use mirror.ordinal. Simplified version is
import scala.deriving.Mirror
import scala.util.NotGiven
trait Generic[T] {
type Repr
def to(t: T): Repr
def from(r: Repr): T
}
object Generic {
type Aux[T, Repr0] = Generic[T] {type Repr = Repr0}
def instance[T, Repr0](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] =
new Generic[T] {
override type Repr = Repr0
override def to(t: T): Repr0 = f(t)
override def from(r: Repr0): T = g(r)
}
object ops {
extension[A] (a: A) {
def toRepr(using g: Generic[A]): g.Repr = g.to(a)
}
extension[Repr] (a: Repr) {
def to[A](using g: Generic.Aux[A, Repr]): A = g.from(a)
}
}
given [T <: Product](using
// ev: NotGiven[T <:< Tuple],
// ev1: NotGiven[T <:< Coproduct],
m: Mirror.ProductOf[T],
m1: Mirror.ProductOf[m.MirroredElemTypes]
): Aux[T, m.MirroredElemTypes] = instance(
m1.fromProduct(_),
m.fromProduct(_)
)
given[T, C <: Coproduct](using
// ev: NotGiven[T <:< Tuple],
// ev1: NotGiven[T <:< Coproduct],
m: Mirror.SumOf[T],
ev2: Coproduct.ToCoproduct[m.MirroredElemTypes] =:= C
): Generic.Aux[T, C/*Coproduct.ToCoproduct[m.MirroredElemTypes]*/] = {
instance(
t => Coproduct.unsafeToCoproduct(m.ordinal(t), t).asInstanceOf[C],
Coproduct.unsafeFromCoproduct(_).asInstanceOf[T]
)
}
}
sealed trait Coproduct extends Product with Serializable
sealed trait +:[+H, +T <: Coproduct] extends Coproduct
final case class Inl[+H, +T <: Coproduct](head: H) extends (H +: T)
final case class Inr[+H, +T <: Coproduct](tail: T) extends (H +: T)
sealed trait CNil extends Coproduct
object Coproduct {
def unsafeToCoproduct(length: Int, value: Any): Coproduct =
(0 until length).foldLeft[Coproduct](Inl(value))((c, _) => Inr(c))
#scala.annotation.tailrec
def unsafeFromCoproduct(c: Coproduct): Any = c match {
case Inl(h) => h
case Inr(c) => unsafeFromCoproduct(c)
case _: CNil => sys.error("impossible")
}
type ToCoproduct[T <: Tuple] <: Coproduct = T match {
case EmptyTuple => CNil
case h *: t => h +: ToCoproduct[t]
}
type ToTuple[C <: Coproduct] <: Tuple = C match {
case CNil => EmptyTuple
case h +: t => h *: ToTuple[t]
}
}
Replacing type classes with compile-time/inline methods and match types
import scala.compiletime.erasedValue
inline def loop[C <: Coproduct, L <: Tuple](c: C, l: L): L = (inline erasedValue[C] match {
case _: CNil => inline erasedValue[L] match {
case _: EmptyTuple => EmptyTuple
}
case _: (h +: ct) => inline erasedValue[L] match {
case _: (List[`h`] *: ht) => (c, l) match {
case (Inl(h_v: `h`), (hs_v: List[`h`]) *: (ht_v: `ht`)) =>
(h_v :: hs_v) *: ht_v
case (Inr(ct_v: `ct`), (hs_v: List[`h`]) *: (ht_v: `ht`)) =>
hs_v *: loop[ct, ht](ct_v, ht_v)
}
}
}).asInstanceOf[L]
inline def fillWithNil[L <: Tuple]: L = (inline erasedValue[L] match {
case _: EmptyTuple => EmptyTuple
case _: (List[h] *: t) => Nil *: fillWithNil[t]
}).asInstanceOf[L]
type TupleList[C <: Coproduct] = Tuple.Map[Coproduct.ToTuple[C], List]
inline def partition[A](as: List[A])(using
generic: Generic.Aux[A, _ <: Coproduct]
): TupleList[generic.Repr] =
as.foldRight(fillWithNil[TupleList[generic.Repr]])((a, l1) => loop(generic.to(a), l1))
sealed trait A
case class B(i: Int) extends A
case class C(i: Int) extends A
case class D(i: Int) extends A
#main def test = {
println(partition(List[A](B(1), B(2), C(1), C(2), D(1), D(2), B(3), C(3))))
// (List(B(1), B(2), B(3)),List(C(1), C(2), C(3)),List(D(1), D(2)))
}
Tested in 3.2.0 https://scastie.scala-lang.org/DmytroMitin/940QaiqDQQ2QegCyxTbEIQ/1
How to access parameter list of case class in a dotty macro
Alternative implementation of loop
//Loop[C, L] = L
type Loop[C <: Coproduct, L <: Tuple] <: Tuple = C match {
case CNil => CNilLoop[L]
case h +: ct => CConsLoop[h, ct, L]
}
// match types seem not to support nested type matching
type CNilLoop[L <: Tuple] <: Tuple = L match {
case EmptyTuple => EmptyTuple
}
type CConsLoop[H, CT <: Coproduct, L <: Tuple] <: Tuple = L match {
case List[H] *: ht => List[H] *: Loop[CT, ht]
}
/*inline*/ def loop0[C <: Coproduct, L <: Tuple](c: C, l: L): Loop[C, L] = /*inline*/ c match {
case _: CNil => /*inline*/ l match {
case _: EmptyTuple => EmptyTuple
}
case c: (h +: ct) => /*inline*/ l match {
case l: (List[`h`] *: ht) => (c, l) match {
case (Inl(h_v/*: `h`*/), (hs_v/*: List[`h`]*/) *: (ht_v/*: `ht`*/)) =>
(h_v :: hs_v) *: ht_v.asInstanceOf[Loop[ct, ht]]
case (Inr(ct_v/*: `ct`*/), (hs_v/*: List[`h`]*/) *: (ht_v/*: `ht`*/)) =>
hs_v *: loop0[ct, ht](ct_v, ht_v)
}
}
}
/*inline*/ def loop[C <: Coproduct, L <: Tuple](c: C, l: L): L = loop0(c, l).asInstanceOf[L]
Another implementation for Scala 2: Split list of algebraic date type to lists of branches?

How to extract types from a tuple that implements a typeclass

Function a can receive single argument or a tuple, these arguments need to be members of typeclass StringIdentifiable
How to extract and decompose tuple type into types that also have instances of the typeclass
#typeclass trait StringIdentifiable[M] {
def identify(id: M): String
}
def a[K: StringIdentifiable] (k:K){
k match{
case (k1) =>
implicitly[StringIdentifiable[K]].identify(k1)
case (k1,k2) =>
implicitly[StringIdentifiable[k1.type]].identify(k1)
implicitly[StringIdentifiable[k2.type]].identify(k2)
}
I get error in the second match:
Could not find an instance of StringIdentifiable for k1.type
k1.type, k2.type are singleton types. Try
#typeclass trait StringIdentifiable[M] {
def identify(id: M): String
}
object StringIdentifiable {
implicit def one[K]: StringIdentifiable[K] = ???
implicit def two[K1: StringIdentifiable, K2: StringIdentifiable]: StringIdentifiable[(K1, K2)] = {
new StringIdentifiable[(K1, K2)] {
override def identify(id: (K1, K2)): String = id match {
case (k1,k2) =>
implicitly[StringIdentifiable[K1]].identify(k1)
implicitly[StringIdentifiable[K2]].identify(k2)
???
}
}
}
}
def a[K: StringIdentifiable](k:K): String = implicitly[StringIdentifiable[K]].identify(k)
You can do this with shapeless. For instance:
import shapeless._, ops.hlist._
object MyPoly extends Poly2 {
implicit def foo[A] = at[A, StringIdentifiable[A]]( (a, f) => f.identify(a) )
}
def a[K: StringIdentifiable, L <: HList, O <: HList](k: K)(
implicit
gen: Generic.Aux[K, L], // decompose K into HList L
lift: LiftAll.Aux[StringIdentifiable, L, O], // find an instance of StringIdentifiable for every element of L
zip: ZipWith[L, O, MyPoly.type] // zip L with its typeclass instances and map the results with the polymorphic function MyPoly
): String :: zip.Out = {
val l = gen.to(k)
val o = lift.instances
implicitly[StringIdentifiable[K]].identify(k) :: zip(l, o)
}
implicit def id1[A,B]: StringIdentifiable[(A, B)] = _ => "1"
implicit val id2: StringIdentifiable[String] = _ => "2"
implicit val id3: StringIdentifiable[Int] = _ => "3"
a(("foo", 42)) // 1 :: 2 :: 3 :: HNil
A full solution to your problem (IIUC) probably consists of using shapeless to automatically generate StringIdentifiable instances for all tuples.
trait StringIdentifiable[M] {
def identify(id: M): String
}
object StringIdentifiable {
object MyPoly extends Poly2 {
implicit def foo[A] = at[A, StringIdentifiable[A]]( (a, f) => f.identify(a) )
}
implicit def mkSI[K, L <: HList, O <: HList](
implicit
tup: IsTuple[K],
gen: Generic.Aux[K, L],
lift: LiftAll.Aux[StringIdentifiable, L, O],
zip: ZipWith[L, O, MyPoly.type]
): StringIdentifiable[K] = {
val o = lift.instances
k => {
val l = gen.to(k)
zip(l, o).mkString("(", ", ", ")")
}
}
}

Deriving nested shapeless lenses using only a type

I'm trying to come up with something similar to Classy Lenses to use with cats-mtl. For this, I want to be able to construct a Lens based on provided types only. I found no way to do it using operations provided in shapeless, so I'm writing a new one.
import shapeless._
class Classy[O[_, _], S, A](val get: O[S, A])
object Classy {
def apply[O[_, _], S, A](implicit ev: Classy[O, S, A]): Classy[O, S, A] = ev
implicit def rootLens[S]: Classy[Lens, S, S] =
new Classy(OpticDefns.id[S])
implicit def elementLens[S, L <: HList, A](
implicit genLens: MkGenericLens.Aux[S, L],
aLens: MkHListSelectLens[L, A]
): Classy[Lens, S, A] = new Classy(aLens() compose genLens())
implicit def composeLens[S, A, T](
implicit lh: Lazy[Classy[Lens, S, A]],
rh: Classy[Lens, A, T]
): Classy[Lens, S, T] = new Classy(rh.get compose lh.value.get)
}
Unfortunately, the case I'm after is not compiling:
Classy[Lens, String, String] // OK
Classy[Lens, (Long, String), String] // OK
Classy.composeLens[(Int, (Long, String)), (Long, String), String] // OK, explicit call with explicit params
//Classy[Lens, (Int, (Long, String)), String] // <- doesn't compile
I tried a number of combinations with Lazy/ Strict / plain implicit, but none of these have worked.
Try the following approach with redefining operations making them work deeper:
import shapeless.{::, DepFn1, DepFn2, Generic, HList, HNil, Lens, OpticDefns}
trait DeepGeneric[T <: Product] {
type Repr <: HList
def to(t : T) : Repr
def from(r : Repr) : T
}
object DeepGeneric {
type Aux[T <: Product, Repr0 <: HList] = DeepGeneric[T] { type Repr = Repr0 }
def instance[T <: Product, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def deepGeneric[A <: Product, L <: HList, L1 <: HList](implicit
generic: Generic.Aux[A, L],
hListDeepGeneric: HListDeepGeneric.Aux[L, L1]
): Aux[A, L1] = instance(a => hListDeepGeneric.to(generic.to(a)), l1 => generic.from(hListDeepGeneric.from(l1)))
}
trait HListDeepGeneric[T <: HList] {
type Repr <: HList
def to(t : T) : Repr
def from(r : Repr) : T
}
trait LowPriorityHListDeepGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepGeneric[T] { type Repr = Repr0 }
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def headNotCaseClass[H, T <: HList, T_hListDeepGen <: HList](implicit
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H :: T_hListDeepGen] = instance({
case h :: t => h :: tailHListDeepGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepGeneric.from(t)
})
}
object HListDeepGeneric extends LowPriorityHListDeepGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[H <: Product, T <: HList, H_deepGen <: HList, T_hListDeepGen <: HList](implicit
headDeepGeneric: DeepGeneric.Aux[H, H_deepGen],
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H_deepGen :: T_hListDeepGen] = instance({
case h :: t => headDeepGeneric.to(h) :: tailHListDeepGeneric.to(t)
}, {
case h :: t => headDeepGeneric.from(h) :: tailHListDeepGeneric.from(t)
})
}
// example
case class A(i: Int, b: Boolean)
case class B(s: String, d: Double)
case class C(a: A, b: B, l: Long)
implicitly[DeepGeneric.Aux[C, (Int :: Boolean :: HNil) :: (String :: Double :: HNil) :: Long :: HNil]]
trait DeepSelector[L <: HList, U] extends DepFn1[L] { type Out = U }
trait LowPriorityDeepSelector {
def instance[L <: HList, U](f: L => U): DeepSelector[L, U] = (l: L) => f(l)
implicit def select[H, T <: HList]: DeepSelector[H :: T, H] = instance(_.head)
}
object DeepSelector extends LowPriorityDeepSelector {
implicit def deepSelect[H <: HList, T <: HList, H_deepGen <: HList, U](implicit
deepSelector: DeepSelector[H, U]
): DeepSelector[H :: T, U] =
instance(l => deepSelector(l.head))
implicit def recurse[H, T <: HList, U](implicit deepSelector: DeepSelector[T, U]): DeepSelector[H :: T, U] =
instance(l => deepSelector(l.tail))
}
trait DeepReplacer[L <: HList, U, V] extends DepFn2[L, V]
trait LowPriorityDeepReplacer {
type Aux[L <: HList, U, V, Out0] = DeepReplacer[L, U, V] { type Out = Out0 }
def instance[L <: HList, U, V, Out0](f: (L, V) => Out0): Aux[L, U, V, Out0] = new DeepReplacer[L, U, V] {
override type Out = Out0
override def apply(l: L, v: V): Out = f(l, v)
}
implicit def replace[T <: HList, U, V]: Aux[U :: T, U, V, (U, V :: T)] = instance((l, v) => (l.head, v :: l.tail))
}
object DeepReplacer extends LowPriorityDeepReplacer {
implicit def deepReplace[H <: HList, T <: HList, U, V, H1 <: HList](implicit
deepReplacer: Aux[H, U, V, (U, H1)]): Aux[H :: T, U, V, (U, H1 :: T)] = instance((l, v) => {
val (u, h1) = deepReplacer(l.head, v)
(u, h1 :: l.tail)
})
implicit def recurse[H, T <: HList, U, V, OutT <: HList](implicit
deepReplacer : Aux[T, U, V, (U, OutT)]): Aux[H :: T, U, V, (U, H :: OutT)] = instance((l, v) => {
val (u, l1) = deepReplacer(l.tail, v)
(u, l.head :: l1)
})
}
trait MkDeepGenericLens[T] {
type Repr
def apply(): Lens[T, Repr]
}
object MkDeepGenericLens {
type Aux[T, Repr0] = MkDeepGenericLens[T] { type Repr = Repr0 }
def instance[T, Repr0](f: => Lens[T, Repr0]): Aux[T, Repr0] = new MkDeepGenericLens[T] {
override type Repr = Repr0
override def apply(): Lens[T, Repr] = f
}
implicit def mkDeepGenericLens[T <: Product](implicit gen: DeepGeneric[T]): Aux[T, gen.Repr] =
instance(new Lens[T, gen.Repr] {
def get(t: T): gen.Repr = gen.to(t)
def set(t: T)(r: gen.Repr): T = gen.from(r)
})
}
trait MkHListDeepSelectLens[L <: HList, U] {
def apply(): Lens[L, U]
}
object MkHListDeepSelectLens {
def instance[L <: HList, U](f: => Lens[L, U]): MkHListDeepSelectLens[L, U] = () => f
implicit def mKHlistDeepSelectLens[L <: HList, U](implicit
selector: DeepSelector[L, U], replacer: DeepReplacer.Aux[L, U, U, (U, L)]): MkHListDeepSelectLens[L, U] =
instance(new Lens[L, U] {
def get(l: L) = selector(l)
def set(l: L)(u: U): L = replacer(l, u)._2
})
}
class Classy[O[_, _], S, A](val get: O[S, A])
object Classy {
def apply[O[_, _], S, A](implicit ev: Classy[O, S, A]): Classy[O, S, A] = ev
implicit def rootLens[S]: Classy[Lens, S, S] = new Classy(OpticDefns.id[S])
implicit def elementLens[S, L <: HList, A](implicit
genLens: MkDeepGenericLens.Aux[S, L],
aLens: MkHListDeepSelectLens[L, A]
): Classy[Lens, S, A] = new Classy(aLens() compose genLens())
}
Classy[Lens, String, String] // OK
Classy[Lens, (Long, String), String] // OK
Classy[Lens, (Int, (Long, String), Double), String] // OK
Motivated by example https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/deephlister.scala

Merge non-null fields in two case classes

I'm trying to modify caseclassmerge example from shapeless library to only merge non-null fields.
object mergeSyntax {
implicit class MergeSyntax[T](t: T) {
def merge[U](u: U)(implicit merge: CaseClassMerge[T, U]): T = merge(t, u)
}
}
trait CaseClassMerge[T, U] {
def apply(t: T, u: U): T
}
object CaseClassMerge {
import ops.record.Merger
def apply[T, U](implicit merge: CaseClassMerge[T, U]): CaseClassMerge[T, U] = merge
implicit def mkCCMerge[T, U, RT <: HList, RU <: HList]
(implicit
tgen: LabelledGeneric.Aux[T, RT],
ugen: LabelledGeneric.Aux[U, RU],
merger: Merger.Aux[RT, RU, RT]
): CaseClassMerge[T, U] =
new CaseClassMerge[T, U] {
def apply(t: T, u: U): T =
tgen.from(merger(tgen.to(t), ugen.to(u)))
}
}
How to modify the merging logic in a way that only non-null fields in the second argument will be merged into the first argument?
You could add you own implicit merger implementation to the current scope, overriding the standard merger:
object NotNullHListMerger {
import shapeless.labelled._
import shapeless.ops.record.Merger
import shapeless.ops.record.Remover
implicit def notNullHListMerger[K, V, T <: HList, M <: HList, MT <: HList]
(implicit
rm: Remover.Aux[M, K, (V, MT)],
mt: Merger[T, MT]
): Merger.Aux[FieldType[K, V] :: T, M, FieldType[K, V] :: mt.Out] =
new Merger[FieldType[K, V] :: T, M] {
type Out = FieldType[K, V] :: mt.Out
def apply(l: FieldType[K, V] :: T, m: M): Out = {
val (mv, mr) = rm(m)
val up = field[K](mv)
// Replace only if value is not null
val h = Option(up).getOrElse(l.head)
h :: mt(l.tail, mr)
}
}
}
import mergeSyntax._
import NotNullHListMerger._
case class Foo(i: Int, s: String, b: Boolean)
case class Bar(b: Boolean, s: String)
val foo = Foo(23, "foo", true)
val bar = Bar(false, null)
val merged = foo merge bar
assert(merged == Foo(23, "foo", false))