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).
Related
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)
}
I'm trying to express the following idea:
Function caseClassFields should return an array of (String, T) pairs, by processing a case class.
I put upper bound for T, expecting that it should be a subtype of AnyRef or AnyRef itself.
Here is a function:
def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, T)] = {
val metaClass = obj.getClass
metaClass.getDeclaredFields.map {
field => {
field.setAccessible(true)
(field.getName, field.get(obj))
}
}
}
But unfortunately I get following error:
Expression of type Array[(String, AnyRef)] doesn't conform to expected type Array[(String, T)]
How to fix this?
Doing what you want with reflection and keeping type safety are orthogonal requirements. But shapeless, a library for generic derivation, can do what you want and still keep you type safe.
Here's a short example using shapeless to get you started.
We first define our algebra:
sealed trait ValidatableField
case class ValidatableString(value: Boolean)
extends ValidatableField
case class ValidatableInt(value: Boolean) extends ValidatableField
case class ValidatableRecord(fields: List[(String, ValidatableField)])
extends ValidatableField
Now we define our validator trait:
trait Validator[T] {
def validate(value: T): ValidatableField
}
trait RecordValidator[T] extends Validator[T] {
def validate(value: T): ValidatableRecord
}
Now lets define, for the sake of the example, validation on Int and String:
implicit val intValidator = new Validator[Int] {
override def validate(t: Int): ValidatableField = ValidatableInt(t > 42)
}
implicit val stringValidator = new Validator[String] {
override def validate(t: String): ValidatableField = ValidatableString(t.length < 42)
}
Now we define a generic implementation for HList which will cover our ValidatableRecord which is the generic representation of our case class:
implicit val hnilEncoder: RecordValidator[HNil] = new RecordValidator[HNil] {
override def validate(value: HNil): ValidatableRecord = ValidatableRecord(Nil)
}
implicit def hlistValidator[K <: Symbol, H, T <: HList](
implicit witness: Witness.Aux[K],
hEncoder: Lazy[Validator[H]],
tEncoder: RecordValidator[T]
): RecordValidator[FieldType[K, H] :: T] = {
val fieldName = witness.value.name
new RecordValidator[::[FieldType[K, H], T]] {
override def validate(value: ::[FieldType[K, H], T]): ValidatableRecord = {
val head = hEncoder.value.validate(value.head)
val tail = tEncoder.validate(value.tail)
ValidatableRecord((fieldName, head) :: tail.fields)
}
}
}
implicit def genericEncoder[A, H <: HList](
implicit generic: LabelledGeneric.Aux[A, H],
hEncoder: Lazy[RecordValidator[H]]): Validator[A] = {
new RecordValidator[A] {
override def validate(value: A): ValidatableRecord =
hEncoder.value.validate(generic.to(value))
}
}
With this much code, we can now validate any case class which has a String and Int field in it, and it is trivial to add other validator for more primitives:
object Test {
def main(args: Array[String]): Unit = {
case class Foo(s: String, i: Int)
val foo = Foo("hello!", 42)
println(Validator[Foo].validate(foo))
}
}
Yields:
ValidatableRecord(List((s,ValidatableString(true)), (i,ValidatableInt(false))))
I know this can be overwhelming a bit, but David Gurnells "Guide To Shapeless" is a great place to get started.
The reason is field.get(obj) returns AnyRef while your return type is T. Therefore, you need to convert it into T. However, I don't see any use of Generic type T in your code, so you can simply change the return type to Array[(String, AnyRef)].
def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, AnyRef)]
However, if you insist to use Generic, you need to convert field.get(obj) to type T. Note, that you might get exception in case of invalid type while converting to type T.
def caseClassFields[T <: AnyRef](obj: AnyRef): Array[(String, T)] = {
val metaClass = obj.getClass
metaClass.getDeclaredFields.map {
field => {
field.setAccessible(true)
(field.getName, field.get(obj).asInstanceOf[T])
}
}
}
case class Foo(name:String)
val result:Array[(String, String)] = caseClassFields[String](Foo("bar"))
As discussed in the comments you probably want to use shapeless, but to elaborate.
The method field.get() isnt related to the type parameter. Normally you would use a type parameter like this
def caseClassFields[T <: AnyRef](obj: T): Array[(String, T)] = ???
or this ..
def caseClassFields[T <: AnyRef](obj: Container[T]): Array[(String, T)] = ???
If there was some link between field.get and T it could work, but the relationship would need to be proven to the compiler. As the T can be anything the compiler cant prove anything about it.
I can strongly recommend this book the 'Type Astronaut’s Guide to Shapeless' as a intro to the topic.
http://underscore.io/books/shapeless-guide/
https://github.com/milessabin/shapeless
I've got the following code to basically iterate through the fields of a case class and map them using a Poly to the same type and use ToList[HL, Out]
For simplicity we can assume the Poly does this:
object Schema extends Poly1 {
implicit def caseInt = at[Int](_ => "I'm an int")
implicit def caseString = at[String](_ => "Oh boy a string")
}
def infer[V1 <: Product, Out <: HList, MapperOut <: HList](v1: V1)(
implicit gen: Generic.Aux[V1, Out],
map: Mapper.Aux[Schema.type, Out, MapperOut],
to: ToList[MapperOut, String]
): List[String] = to (gen to v1 map Schema)
This is all very straightforward and works very well for simple scenarios:
case class Test(id: Int, text: String)
val list = infer(Test(2, "text"))
// List("I'm an int", "Oh boy a string")
Now going out to where the buses don't run:
class Automagical[T <: Product with Serializable : TypeTag] {
def instance: T
// The typetag bit is needed for something else
def convert: List[String] = infer(instance)
}
Sadly any call to above fails with:
could not find implicit value for parameter gen: shapeless.Generic.Aux[T,Out]
Bonus
How can I improve the infer method by not requiring an instance of T at all? Obviously type inference is fine, but I do need to somehow materialise a List[String] from an HList[Lub] and map over something.
Given I only ever care about the types, is it possible to derive a concrete instance of List[String] by only knowing the types to be poly mapped encoded as an HList?
Something like:
def infer[V1 <: Product, Out <: HList, MapperOut <: HList]()(
implicit gen: Generic.Aux[V1, Out],
map: Mapper.Aux[Schema.type, Out, MapperOut],
to: ToList[MapperOut, String]
): List[String] = {
// Magically build an HList with samples from its types.
// Or an alternative approach that gives the same outcome
val reifiedInstance = reify[Out]
to (reifiedInstance map Schema)
}
To ensure that convert has access to the implicit parameters for infer, they have to be present when the Automagical instance is created:
abstract class Automagical[T <: Product with Serializable : TypeTag,
Out <: HList, MapperOut <: HList]
(implicit
gen: Generic.Aux[T, Out],
map: Mapper.Aux[Schema.type, Out, MapperOut],
to: ToList[MapperOut, String]) {
def instance: T
// The typetag bit is needed for something else
def convert: List[String] = infer(instance)
}
This is continuation of Diverging implicit expansion for type class. I've came up with another version that still doesn't compile, but now for another reason, hence a different question. Here's what I did:
I've basically added a new typeclass NestedParser for the cases where some case class consists of other case classes.
trait Parser[A] {
def apply(s: String): Option[A]
}
trait NestedParser[A] {
def apply(s: String): Option[A]
}
object Parser {
def apply[A](s: String)(implicit parser: Parser[A]): Option[A] = parser(s)
}
object NestedParser {
def apply[A](s: String)(implicit parser: NestedParser[A]): Option[A] = parser(s)
}
I've changed the previous implicit function to return the NestedParser to avoid diverging implicit expansions. Otherwise it's same as before:
implicit def nestedCaseClassParser[A, B, C]
(
implicit pb: Parser[B],
pc: Parser[C],
gen: Generic.Aux[A, B :: C :: HNil]
): NestedParser[A] = new NestedParser[A] {
override def apply(s: String): Option[A] = {
val tmp = s.span(_ != '|') match {
case (h, t) =>
for {
a <- pb(h)
b <- pc(t.substring(1))
} yield a :: b :: HNil
}
tmp.map(gen.from)
}
}
Case classes are same as before:
case class Person(name: String, age: Int)
case class Family(husband: Person, wife: Person)
Now when I try to parse the Family, I get the following compile error:
scala> NestedParser[Family]("")
<console>:32: error: could not find implicit value for parameter parser: NestedParser[Family]
NestedParser[Family]("")
However, it doesn't make sense to me. The function above clearly provides the implicit instance of NestedParser. Why doesn't it satisfy the compiler?
Well, as far as I can see you are not providing any implicit Parser instances which nestedCaseClassParser requires.