Select field by type - scala

I am trying to create a type class that allows to select a field for a given type. This is what I have done so far but the compiler is unable to find the Selector.Aux
case class AddressKey(street: String, city: String)
trait AddressKeySelector[A] {
def addressKey(v: A): AddressKey
def addressKeyColumnName: String
}
object AddressKeySelector {
implicit def typeAddressKeySelector[A, Repr <: HList, K](
implicit gen: Generic.Aux[A, Repr],
reprSelector: Selector.Aux[Repr, K, AddressKey]): AddressKeySelector[A] =
new AddressKeySelector[A] {
override def addressKey(v: A): AddressKey = reprSelector(gen.to(v))
override def addressKeyColumnName: String = ???
}
}
It will be used like this
case class MyType(addressKeyField: AddressKey, otherField: String)
val data = MyType(AddressKey("addr", "city"), "other")
val addrKey = AddressKeySelector[MyType].addressKey(data)
// addrKey: AddressKey = AddressKey(addr,city)
val addrField = AddressKeySelector[MyType].addressKeyColumnName(data)
// addrField: String = addressKeyField
This should just work when there is one and only one field with type AddressKey. Any idea on how to implement it?

ops.hlist.Selector doesn't have an Aux, so I'm assuming you're using that one. Seeing as you want to extract the field name you probably want to use LabelledGeneric and ops.record.Selector instead. But then Selector.Aux[Repr, K, AddressKey] would still not work because Selector can only search by "key" (K) and not by "value" (AddressKey) while you are searching for the key by the value.
Ideally I think you would implement it like this:
import shapeless._, ops.record._
trait AddressKeySelector[A] {
def addressKey(v: A): AddressKey
def addressKeyColumnName: String
}
object AddressKeySelector {
def apply[A](implicit sel: AddressKeySelector[A]): sel.type = sel
implicit def typeAddressKeySelector[A, Repr <: HList, K <: Symbol, Swapped <: HList](
implicit
gen: LabelledGeneric.Aux[A, Repr],
swap: SwapRecord.Aux[Repr, Swapped],
swappedSelector: Selector.Aux[Swapped, AddressKey, K],
reprSelector: Selector.Aux[Repr, K, AddressKey]
): AddressKeySelector[A] =
new AddressKeySelector[A] {
override def addressKey(v: A): AddressKey = reprSelector(gen.to(v))
override def addressKeyColumnName: String = swappedSelector(swap()).name
}
}
But for some reason that doesn't work.
So I think your best bet is implementing the search by value yourself. It's relatively straightforward. You can take inspiration from the ops.hlist.Selector. Keeping in mind that every entry in the Hlist that is emitted by LabelledGeneric is encoded as labelled.FieldType[Key,Value] and that you can get the runtime value of Key (or in other words: the field name) with Witness.Aux[Key].

Related

Could not find implicit value for parameter Mapper

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).

Achieving Ad hoc polymorphism at function parameter level (mixing parameters of different type)

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)

Mapped types in Scala

Is there a way to derive a type from an existing one in Scala?
For example, for case class Person(name: String, age: Int) I'd like to get a Product/Tuple of (Option[String], Option[Int]), i.e. a type mapped from an existing one.
There's a feature in Typescript (mapped types) that allows this relatively easily, which is how I started thinking down this path. But I'm not sure how something like this would be done in Scala.
I feel like the solution involves using shapeless in some way but I'm not sure how to get there.
I'd suggest parameterize the type as follows:
case class Person[F[_]](name: F[String], age: F[Int])
And then you can derive types you want, like
import cats.Id
type IdPerson = Person[Id]
type OptPerson = Person[Option]
Where cats.Id is simply defined as type Id[A] = A. It is straightforward to write your own, but I suggest using cats' one since it comes with useful typeclass instances.
With Shapeless you can define type class
import shapeless.ops.{hlist, product, tuple}
import shapeless.poly.~>
import shapeless.{Generic, HList, Id, the}
trait Partial[A] {
type Out
}
object Partial {
type Aux[A, Out0] = Partial[A] { type Out = Out0 }
object optionPoly extends (Id ~> Option) {
override def apply[T](t: T): Option[T] = null
}
// implicit def mkPartial[A, L <: HList, L1 <: HList](implicit
// generic: Generic.Aux[A, L],
// mapper: hlist.Mapper.Aux[optionPoly.type, L, L1],
// tupler: hlist.Tupler[L1]): Aux[A, tupler.Out] = null
implicit def mkPartial[A, T](implicit
toTuple: product.ToTuple.Aux[A, T],
mapper: tuple.Mapper[T, optionPoly.type],
): Aux[A, mapper.Out] = null
}
and use it (the is improved version of implicitly)
case class Person(name: String, age: Int)
// val pp = the[Partial[Person]]
// type PersonPartial = pp.Out
type PersonPartial = the.`Partial[Person]`.Out
implicitly[PersonPartial =:= (Option[String], Option[Int])]

Scala: upper bound [T <: AnyRef] does not allow to return [AnyRef]

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

Shapeless: How to express <: case class type param for Generix.Aux

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)
}