How to create an implicit converter so this code
case class Cookie(name: String, value: String)
val cookies: Seq[Cookie] = ???
val cookieMap: Map[String, String] = cookies.toMap
wil work? How to define implicit ev: A <:< (K, V)?
You can just do:
cookies.iterator.map(c => c.name -> c.value).toMap
To do the conversion in a single iteration. You may even hide that behind an extension method or something if you please.
Or you can provide an
implicit def Cookie2Tuple2(c: Cookie): (String, String) = c.name -> c.value
But that may blow up in other unexpected parts of the code.
I personally would define my own.
final case class Cookie(underlying: List[Cookie]) {
def toMap: Map[String, String] =
underlying.iterator.map(c => c.name -> c.value).toMap
}
From scala doc:
An instance of A <:< B witnesses that A is a subtype of B. Requiring
an implicit argument of the type A <:< B encodes the generalized
constraint A <: B.
That means that evidence Cookie <:< (K, V) means Cookie must be a subclass of (K,V). But it's not possible as tuple cannot be subclassed. Also, note that evidence are not implicit conversions, there are used to get guaranty at compile time but are not "applied".
(you can also read: https://stackoverflow.com/a/3427759/1206998)
Also, you can look at the implementation of toMap in IterableOnce
def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
immutable.Map.from(this.asInstanceOf[IterableOnce[(K, V)]])
As you can see, it cast the iterable to an iterableOnce of tuple, no conversion of the collection's item is performed. Thus, entries should be effectively (K,V)
So the only thing you can do is add an implicit conversion from Seq[Cookie] to Map(String, String]
import scala.collection.breakOut // to directly convert to Map
case class Cookie(name: String, value: String)
implicit class Cookies(cookies: Seq[Cookie]) {
// use a self descriptive name, to avoid confusion
def toNameValueMap: Map[String, String] = {
cookies.map(c => (c.name, c.value))(breakout)
}
}
// example of use
val cookies: Seq[Cookie] = List(Cookie("chocolate", "good"), Cookie("hazelnut", "better"))
val cookieMap: Map[String, String] = cookies.toNameValueMap
And for the fun of it, here is the generic conversion method that correspond to what you expected toMap to be able to do:
implicit class SeqConversion[A](seq: Seq[A]) {
def convertToMap[K,V](getKey: A => K, getValue: A => V): Map[K,V] ={
seq.map(a => (getKey(a), getValue(a)))(breakOut)
}
}
cookies.convertToMap(_.name, _.value)
Related
So I would like to use a generic return type and be able to use the info of that type within the function. Not sure this is possible but here is what I would like:
def getStuff[A](a: MyObj, b: String): Option[A] = {
// do some stuff
A match {
case String => Some(a.getString(b))
case Integer => Some(a.getInt(b))
...
case _ => None
}
}
However, as you know, A match is not a possibility. Any ideas on how I could achieve this ?
This is a classic case for using a typeclass:
trait StuffGetter[T] { // typeclass
def get(obj: MyObj, s: String): Option[T]
}
implicit val stringGetter = new StuffGetter[String] {
def get(o: MyObj, s: String): Option[String] = ???
}
implicit val intGetter = new StuffGetter[Int] {
def get(o: MyObj, s: String): Option[Int] = ???
}
def getStuff[A](a: MyObj, b: String)(implicit ev: StuffGetter[A]): Option[A] =
ev.get(a, b)
val stuff0 = getStuff[String](obj, "Hello") // calls get on stringGetter
val stuff1 = getStuff[Int](obj, "World") // call get on intGetter
val stuff2 = getStuff[Boolean](obj, "!") // Compile-time error
The StuffGetter trait defines the operations that you want to perform on the generic type, and each implicit value of that trait provides the implementation for a specific type. (For a custom type these are typically place in the companion object for the type; the compiler will look there for them)
When getStuff is called the compiler will look for an implicit instance of StuffGetter with the matching type. This will fail if no such instance exists, otherwise it will be passed in the ev parameter.
The advantage of this is that the "match" is done at compile time and unsupported types are also detected at compile time.
Conceptually we can differentiate between pattern matching at run-time which looks something like this
def getStuff[A](...) =
A match {
...
}
and pattern matching at compile-time which looks something like this
def getStuff[A](...)(implicit ev: Foo[A]) = {
ev.bar(...)
}
Key concept to understand is that types do not exists at run-time because they get "erased" after compilation so there is not enough information to pattern match on types once the program is running. However at compile-time, that is before the program runs, types do exist and Scala provides means to ask the compiler to effectively pattern match on them via implicit/givens mechanism which looks something like so
// type class requirements for type A
trait StringConverter[A] {
def getOptValue(b: String): Option[A]
}
// evidence which types satisfy the type class
implicit val intStringConverter: StringConverter[Int] = (b: String) => b.toIntOption
implicit val strStringConverter: StringConverter[String] = (b: String) => Some(b)
implicit def anyStringConverter[A]: StringConverter[A] = (b: String) => None
// compile-time pattern matching on type A
def getStuff[A](b: String)(implicit ev: StringConverter[A]): Option[A] = {
ev.getOptValue(b)
}
getStuff[Int]("3") // : Option[Int] = Some(value = 3)
getStuff[String]("3") // : Option[String] = Some(value = "3")
getStuff[Double]("3") // : Option[Double] = None
This compile-time pattern matching is called type class pattern.
Understanding the distinction between types and classes is one of the fundamental concepts in Scala https://docs.scala-lang.org/tutorials/FAQ/index.html#whats-the-difference-between-types-and-classes and gorking it will help understand how to write type classes.
Using custom typeclass similar to Getter:
trait KeyedGetter[S, K, A]:
def get(s: S, key: K): Option[A]
case class MyObj(ints: Map[String, Int], strs: Map[String, String])
object MyObj:
given KeyedGetter[MyObj, String, Int] with
def get(m: MyObj, k: String) = m.ints.get(k)
given KeyedGetter[MyObj, String, String] with
def get(m: MyObj, k: String) = m.strs.get(k)
def getStuff[A](m: MyObj, key: String)(using g: KeyedGetter[MyObj, String, A]): Option[A] =
g.get(m, key)
Using class tags:
case class MyObj(ints: Map[String, Int], strs: Map[String, String])
import reflect._
def getStuff[A](m: MyObj, key: String)(using ct: ClassTag[A]): Option[A] = (ct match
case _ if ct == classTag[String] => m.strs.get(key)
case _ if ct == classTag[Int] => m.ints.get(key)
case _ => None
).asInstanceOf[Option[A]]
If the erased types are insufficient, for a similar approach with type tags see this answer (and ignore the rest).
I try to create a simple shapeless based function to convert the case class into a list of string I could then encode as csv.
The point of this is to summon type class CsvEncoder on every member of the case class and then invoke method encode to change it to string.
Type class:
trait CsvEncoder[T] {
def encode(t: T): String
}
trait LowPriorityEncoder {
implicit def genericEncoder[T]: CsvEncoder[T] = _.toString
}
object CsvEncoder extends LowPriorityEncoder {
implicit def optionEncoder[T](
implicit e: CsvEncoder[T]
): CsvEncoder[Option[T]] = _.fold("")(e.encode)
}
And shapeless:
class CsvBuilder[Row](rows: List[Row]) {
object csvMapper extends Poly1 {
implicit def caseEncode[V](
implicit e: CsvEncoder[V]
): Case.Aux[FieldType[Symbol, V], FieldType[Symbol, String]] =
at[FieldType[Symbol, V]](s => field[Symbol](e.encode(s)))
}
def build[Repr <: HList, OutRepr <: HList](
implicit labelledGeneric: LabelledGeneric.Aux[Row, Repr],
mapper: Mapper.Aux[csvMapper.type, Repr, OutRepr],
toMap: ToMap.Aux[OutRepr, Symbol, String]
): List[Map[String, String]] = {
def transform(row: Row): Map[String, String] = {
val formattedRows = labelledGeneric
.to(row)
.map(csvMapper)
val fields = toMap(formattedRows)
.map {
case (k: Symbol, value: String) =>
(k.toString -> value)
}
fields
}
rows.map(transform(_))
}
}
When I try to use with simple case class:
final case class ProjectCsvRow(
project: Option[String],
something: Option[String],
site: Option[String],
state: Option[Int]
)
new CsvBuilder[ProjectCsvRow](
List(ProjectCsvRow(Some(""), None, None, None))
).build
I'm getting
could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[stabilizer$1.csvMapper.type,Repr,OutRepr]
I think I'm missing something, but I can't really figure it out.
I already checked if there's instance of CsvEncoder for every field.
Firstly, your Poly is incorrect. Type Symbol is too rough. Keys of a record are not just Symbols, they are singleton subtypes of Symbol. You should define
object csvMapper extends Poly1 {
implicit def caseEncode[K <: Symbol, V](
implicit e: CsvEncoder[V]
): Case.Aux[FieldType[K, V], FieldType[K, String]] =
at[FieldType[K, V]](s => field[K](e.encode(s)))
}
Secondly, making a Poly nested into a class (rather than object) can be dangerous (it starts to depend on an instance of CsvBuilder and therefore on type Row). So move csvMapper outside CsvBuilder (so that its path is stable).
When I have a function in Scala:
def toString[T: Show](xs: T*): String = paths.map(_.show).mkString
And the following type class instances in scope:
implicit val showA: Show[MyTypeA]
implicit val showB: Show[MyTypeB]
I can use function toString in the following ways:
val a1: MyTypeA
val a2: MyTypeA
val stringA = toString(a1, a2)
val b1: MyTypeB
val b2: MyTypeB
val stringB = toString(b1, b2)
But I cannot call toString mixing parameters of type MyTypeA and MyTypeB:
// doesn't compile, T is inferred to be of type Any
toString(a1, b1)
Is it possible to redefine toString in such a way that it becomes possible to mix parameters of different types (but only for which a Show typeclass is available)?
Note that I am aware of the cats show interpolator which solves this specific example, but I'm looking for a solution which can be applied to different cases as well (e.g. toNumber).
I am also aware of circumventing the problem by calling .show on the parameters before passing them to the toString function, but I'm looking for a way to avoid this as it results in code duplication.
Example with shapeless:
object myToString extends ProductArgs { //ProductArgs allows changing variable number of arguments to HList
//polymorphic function to iterate over values of HList and change to a string using Show instances
object showMapper extends Poly1 {
implicit def caseShow[V](implicit show: Show[V]): Case.Aux[V, String] = {
at[V](v => show.show(v))
}
}
def applyProduct[ARepr <: HList](
l: ARepr
)(
implicit mapper: Mapper[showMapper.type, ARepr]
): String = l.map(showMapper).mkString("", "", "")
}
Now let's test it:
case class Test1(value: String)
case class Test2(value: String)
case class Test3(value: String)
implicit val show1: Show[Test1] = Show.show(_.value)
implicit val show2: Show[Test2] = Show.show(_.value)
println(myToString(Test1("a"), Test2("b"))) //"ab"
println(myToString(Test1("a"), Test2("b"), Test3("c"))) //won't compile since there's no instance of Show for Test3
By the way, I think toString is not the best name, because probably it can cause weird conflicts with toString from java.lang.Object.
If you don't want to mess with shapeless, another solution that comes to my mind is to just create functions with different arity:
def toString[A: Show](a: A): String = ???
def toString[A: Show, B: Show](a: A, b: B): String = ???
//etc
It's definitely cumbersome, but it might be the easiest way to solve your problem.
Here's one way to do it in Dotty (note that most of the Dotty-specific features used here are not necessary; they're just to make life easier, but being able to abstract over tuples of different arities is something you can't do (easily) in Scala 2):
opaque type Show[T] = T => String
opaque type ShowTuple[T <: Tuple] = T => String
object ShowTuple {
given ShowTuple[EmptyTuple] = _ => ""
given showTuple[H, T <: Tuple](using show: Show[H], showTail: ShowTuple[T]) as ShowTuple[H *: T] =
{ case h *: t => show(h) + "," + showTail(t) }
}
def multiToString[T <: Tuple](t: T)(using showTuple: ShowTuple[T]) =
showTuple(t)
It can be used like this:
class TypeA(val i: Int)
class TypeB(val s: String)
class TypeC(val b: Boolean)
given Show[TypeA] = t => s"TypeA(${t.i})"
given Show[TypeB] = t => s"TypeB(${t.s})"
given Show[TypeC] = t => s"TypeC(${t.b})"
println(multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true))))
Using a type for which an implicit is not given fails:
class TypeD
multiToString((new TypeA(10), new TypeB("foo"), new TypeC(true), new TypeD))
Try it in Scastie
What is the type of paths?
If it's List[T] then there should be an implicit Show[T] in scope.
If it's List[Any] then there should be an implicit Show[Any] in scope.
If paths contains elements of different types and paths is not a List[Any] then paths shouldn't be a List[...] at all. It can be of type L <: HList. You can try
import shapeless.{HList, HNil, Poly1, Poly2}
import shapeless.ops.hlist.{LeftReducer, Mapper}
trait Show[T] {
def show(t: T): String
}
implicit class ShowOps[T](t: T) {
def show(implicit s: Show[T]): String = s.show(t)
}
object show extends Poly1 {
implicit def cse[T: Show]: Case.Aux[T, String] = at(_.show)
}
object concat extends Poly2 {
implicit def cse: Case.Aux[String, String, String] = at(_ + _)
}
def toString[L <: HList, L1 <: HList](xs: L)(implicit
mapper: Mapper.Aux[show.type, L, L1],
reducer: LeftReducer.Aux[L1, concat.type, String]
): String = xs.map(show).reduceLeft(concat)
type MyTypeA
type MyTypeB
implicit val showA: Show[MyTypeA] = ???
implicit val showB: Show[MyTypeB] = ???
val a1: MyTypeA = ???
val b1: MyTypeB = ???
toString(a1 :: b1 :: HNil)
I'm reading in query parameters and converting them into a Map[Symbol, String]. I would like to add some type safety to these query parameters through a set of case classes.
These case classes will be different depending on the incoming http request, so this needs to support different case classes.
If the incoming query parameters don't match the defined case class the Parser should return None.
I have attempted to use shapeless to implement a generic parser. It work's if all of the parameters are of type String. But I need to support any type of query parameter.
I've tried to incorporate the implicit conversion logic seen in this post, but unable to get it working.
https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/ (new to shapeless)
Existing Parser (without string to type conversion):
class Parser[A] {
def from[R <: HList]
(m: Map[Symbol, String])
(implicit
gen: LabelledGeneric.Aux[A, R],
fromMap: FromMap[R]
): Option[A] = fromMap(m).map(gen.from)
}
object Parser {
def to[A]: Parser[A] = new Parser[A]
}
Tests describing issue:
class ParserSpec extends FlatSpec with Matchers {
private val sampleName: String = "Bob"
private val sampleVersion: Int = 1
//Partial Solution
case class QueryParams(name: String, version: String)
//Full Solution (not working)
case class QueryParams2(name: String, version: Int)
"A Parser" should "parse query parameters from a map with only string values" in {
val mapOfQueryParams = Map('name -> sampleName, 'version -> sampleVersion.toString)
val result = Parser.to[QueryParams].from(mapOfQueryParams)
result shouldBe 'defined
result.get.name shouldEqual sampleName
result.get.version shouldEqual sampleVersion.toString
}
it should "parse query parameters from a map with any type of value" in {
val mapOfQueryParams = Map('name -> sampleName, 'version -> sampleVersion.toString)
val result = Parser.to[QueryParams2].from(mapOfQueryParams)
//result is not defined as it's not able to convert a string to integer
result shouldBe 'defined
result.get.name shouldEqual sampleName
result.get.version shouldEqual sampleVersion
}
}
FromMap uses shapeless.Typeable to convert values to the expected type. So the easiest way to make your code work is to define an instance of Typeable to convert from String to Int (and additional Typeable instances for any value type, that appears in your case classes):
implicit val stringToInt: Typeable[Int] = new Typeable[Int] {
override def cast(t: Any): Option[Int] = t match {
case t: String => Try(t.toInt).toOption
case _ => Typeable.intTypeable.cast(t)
}
override def describe: String = "Int from String"
}
This is however not an intended use of Typeable, which is designed to confirm that a variable with type Any is already an instance of the expected type without any conversion. In other words it's intended to be a typesafe implementation of asInstanceOf, that can also work around type erasure.
For correctness you can define your own ReadFromMap typeclass, that uses your own Read typeclass for conversion from Strings to the expected types. Here is a simple implementation of the Read typeclass (assuming Scala 2.12):
import scala.util.Try
trait Read[T] {
def apply(string: String): Option[T]
}
object Read {
implicit val readString: Read[String] = Some(_)
implicit val readInt: Read[Int] = s => Try(s.toInt).toOption
// Add more implicits for other types in your case classes
}
And you can copy and adapt the implementation of FromMap to use this Read typeclass:
import shapeless._
import shapeless.labelled._
trait ReadFromMap[R <: HList] extends Serializable {
def apply(map: Map[Symbol, String]): Option[R]
}
object ReadFromMap {
implicit def hnil: ReadFromMap[HNil] = _ => Some(HNil)
implicit def hlist[K <: Symbol, V, T <: HList](implicit
keyWitness: Witness.Aux[K],
readValue: Read[V],
readRest: ReadFromMap[T]
): ReadFromMap[FieldType[K, V] :: T] = map => for {
value <- map.get(keyWitness.value)
converted <- readValue(value)
rest <- readRest(map)
} yield field[K](converted) :: rest
}
Then simply use this new typeclass in your Parser:
class Parser[A] {
def from[R <: HList]
(m: Map[Symbol, String])
(implicit
gen: LabelledGeneric.Aux[A, R],
fromMap: ReadFromMap[R]
): Option[A] = fromMap(m).map(gen.from)
}
Why does the following code:
case class Test[E](name: String, e: E)
val seq = Seq[Test[_]]()
val map = seq.map(i => i.name -> i).toMap
Give me this compiler error:
Cannot prove that (String, Test[_$1]) forSome { type _$1 } <:< (T, U)
Your two options are:
case class Test[E](name: String, e: E)
val seq = Seq[Test[_]]()
val map = seq.map(i => i.name -> i).toMap[String, Test[_]]
Or you can also "inform" the compiler about the map type ahead of time which also works.
val seq = Seq.empty[Test[_]]
val map: Map[String, Test[_]] = seq.map(i => i.name -> i).toMap
Why this doesn't work
As to why this doesn't work, it really should, but what happens is Scala uses an implicit to determine the inner type of a collection M[X] <: TraversableOnce[X] is a tuple, and that's found in the toMap signature.
def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U]
The part you care about is A <:< (T, U), which is saying A is actually a subtype of Tuple2[T, U] where T should become the keytype and U the value type. A is the original underlying type of the collection.
Somehow the compiler is not smart enough to infer (T, U) unless you explicitly provided, and it seems it doesn't have anything to do with the existential type either.
In theory you should be able to convert any traversable to a map easily, and we can reconstruct what happens when you call toMap using higher kinds.
implicit class TraversableOps[
// We are saying here every M is a higher kinded collection type
// that extends TraversableOnce, like Map, Seq, and so on.
M[A] <: TraversableOnce[A],
T
](val col: M[T]) {
// Proof that T is actually a tuple2 type.
def customToMap[K, V](implicit ev: T <:< (K, V)): Map[K, V] = {
...
}
}