I am new to shapeless (and still low level in the learning curve of scala...) and i have some hard time with shapeless
import shapeless._
case class FooBar[T](foo: String, bar: T)
val hl = 0 :: FooBar("A", "one") :: FooBar("B", 1) :: "0" :: FooBar("C", "two") :: HNil
val l = hl.filter[FooBar[String]].toList
println(l) //List(FooBar(A,one), FooBar(C,two))
It works fine
Next step, i want to put that in function, something like
def filter[T](hl: HList): List[FooBar[T]] = ???
so i can simplify calling to
filter[String](hl)
filter[Int](hl)
naively i tested
def filter[T](hl: HList): List[FooBar[T]] = {
hl.filter[FooBar[T]].toList
}
which give
could not find implicit value for parameter partition: shapeless.ops.hlist.Partition[shapeless.HList,FooBar[T]]
after some tries playing with implicit, i still have not found the correct way to do that
Do you have any idea ?
Thank you !
If you lack some implicits then in your method you should suppose they are provided. Saying that an argument of the method is of type just HList (and not some specific L <: HList) is too rough.
Since probably you would like to specify T and not specify L (expecting that L will be inferred) try a type class + extension method
import shapeless._
import shapeless.ops.hlist.{Partition, ToTraversable}
case class FooBar[T](foo: String, bar: T)
val hl = 0 :: FooBar("A", "one") :: FooBar("B", 1) :: "0" :: FooBar("C", "two") :: HNil
trait FilterFooBar[L <: HList, T] {
def apply(l: L): List[FooBar[T]]
}
object FilterFooBar {
implicit def mkFilterFooBar[L <: HList, T, Prefix <: HList, Suffix <: HList](implicit
partition: Partition.Aux[L, FooBar[T], Prefix, Suffix],
toTraversable: ToTraversable.Aux[Prefix, List, FooBar[T]]
): FilterFooBar[L, T] = _.filter.toList
}
implicit class FilterFooBarOp[L <: HList](l: L) {
def filterFooBar[T](implicit filterFooBarInstance: FilterFooBar[L, T]): List[FooBar[T]] =
filterFooBarInstance(l)
}
println(hl.filterFooBar[String]) // List(FooBar(A,one), FooBar(C,two))
println(hl.filterFooBar[Int]) // List(FooBar(B,1))
Related
I'm trying to use shapeless's hlist to construct introspectable URL templates, but I'm having trouble with traversing my HList. The following doesn't compile:
import shapeless.{::, HList, HNil}
import shapeless.LUBConstraint._
import shapeless.ops.hlist.ToTraversable._
import scala.util.Try
import shapeless.ops.hlist._
object Path {
def /(s: String) = Path(PathLiteral(s) :: HNil)
def param[T](name: String) = PathParameter(name)
}
sealed trait PathSegment[+T]
case class PathLiteral(value: String) extends PathSegment[String]
case class PathParameter[+T](name: String) extends PathSegment[T]
case class Path[L <: HList : <<:[PathSegment[_]]#λ](segments: L)
(implicit ev: ToList[L, PathSegment[_]])
{
def /(literal: String) = Path(PathLiteral(literal) :: segments)
def /[T](param: PathParameter[T]) = Path(param :: segments)
override def toString: String = s"Path(${segments.toList.reverse})"
}
object Test extends App {
import Path.param
val myPath = Path / "v1" / "pets" / param[String]("name") / "pictures"
println(myPath)
}
It seems to me like the ToTraversable._ import covers the HNil case and the case of having the tail of an HList and a new head with the same Least Upper Bound. Obviously I'm either missing an import or misunderstanding everything.
I'm not sure if caching the evidence in the class as an implicit parameter is kosher; I'm doing it because
I don't want hlist details to leak into the external API
I need it for a nice toString
The reason this doesn't work is the implicit which can prove:
ToList[PathLiteral :: L, PathSegment[_]] given
ToList[L, PathSegment[L]]
L <: HList
is this implicit def's type signature from hlists.scala:
implicit def hlistToTraversable[H1, H2, T <: HList, LubT, Lub0, M[_]]
(implicit
tttvs : Aux[H2 :: T, M, LubT],
u : Lub[H1, LubT, Lub0],
cbf : CanBuildFrom[M[Lub0], Lub0, M[Lub0]]) : Aux[H1 :: H2 :: T, M, Lub0]
actually needs to know the type of the head of the list as well as the new item (H1 and H2), so it can't be constructed from what's available inside my class.
Some workarounds:
build List manually alongside the HList
case class Path[L <: HList](segments: L, segmentsList: List[PathSegment[_]]) {
def /(literal: String) = Path(PathLiteral(literal) :: segments, PathLiteral(literal) :: segmentsList)
def /[T](param: PathParameter[T]) = Path(param :: segments, param :: segmentsList)
override def toString: String = s"Path(${segmentsList.reverse})"
}
push implicit parameters out to Path's methods
def printPath[L <: HList](path: Path[L])
(implicit ev: ToList[L, PathSegment[_]]) =
path.segments.toList.reverse.collect {
case PathLiteral(value) => URLEncoder.encode(value, "UTF-8")
case PathParameter(name) => s"<$name>"
}.mkString("/")
printPath(Path / "v1" / "pets" / param[String]("name") / "pictures")
// v1/pets/<name>/pictures
Suppose I have container-marker
case class TypedString[T](value: String)
and partial function trick
abstract class PartFunc[Actual <: HList] {
val poly: Poly
def apply[L1 <: HList](l: L1)(implicit
mapped: Mapped.Aux[Actual, TypedString, L1],
mapper: Mapper[poly.type, L1]): L1 = l
}
Poly for Mapper
object f extends (TypedString ~>> String) {
def apply[T](s : TypedString[T]) = s.value
}
and result method
def func[Actual <: HList] = new PartFunc[Actual] {
val poly = f
}
Example of usage:
func[
Int :: String :: HNil
](TypedString[Int]("42") :: TypedString[String]("hello") :: HNil)
This code fails at compile time because compiler cannot find Mapped implicit parameter:
could not find implicit value for parameter mapped:
shapeless.ops.hlist.Mapped[shapeless.::[Int,shapeless.::[String,shapeless.HNil]],nottogether.MapperTest.TypedString]{type Out = shapeless.::[nottogether.MapperTest.TypedString[Int],shapeless.::[nottogether.MapperTest.TypedString[String],shapeless.HNil]]}
](TypedString[Int]("42") :: TypedString[String]("hello") :: HNil)
But if we remove Mapper implicit parameter from PartFunc.apply(...) signature all works fine. So I have no idea why and how Mapper influence on Mapped.
The compiler complains about Mapped while the actual issue is with Mapper. I am not sure why, but there seems to be going something wrong with getting a Mapped for the singleton type poly.type when poly is an abstract value or a constructor argument of PartFunc.
A solution would be to make poly a P <: Poly and passing the singleton type along with Actual when we create the PartFunc :
import shapeless._
import ops.hlist.{Mapped, Mapper}
import poly.~>>
abstract class PartFunc[Actual <: HList, P <: Poly] {
val poly: P
def apply[L1 <: HList](l: L1)(implicit
mapped: Mapped.Aux[Actual, TypedString, L1],
mapper: Mapper[P, L1]
): mapper.Out = mapper(l)
}
def func[Actual <: HList] = new PartFunc[Actual, f.type] { val poly = f }
Or with a regular class :
class PartFunc[Actual <: HList, P <: Poly](poly: P) {
def apply[L1 <: HList](l: L1)(implicit
mapped: Mapped.Aux[Actual, TypedString, L1],
mapper: Mapper[P, L1]
): mapper.Out = mapper(l)
}
def func[Actual <: HList] = new PartFunc[Actual, f.type](f)
Notice that we now have to write mapper(l), because l map poly will still look for a Mapped[poly.type, L1].
We can now call func :
func[
Int :: String :: HNil
](TypedString[Int]("42") :: TypedString[String]("hello") :: HNil)
// String :: String :: HNil = 42 :: hello :: HNil
I am sure that someone with some more in depth knowledge of the Scala type system could provide us with a clearer explanation and possibly a better solution for this problem.
I have the following problem, I want to map items of an HList to another HList but Strings in the source HList should only be converted to URL if the "target" type is URL.
val name = "Stackoverflow"
val url = "https://stackoverflow.com/q"
val list = name :: url :: HNil
val mapped: String :: URL :: HNil = list.map(???)
As far as my research took me is that all the Poly stuff only cares about the input type but not about the output type. So are there ways to archive my goal ?
I don't think you're going to get exactly what you want, since Scala's implicit resolution happens before type inference (but who knows—people do things that surprise me in Scala all the time).
(Side note: the CanBuildFrom / breakOut pattern supports something similar to what you're asking for, but I don't see a way to make it work in this situation, since the source type does constrain what instances are available.)
There is a pretty standard workaround for this kind of situation, though, involving the use of a helper class to approximate partial application of type parameters. Suppose you've got a fairly straightforward type class that captures your conversion logic:
import java.net.URL
import shapeless._
trait Convert[I <: HList, O <: HList] { def apply(i: I): O }
object Convert extends LowPriorityConvertInstances {
implicit val convertHNil: Convert[HNil, HNil] = new Convert[HNil, HNil] {
def apply(i: HNil): HNil = i
}
implicit def convertHConsURL[T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[String :: T, URL :: TO] = new Convert[String :: T, URL :: TO] {
def apply(i: String :: T): URL :: TO = new URL(i.head) :: c(i.tail)
}
}
sealed class LowPriorityConvertInstances {
implicit def convertHCons[H, T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[H :: T, H :: TO] = new Convert[H :: T, H :: TO] {
def apply(i: H :: T): H :: TO = i.head :: c(i.tail)
}
}
Now you might try something like this:
def convert[I <: HList, O <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)
But there are two problems here. The first is that if you let the type parameters be inferred, you'll always get a conversion that turns every string into a URL. You can override this behavior by explicitly providing both type parameters, but ugh.
We can (kind of) improve this situation with a helper class:
class PartiallyAppliedConvert[O <: HList] {
def apply[I <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)
}
def convert[O <: HList]: PartiallyAppliedConvert[O] =
new PartiallyAppliedConvert[O]
Now you can write the following:
scala> val mapped = convert[String :: URL :: HNil](list)
mapped: shapeless.::[String,shapeless.::[java.net.URL,shapeless.HNil]] = Stackoverflow :: https://stackoverflow.com/q :: HNil
It's not exactly what you asked for, but it's pretty close, in that the only type we have to specify explicitly is the desired target type.
I'm trying to make this rather silly example work, planning to then extend it to something more meaningful.
But no luck so far: I get could not find implicit value for parameter ihc
What am I missing?
sealed trait Field[T] { def name: String }
case class IntegerField(name: String) extends Field[Int]
val year = IntegerField("year")
val test = (year :: 23 :: HNil) :: (year :: 2 :: HNil) :: HNil
type TypedMap = IntegerField :: Int :: HNil
def get[L <: HList : <<:[TypedMap]#λ]
(key: IntegerField, list: L)
(implicit ihc: IsHCons.Aux[L, TypedMap, L]
): Option[Int] = {
if( list == HNil ) return None
val elem: TypedMap = list.head
if( elem.head == key ) Some(elem.tail.head)
else get(key, list.tail)
}
get(year, test)
When you write IsHCons.Aux[L, TypedMap, L] you're asking for evidence that the hlist L has head TypedMap and tail L, which would mean that it's an infinite hlist, which isn't possible, since Scala doesn't allow this kind of arbitrarily recursive type (try writing something like type Foo = Int :: Foo, for example—you'll get a "illegal cyclic reference" error). It's also probably not what you want.
In general you're unlikely to use IsHCons much in Shapeless, since it's almost always better just to indicate the structure you want in the type. For example, the following two definitions do the same thing:
import shapeless._, ops.hlist.IsHCons
def foo[L <: HList](l: L)(implicit ev: IsHCons[L]) = ev.head(l)
And:
def foo[H, T <: HList](l: H :: T) = l.head
But the second is obviously preferable (it's clearer, it doesn't require an extra type class instance to be found at compile time, etc.), and it's almost always possible to write whatever you're trying to do that way.
Also note that requiring an IsHCons instance means that the recursion here won't work as stated—you can't call get on an HNil, since the compiler can't prove that it's an HCons (because it's not).
Are you sure you need an hlist at all? If you're requiring that all members of the hlist are of type TypedMap, you might as well use Shapeless's Sized (if you want the type to capture the length) or even just a plain old List.
If you really, really want to use an HList here, I'd suggest writing a new type class:
trait FindField[L <: HList] {
def find(key: IntegerField, l: L): Option[Int]
}
object FindField {
implicit val findFieldHNil: FindField[HNil] = new FindField[HNil] {
def find(key: IntegerField, l: HNil) = None
}
implicit def findFieldHCons[H <: TypedMap, T <: HList](implicit
fft: FindField[T]
): FindField[H :: T] = new FindField[H :: T] {
def find(key: IntegerField, l: H :: T) = if (l.head.head == key)
Some(l.head.tail.head)
else fft.find(key, l.tail)
}
}
def get[L <: HList](key: IntegerField, l: L)(implicit
ffl: FindField[L]
): Option[Int] = ffl.find(key, l)
And then:
scala> get(IntegerField("year"), test)
res3: Option[Int] = Some(23)
scala> get(IntegerField("foo"), test)
res4: Option[Int] = None
This is a pretty common pattern in Shapeless—you inductively describe how to perform an operation on an empty hlist, then on an hlist with a head prepended to a tail that you know how to perform the operation on, etc.
I would like to do something like the following:
val foo = List[B <% JValue] = 42 :: "hello" : Nil
for the compiler to know that the members of my list can be converted to JValues.
However, this does not compile. I cannot settle for a List[Any] because I have to use its members where values that can be converted into JValues are expected, say in:
def fun[A <% JValue](x: List[A]) = ...
Is there any way to solve this?
You can write
val foo: List[JValue] = List(42, "hello")
If there is no implicit conversion from these types to JValue then it won't type check.
Unfortunately you can't write it as 42 :: "hello" :: Nil because the compiler isn't smart enough to know that you want to apply the conversions on each term (you could add a type annotation on each term, but that's messy). Each :: method would have to somehow look ahead all the way to the end of the expression to check if some later method made it fit the type parameter.
However, if you want to add your own funky operator, you can constrain the types allowed by :::
implicit class pimp(xs: List[JValue]) {
def |: (x: JValue) = x :: xs
}
after which you can write stuff like this:
val foo = 42 |: "hello" |: Nil
// type is List[JValue]
(I tried parameterizing this so that it would infer the most specific common type, as :: does, but with an upper bound the compiler didn't want to play ball - see here. Maybe someone with more Scala-fu can fix it, if it's possible.)
A little improvement of |: method from Luigi Plinge's answer:
You could write
val foo: List[JValue] = 42 :: "hello" :: HNil
with an appropriate implicit conversion (using shapeless):
import shapeless._
trait HListTConv[H <: HList, T] {
def apply(l: List[T], hl: H): List[T]
}
object HListTConv {
implicit def apply0[T] = new HListTConv[HNil, T] {
def apply(l: List[T], hl: HNil): List[T] = l
}
implicit def applyN[Head, Tail <: HList, T](implicit c: HListTConv[Tail, T], conv: Head => T) =
new HListTConv[Head :: Tail, T] {
def apply(l: List[T], hl: Head :: Tail): List[T] = (hl.head: T) :: c(l, hl.tail)
}
}
implicit def hListToJValueList[H <: HList](hl: H)(implicit c: HListTConv[H, JValue]): List[JValue] = c(Nil, hl)
Test:
case class Test(s: String)
implicit def intToTest(i: Int): Test = Test(i.toString)
implicit def strToTest(s: String): Test = Test(s)
implicit def hListToListTest[H <: HList](hl: H)(implicit c: HListTConv[H, Test]): List[Test] = c(Nil, hl)
scala> val foo: List[Test] = 42 :: "hello" :: HNil
foo: List[Test] = List(Test(42), Test(hello))
You can enable the conversions with a simple set of implicits.
class JValue
implicit intToJValue(x: Int) = new JValue
implicit stringToJValue(x: String) = new JValue
val xs: List[JValue] = List(1, "hello")
For your second question you can enable wholesale list conversion with:
implicit def listToJList[A <% JValue](xs: List[A]): List[JValue] = xs
def foo[A <% JValue](x: List[A]): List[JValue] = x
This above example only works if you have a uniform type, otherwise you will need to employ a more sophisticated means, a list of heterogeneous types will unify to List[Any] in most cases.
You could come up more elegant/complicated solutions using shapeless, most employing shapless.Poly and HList's.