Map for generic HList - scala

Say we have following method
def func[T <: HList](hlist: T, poly: Poly)
(implicit mapper : Mapper[poly.type, T]): Unit = {
hlist map poly
}
and custom Poly
object f extends (Set ~>> String) {
def apply[T](s : Set[T]) = s.head.toString
}
So I can use this func like
func(Set(1, 2) :: Set(3, 4) :: HNil, f)
In my code I have small number of Polies and a big number of func invocations. For this purpose I tried to move poly: Poly to implicit parameters and got expected message
illegal dependent method type: parameter appears in the type of another parameter in the same section or an earlier one
How could I change or extend poly: Poly parameter to avoid this error (I need to keep type signature func[T <: HList](...))?

Maybe you could use the "partially applied" trick using a class with an apply method :
import shapeless._
import ops.hlist.Mapper
final class PartFunc[P <: Poly](val poly: P) {
def apply[L <: HList](l: L)(implicit mapper: Mapper[poly.type, L]): mapper.Out =
l map poly
}
def func[P <: Poly](poly: P) = new PartFunc(poly)
With your poly f :
val ff = func(f)
ff(Set(1, 2) :: Set(3, 4) :: HNil) // 1 :: 3 :: HNil
ff(Set("a", "b") :: Set("c", "d") :: HNil) // a :: c :: HNil

Related

Is there a way to use the type of an object as the argument of a type parameter?

I'm trying to use shapeless to do additions and removals from Hlists. But I can't seem to get it to work.
So I here are my lists:
object ShapelessExample {
import shapeless._
def main(args: Array[String]): Unit = {
case class Container[T <: Singleton](name: T)
val a = Container("A") :: Container("B") :: Container("C") :: HNil
val b = Container("B") :: Container("C") :: HNil
println {
a.removeAll[b.type] //doesn't work
}
}
}
So the removeAll method on Hlist only takes a type parameter, but I can't seem to use b.type. I can manually specify a.removeAll[Container["B"] :: Container["C"] :: HNil], but is there any way to just use b's type?
Shapeless tries to remove precisely type b.type but can't find it among Container["A"] :: Container["B"] :: Container["C"] :: HNil so #user is correct, singleton type b.type is too specific.
In order to infer an HList type from a val singleton type try to modify the method
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAll[L1 <: HList](b: L1)(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
a.removeAll(b) // (Container(B) :: Container(C) :: HNil,Container(A) :: HNil)
or
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAllFrom[O <: Singleton] = new {
def apply[L1 >: O <: HList]()(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
}
a.removeAllFrom[b.type]() //(Container(B) :: Container(C) :: HNil,Container(A) :: HNil)

Automatically transfer HList into a Record

My goal is to automatically transfer a HList into a Record on demand.
Note that the below code is written in Scala 2.13 and uses singleton types instead of Symbols.
import shapeless._
import shapeless.record._
import shapeless.labelled._
type BestBeforeDate = Record.`"day" -> Option[Int], "month" -> Option[Int], "year" -> Int`.T
object PolyToField extends Poly1 {
implicit def mapA[A, K]: Case.Aux[A, FieldType[K, A]] = at[A](a => field[K][A](a))
}
val x: BestBeforeDate = (None :: None :: 12 :: HNil)
.map(PolyToField)
I get this error:
type mismatch;
found : None.type with shapeless.labelled.KeyTag[Nothing,None.type] :: None.type with shapeless.labelled.KeyTag[Nothing,None.type] :: Int with shapeless.labelled.KeyTag[Nothing,Int] :: shapeless.HNil
required: emergencymanager.commons.data.package.BestBeforeDate
(which expands to) Option[Int] with shapeless.labelled.KeyTag[String("day"),Option[Int]] :: Option[Int] with shapeless.labelled.KeyTag[String("month"),Option[Int]] :: Int with shapeless.labelled.KeyTag[String("year"),Int] :: shapeless.HNil
It seems like the second type parameter of the Poly1 function is inferred to Nothing instead of what I need.
How can I achieve this?
Firstly, you should specify with type ascription that None has type Option[...] (that's the reason why in Cats there are none[...] and .some, see also, item 9). Or use Option.empty[...].
Secondly, you should use mapper with return type rather than standard shapeless.ops.hlist.Mapper
trait MapperWithReturnType[HF, Out <: HList] extends Serializable {
type In <: HList
def apply(t: In): Out
}
object MapperWithReturnType {
type Aux[HF, Out <: HList, In0 <: HList] =
MapperWithReturnType[HF, Out] { type In = In0 }
def instance[HF, Out <: HList, In0 <: HList](f: In0 => Out): Aux[HF, Out, In0] =
new MapperWithReturnType[HF, Out] {
override type In = In0
override def apply(t: In0): Out = f(t)
}
implicit def hnilMapper[HF <: Poly]: Aux[HF, HNil, HNil] = instance(_ => HNil)
implicit def hconsMapper[HF <: Poly, InH, InT <: HList, OutH, OutT <: HList](implicit
hc : poly.Case1.Aux[HF, InH, OutH],
mt : Aux[HF, OutT, InT]
): Aux[HF, OutH :: OutT, InH :: InT] = instance(l => hc(l.head) :: mt(l.tail))
}
implicit final class HListOps[L <: HList](l : L) extends Serializable {
def mapWithReturnType[Out <: HList](f: Poly)(implicit
mapper: MapperWithReturnType.Aux[f.type, Out, L]
): Out = mapper(l)
}
val x = ((None: Option[Int]) :: (None: Option[Int]) :: 12 :: HNil)
.mapWithReturnType[BestBeforeDate](PolyToField)
// val x = (Option.empty[Int] :: Option.empty[Int] :: 12 :: HNil)
// .mapWithReturnType[BestBeforeDate](PolyToField)
x: BestBeforeDate

Shapeless: complex HList Constraint

I have the following method:
import shapeless._
import shapeless.UnaryTCConstraint._
def method[L <: HList : *->*[Seq]#λ](list: L) = println("checks")
It allows me to ensure the following happens:
val multipleTypes = "abc" :: 1 :: 5.5 :: HNil
val onlyLists = Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil
method(multipleTypes) // throws could not find implicit value ...
method(onlyList) // prints checks
How can I augment method with another parameter list, something like:
def method2[L <: HList : *->*[Seq]#λ, M <: HList](list: L)(builder: M => String) = println("checks")
But with the restriction that the HList M must be of the same size as the HList L and only contain elements of the inner types of the HList L. Let me give an example:
// This should work
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case a :: 1 :: d :: HNil => "works"
}
// This should throw some error at compile time, because the second element is Seq[Int]
// therefore in the builder function I would like the second element to be of type Int.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case a :: true :: d :: HNil => "fails"
}
// This should also fail because the HLists are not of the same length.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
case 1 :: d :: HNil => "fails"
}
If I define method2 like this:
def method2[L <: HList : *->*[Seq]#λ](list: L)(builder: L => String) = println("checks")
It almost solves the problem, the only thing that is missing is that builder will have elements of type Seq[T] instead of elements of type T.
This is a case for ops.hlist.Comapped.
You'd want to define method2 as
def method2[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(builder: M => String)
(implicit ev: Comapped.Aux[L, Seq, M])
But this won't work, because the type M should be calculated prior to typechecking the builder argument.
So the actual implementation becomes something like:
class Impl[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(implicit ev: Comapped.Aux[L, Seq, M])
{
def apply(builder: M => String) = println("checks")
}
def method2[L <: HList : *->*[Seq]#λ, M <: HList]
(list: L)
(implicit ev: Comapped.Aux[L, Seq, M]) = new Impl[L, M](list)
And you can't call it directly. You may use an additional apply, or some other method to provide the implicit argument list implicitly:
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil) apply {
case a :: 1 :: d :: HNil => "works"
}

Creating an HList of all pairs from two HLists

I'm using shapeless in Scala, and I'd like to write a function allPairs that will take two HLists and return an HList of all pairs of elements. For example:
import shapeless._
val list1 = 1 :: "one" :: HNil
val list2 = 2 :: "two" :: HNil
// Has value (1, 2) :: (1, "two") :: ("one", 2) :: ("one", "two") :: HNil
val list3 = allPairs(list1, list2)
Any idea how to do this?
Also, I'd like to emphasize I'm looking for a function, not an inlined block of code.
You can't use a for-comprehension or a combination of map and flatMap with function literals here (as the other answers suggest), since these methods on HList require higher rank functions. If you just have two statically typed lists, this is easy:
import shapeless._
val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
object eachFirst extends Poly1 {
implicit def default[A] = at[A] { a =>
object second extends Poly1 { implicit def default[B] = at[B](a -> _) }
ys map second
}
}
val cartesianProductXsYs = xs flatMap eachFirst
Which gives us the following (appropriately typed):
(1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Writing a method that will do this with HList arguments is trickier. Here's a quick example of how it can be done (with some slightly more general machinery).
I'll start by noting that we can think of finding the Cartesian product of two ordinary lists as "lifting" a function that takes two arguments and returns them as a tuple into the applicative functor for lists. For example, you can write the following in Haskell:
import Control.Applicative (liftA2)
cartesianProd :: [a] -> [b] -> [(a, b)]
cartesianProd = liftA2 (,)
We can write a polymorphic binary function that corresponds to (,) here:
import shapeless._
object tuple extends Poly2 {
implicit def whatever[A, B] = at[A, B] { case (a, b) => (a, b) }
}
And define our example lists again for completeness:
val xs = 1 :: 'b :: 'c' :: HNil
val ys = 4.0 :: "e" :: HNil
Now we'll work toward a method named liftA2 that will allow us to write the following:
liftA2(tuple)(xs, ys)
And get the correct result. The name liftA2 is a little misleading, since we don't really have an applicative functor instance, and since it's not generic—I'm working on the model of the methods named flatMap and map on HList, and am open to suggestions for something better.
Now we need a type class that will allow us to take a Poly2, partially apply it to something, and map the resulting unary function over an HList:
trait ApplyMapper[HF, A, X <: HList, Out <: HList] {
def apply(a: A, x: X): Out
}
object ApplyMapper {
implicit def hnil[HF, A] = new ApplyMapper[HF, A, HNil, HNil] {
def apply(a: A, x: HNil) = HNil
}
implicit def hlist[HF, A, XH, XT <: HList, OutH, OutT <: HList](implicit
pb: Poly.Pullback2Aux[HF, A, XH, OutH],
am: ApplyMapper[HF, A, XT, OutT]
) = new ApplyMapper[HF, A, XH :: XT, OutH :: OutT] {
def apply(a: A, x: XH :: XT) = pb(a, x.head) :: am(a, x.tail)
}
}
And now a type class to help with the lifting:
trait LiftA2[HF, X <: HList, Y <: HList, Out <: HList] {
def apply(x: X, y: Y): Out
}
object LiftA2 {
implicit def hnil[HF, Y <: HList] = new LiftA2[HF, HNil, Y, HNil] {
def apply(x: HNil, y: Y) = HNil
}
implicit def hlist[
HF, XH, XT <: HList, Y <: HList,
Out1 <: HList, Out2 <: HList, Out <: HList
](implicit
am: ApplyMapper[HF, XH, Y, Out1],
lift: LiftA2[HF, XT, Y, Out2],
prepend : PrependAux[Out1, Out2, Out]
) = new LiftA2[HF, XH :: XT, Y, Out] {
def apply(x: XH :: XT, y: Y) = prepend(am(x.head, y), lift(x.tail, y))
}
}
And finally our method itself:
def liftA2[HF, X <: HList, Y <: HList, Out <: HList](hf: HF)(x: X, y: Y)(implicit
lift: LiftA2[HF, X, Y, Out]
) = lift(x, y)
And that's all—now liftA2(tuple)(xs, ys) works.
scala> type Result =
| (Int, Double) :: (Int, String) ::
| (Symbol, Double) :: (Symbol, String) ::
| (Char, Double) :: (Char, String) :: HNil
defined type alias Result
scala> val res: Result = liftA2(tuple)(xs, ys)
res: Result = (1,4.0) :: (1,e) :: ('b,4.0) :: ('b,e) :: (c,4.0) :: (c,e) :: HNil
Just as we wanted.

Map on HList in method with Poly1 based on type parameter of class

I have class, parameterized with HList and some other type. How can I use map on HList in one of its methods?
Compilation of this code throws java.lang.AssertionError:
class Test[L <: HList, P](l: L, p: P) {
type Cont[T] = (P, T)
object generator extends (Id ~> Cont) {
def apply[T](t: T) = p -> t
}
def test(implicit m: Mapper[generator.type, L]) = {
l map generator
}
}
new Test(1 :: HNil, 'a).test // java.lang.AssertionError
My goal is this kind of result:
type Cont[T] = (Symbol, T)
val p = 'a
object generator extends (Id ~> Cont) {
def apply[T](t: T) = p -> t
}
scala> (1 :: 'b' :: HNil) map generator
res0: shapeless.::[(Symbol, Int),shapeless.::[(Symbol, Char),shapeless.HNil]] = ('a,1) :: ('a,b) :: HNil
This is a bug in the Scala compiler (both 2.9.2 and 2.10.0-RC1).
As a workaround, if you split the creation of the instance of Test and the invocation of the test method across two expression then it works as expected,
scala> val t = new Test(1 :: HNil, 'a)
t: Test[shapeless.::[Int,shapeless.HNil],Symbol] = Test#4b153b34
scala> t.test
res0: shapeless.::[(Symbol, Int),shapeless.HNil] = ('a,1) :: HNil