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
Related
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)
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?
I'm trying to foldLeft on a HList with an accumulator of type (HL, Int), where HL is a HList. The program below does not compile. However, if I switch to a simpler accumulator of type HL (by just switching the commented lines with the ones above), it compiles and it works.
Wrapping an HList in a tuple breaks the implicit resolution for the leftFolder. What am I missing?
package foo.bar
import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil, Lazy, Poly2}
import shapeless.ops.hlist.{LeftFolder, Reverse}
object StackOverflow extends App {
trait MyTypeclass[T] {
def doSomething(t: T): (T, Int)
}
implicit lazy val stringInstance: MyTypeclass[String] = (t: String) => (t, 0)
implicit val hnilInstance: MyTypeclass[HNil] = (t: HNil) => (t, 0)
implicit def hlistInstance[H, T <: HList](
implicit
head: Lazy[MyTypeclass[H]],
tail: MyTypeclass[T]
): MyTypeclass[H :: T] =
(ht: H :: T) =>
ht match {
case h :: t =>
val (hres, hint) = head.value.doSomething(h)
val (tres, tint) = tail.doSomething(t)
(hres :: tres, hint + tint)
}
implicit val cnilInstance: MyTypeclass[CNil] = (t: CNil) => ???
implicit def coproductInstance[L, R <: Coproduct](
implicit
head: Lazy[MyTypeclass[L]],
tail: MyTypeclass[R]
): MyTypeclass[L :+: R] = (lr: L :+: R) => ???
object leftFolder extends Poly2 {
implicit def caseAtSimple[F, HL <: HList]: Case.Aux[HL, F, F :: HL] =
at {
case (acc, f) => f :: acc
}
implicit def caseAtComplex[F, HL <: HList]: Case.Aux[(HL, Int), F, (F :: HL, Int)] =
at {
case ((acc, i), f) => (f :: acc, i)
}
}
implicit def genericInstance[T, HL <: HList, LL <: HList](
implicit
gen: Generic.Aux[T, HL],
myTypeclass: Lazy[MyTypeclass[HL]],
// folder: LeftFolder.Aux[HL, HNil, leftFolder.type, LL],
folder: LeftFolder.Aux[HL, (HNil, Int), leftFolder.type, (LL, Int)],
reverse: Reverse.Aux[LL, HL]
): MyTypeclass[T] = (t: T) => {
val generic = gen.to(t)
val (transformed, idx) = myTypeclass.value.doSomething(generic)
// val ll = transformed.foldLeft(HNil: HNil)(leftFolder)
val (ll, _) = transformed.foldLeft((HNil: HNil, 0))(leftFolder)
val reversed = reverse(ll)
(gen.from(reversed), idx)
}
def doSomething[T](t: T)(implicit myTypeclass: MyTypeclass[T]): T = myTypeclass.doSomething(t)._1
case class Foo(
str1: String,
str2: String
)
val original = Foo("Hello World!", "Hello there!")
val result = doSomething(original)
println(result == original)
}
You want implicits to do too much work in a single step.
Try to add one more type parameter Out
implicit def genericInstance[T, HL <: HList, Out, LL <: HList](
implicit
gen: Generic.Aux[T, HL],
myTypeclass: Lazy[MyTypeclass[HL]],
//folder: LeftFolder.Aux[HL, (HNil, Int), leftFolder.type, (LL, Int)],
folder: LeftFolder.Aux[HL, (HNil, Int), leftFolder.type, Out],
ev: Out <:< (LL, Int), // added
reverse: Reverse.Aux[LL, HL]
): MyTypeclass[T] = (t: T) => {
val generic = gen.to(t)
val (transformed, idx) = myTypeclass.value.doSomething(generic)
//val (ll, _) = transformed.foldLeft((HNil: HNil, 0))(leftFolder)
val (ll, _) = ev(transformed.foldLeft((HNil: HNil, 0))(leftFolder))
val reversed = reverse(ll)
(gen.from(reversed), idx)
}
Read about over-constrained implicits:
https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:type-level-programming:chaining (4.3 Chaining dependent functions)
Scala shapeless Generic.Aux implicit parameter not found in unapply
Extract FieldType key and value from HList
How to implicitly figure out the type at the head of a shapeless HList
How to infer inner type of Shapeless record value with unary type constructor?
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
I'm working on an API that should enable to build a shapeless Poly1 function dynamically from standard monomorphic functions that operate on types of some coproduct.
The goal is to expose a simple method that receives a function as:
type FooCoproduct = Foo :+: Bar :+: CNil
def addF[E](f: E => E)(implicit ev: Inject[FooCoproduct, E]) = ???
and accumulate these functions in order to build a total Poly1 function covering all types in the coproduct. The evidence ev here is to force that the type paremeter E is a type in the coproduct.
After testing several approaches, including generic derivation of typeclasses, the most promising one has led me to accumulate these monomorphic functions in an HList and try to resolve the one that applies by means of a Selector. This is probably better understood by example:
object CoproductSample extends App {
import shapeless.{ :+:, CNil, Coproduct, HList, HNil, Poly1 }
import shapeless.ops.coproduct.Inject
import shapeless.ops.hlist.Selector
class Builder[A <: Coproduct] {
def accum[B](f: B => B, hl: HList)(implicit ev: Inject[A, B]) = f :: hl
class PolyBuilder[L <: HList](hl: L) extends Poly1 {
implicit def run[T](implicit ev: Selector[L, T => T]) =
at[T](hl.select[T => T])
}
}
type Cop = Int :+: String :+: CNil
val builder = new Builder[Cop]
val hl1 = builder.accum((i: Int) => i + 1, HNil)
val hl2 = builder.accum((s: String) => s + "one", hl1)
object pf extends builder.PolyBuilder(hl2)
val rInt = Coproduct[Cop](10).fold(pf)
val rStr = Coproduct[Cop]("ten").fold(pf)
}
This code doesn't compile with the message:
could not find implicit value for parameter folder:
shapeless.ops.coproduct.Folder[CoproductSample.pf.type, CoproductSample.Cop]
I suppose that I need to provide a Selector[L, T => T] where L is the type of the accumulated HList but I can't come up with the way to do this. On the other hand, I have the feeling that there must be a simpler solution to my problem.
Any help would be appreciated.
Update
After doing some more research I've come up with a solution that almost works. Unfortunately I'm not able to track the result type properly.
object CoproductSample {
import shapeless.{ CNil, Coproduct, HList, HNil, Inl, Inr, Poly2, ::, :+: }
// Accumulates ordinary functions from A => A in an HList
def accum[A, L <: HList](f: A => A, hl: L): (A => A) :: L = f :: hl
// A poly2 function that evaluates some monomorphic function present in
// an HList for certain value that satifies the signature of this function
object PolyEval extends Poly2 {
implicit def hnilCase[A]: Case.Aux[A, HNil, Option[A]] =
at[A, HNil]((a, l) => None)
implicit def hheadCaseSuccess[A, T <: HList]: Case.Aux[A, (A => A) :: T, Option[A]] =
at[A, (A => A) :: T]((a: A, l: (A => A) :: T) => Option(l.head(a)))
implicit def hheadCaseFail[A, H, T <: HList](
implicit tail: Case.Aux[A, T, Option[A]]
): Case.Aux[A, (H => H) :: T, Option[A]] =
at[A, (H => H) :: T]((a: A, l: (H => H) :: T) => PolyEval(a, l.tail))
}
// A poly2 function that uses `PolyEval` for evaluating a value present in
// a coproduct against an HList of monomorphic functions
object PolyEvalCop extends Poly2 {
implicit def cnilCase[A <: CNil, L <: HList]: Case.Aux[A, L, Option[A]] =
at[A, L]((a, l) => sys.error("Impossible!"))
implicit def cconsCase[H, T <: Coproduct, L <: HList](
implicit head: PolyEval.Case.Aux[H, L, Option[H]],
tail: Case[T, L]) // What is the return type here???)
= at[H :+: T, L]((c, l) =>
c match {
case Inl(h) => PolyEval(h, l)
case Inr(t) => PolyEvalCop(t, l)
})
}
}
Console session:
scala> import shapeless._, CoproductSample._
import shapeless._
import CoproductSample._
scala> case class Foo(i: Int); case class Bar(s: String)
defined class Foo
defined class Bar
scala> val f = (foo: Foo) => foo.copy(i = foo.i * 2)
f: Foo => Foo = <function1>
scala> val g = (bar: Bar) => bar.copy(s = bar.s + "_changed!")
g: Bar => Bar = <function1>
scala> val hl = accum(g, accum(f, HNil))
hl: shapeless.::[Bar => Bar,shapeless.::[Foo => Foo,shapeless.HNil.type]] = <function1> :: <function1> :: HNil
scala> type C = Foo :+: Bar :+: CNil
defined type alias C
scala> PolyEvalCop(Coproduct[C](Foo(10)), hl)
res1: Any = Some(Foo(20))
scala> PolyEvalCop(Coproduct[C](Bar("bar")), hl)
res2: Any = Some(Bar(bar_changed!))
The result type is not properly tracked and it's resolved as Any.
From the signature of addF it looks like you want to map over the Coproduct, i.e. modify it's value and stay on a coproduct, i.e. def add[E](e:E):E, if that were the case this would work:
# {
trait Add[E]{
def add(e:E):E
}
object Add{
def apply[E:Add]:Add[E] = implicitly[Add[E]]
implicit object cnil extends Add[CNil] {
def add(e:CNil) = throw new RuntimeException("Impossible")
}
implicit def coproduct[H, T <: Coproduct](
implicit
addH: Add[H],
addT:Add[T],
basis: ops.coproduct.Basis[H :+: T,T]
):Add[H :+: T] = new Add[H :+: T]{
def add(e: H :+: T) = e match {
case Inl(h) => Coproduct[H :+: T](addH.add(h)) // to stay in the Coproduct
case Inr(t) => addT.add(t).embed[H :+: T] // to stay in the coproduct
}
}
}
}
defined trait Add
defined object Add
# implicit def addString = new Add[String] {
def add(e:String) = e + "-ah"
}
defined function addString
# implicit def addInt = new Add[Int] {
def add(e:Int) = e + 1
}
defined function addInt
# type C = Int :+: String :+: CNil
defined type C
# val i = Coproduct[C](1)
i: C = 1
# Add[C].add(i)
res24: C = 2 // notice that the return type is C
# val s = Coproduct[C]("a")
s: C = a
# Add[C].add(s)
res26: C = a-ah // notice that the return type is C
It obviously works with "plain" types:
# Add[Int].add(1)
res38: Int = 2
The above is equivalent to map; but if you want a fold, i.e. def add[E](e:E):Int, you would just modify these two lines:
case Inl(h) => addH.add(h)
case Inr(t) => addT.add(t)