Given:
import shapeless._
case class F(x: Option[Int], y: Option[Int])
I'd like help to write a function, f:
def f(Option[Int] :: Option[Int] :: HNil): String
such that each Option[Int] is replace with the Some number or empty; and "" for HNil.
Example:
val res7 = Generic[F].to( F( Some(42), None) )
//res7: shapeless.::[Option[Int],shapeless.::
[Option[Int],shapeless.HNil]] = Some(42) :: None :: HNil
f(res7) === "42empty"
How can f be written?
You need a Poly:
object OptFolder extends Poly2{
def conv(x: Option[Int]) = x.map(_.toString).getOrElse("empty")
implicit val ff = at{ (y: String, z: Option[Int]) => y + conv(z) }
}
val lala: String = myHlist.foldLeft("")(OptFolder) //:String not required
So a Generic to transform to the HList and then a foldLeft with a well defined Poly.
Related
Scala 3 provides polymorphic functions and Tuples similar to shapeless HList:
scala> 1 *: "foo" *: Tuple()
val res0: (Int, String) = (1,foo)
scala> val f: ([T] => T => Option[T]) = [T] => (v: T) => Some(v)
val f: PolyFunction{apply: [T](x$1: T): Option[T]} = <function1>
scala> res0.map(f)
val res1: Option[Int] *: Option[String] *: EmptyTuple = (Some(1),Some(foo))
How could we reimplement the following shapeless example using Scala 3 functionality?
import poly._
object choose extends (Set ~> Option) {
def apply[T](s : Set[T]) = s.headOption
}
scala> val sets = Set(1) :: Set("foo") :: HNil
sets: Set[Int] :: Set[String] :: HNil = Set(1) :: Set(foo) :: HNil
scala> val opts = sets map choose
opts: Option[Int] :: Option[String] :: HNil = Some(1) :: Some(foo) :: HNil
In other words, how could we make something like so compile
scala> val choose: ([T] => Set[T] => Option[T]) = [T] => (s: Set[T]) => s.headOption
val choose: PolyFunction{apply: [T](x$1: Set[T]): Option[T]} = <function1>
scala> val sets = Set(1) *: Set("foo") *: Tuple()
val sets: (Set[Int], Set[String]) = (Set(1),Set(foo))
scala> sets.map(choose)
1 |sets.map(choose)
| ^^^^^^
| Found: (choose : PolyFunction{apply: [T](x$1: Set[T]): Option[T]})
| Required: PolyFunction{apply: [t](x$1: t): Any}
Shapeless map is quite a bit more magical than Scala 3 tuple map, the signature of latter being:
def map[F[_]](f: [t] => (x$1: t) => F[t]): Map[Tuple, F]
Map[Tuple, F] is a special match type that is basically tuple with every argument type wrapped in F[_]. The issue is that shape, t => F[t] that prevents too much fanciness.
Except of course, F can be a match type itself:
type Choose[T] = T match {
case Set[a] => Option[a]
}
def choose[T](t: T): Choose[T] = t match
case set: Set[a] => set.headOption
// messy, but it works
#main def run =
val sets = Set(1) *: Set("foo") *: Tuple()
println(sets.map([T] => (t: T) => choose(t)))
There's currently an issue that compiler won't infer polymorphic function from methods. Match types aren't fully type-safe either, e.g. doing choose("string") will compile but throw a MatchError. I've also ran into problems with inference of a match type from polymorphic function value, hence the usage of def method.
I'd like to be able to apply an arbitrary list of Function1[I, ?]s to an arbitrary input I. This is what I have so far:
type StringInputFunction[T] = Function[String, T]
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
val expected = 4 :: "evif" :: HNil
object applyTo5 extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f("five")
}
def applyFunctionsTo5[FH <: HList, OH <: HList](fs: FH)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[applyTo5.type, FH, OH]): mapper.Out = {
fs.map(applyTo5)
}
applyFunctionsTo5(functions) shouldBe expected
class ApplyTo(string: String) extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f(string)
}
def applyFunctionsTo[FH <: HList, OH <: HList]
(fs: FH, input: String)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[ApplyTo, FH, OH]): mapper.Out = {
val applyTo = new ApplyTo(input)
fs.map(applyTo)
}
applyFunctionsTo(functions, "five") shouldBe expected
This results in the compile error:
ShapelessSpec.scala:81: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[applyTo.type,FH]
fs.map(applyTo)
ShapelessSpec.scala:83: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[ApplyTo,shapeless.::[String => Int,shapeless.::[String => String,shapeless.HNil]],OH]
applyFunctionsTo(functions, "five") shouldBe expected
How can I fix this to work with any String input?
Can I change genericise this even further to work with any input type T?
I was thinking I'd done this exact operation before, and was able to find this gist from a few years ago. To summarize my example there, you can do this pretty nicely using only operations that are already provided by Shapeless, in a manner that looks a lot like how you'd do something like this with ordinary lists at the value level. Suppose you have the following setup:
import shapeless.{ ::, HNil }
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
Then you can write this:
scala> functions.zipApply(functions.mapConst("five"))
res0: Int :: String :: shapeless.HNil = 4 :: evif :: HNil
Or this:
scala> def foo(in: String) = functions.zipApply(functions.mapConst(in))
foo: (in: String)Int :: String :: shapeless.HNil
scala> foo("six")
res1: Int :: String :: shapeless.HNil = 3 :: xis :: HNil
This will work with any hlists of functions from a particular type applied to that particular type.
The gist gives a few alternative approaches, but zipApply plus mapConst feels by far the best to me.
As an exercise, I am trying to see if I can take a List[Any] and "cast" it into a case class using shapeless.
A very basic example of what I am trying to achieve:
case class Foo(i: Int, j: String)
val foo: Option[Foo] = fromListToCaseClass[Foo]( List(1:Any, "hi":Any) )
Here is how I am shaping my solution (this can be quite off):
def fromListToCaseClass[CC <: Product](a: List[Any]): Option[CC] = a.toHList[???].map( x => Generic[CC].from(x) )
Here is my reasoning:
I know that you can go from a case class to an HList[T] (CC -> HList[T]); where T is the type of the HList. I also know that you can create an HList from a list (list -> Option[HList]) as long as you know the type of the HList. Finally I know that you can go from an HList to a case class (HList -> CC).
CC -> HList[T]
list -> Option[HList[T]] -> Option[CC]
I am wondering if this makes sense or if I am way off here. Can we make this work? Any other suggestions? Thanks!
This can be done very straightforwardly using shapeless's Generic and FromTraversable type classes,
import scala.collection.GenTraversable
import shapeless._, ops.traversable.FromTraversable
class FromListToCaseClass[T] {
def apply[R <: HList](l: GenTraversable[_])
(implicit gen: Generic.Aux[T, R], tl: FromTraversable[R]): Option[T] =
tl(l).map(gen.from)
}
def fromListToCaseClass[T] = new FromListToCaseClass[T]
(There's some accidental complexity here due to Scala's awkwardness when it comes to mixing explicit and inferred type parameters: we want to specify T explicitly, but have R inferred for us).
Sample REPL session ...
scala> case class Foo(i: Int, j: String)
defined class Foo
scala> fromListToCaseClass[Foo](List(23, "foo"))
res0: Option[Foo] = Some(Foo(23,foo))
scala> fromListToCaseClass[Foo](List(23, false))
res1: Option[Foo] = None
You can do it with shapeless the following way:
import shapeless._
trait Creator[A] { def apply(list:List[Any]): Option[A] }
object Creator {
def as[A](list: List[Any])(implicit c: Creator[A]): Option[A] = c(list)
def instance[A](parse: List[Any] => Option[A]): Creator[A] = new Creator[A] {
def apply(list:List[Any]): Option[A] = parse(list)
}
def arbitraryCreate[A] = instance(list => list.headOption.map(_.asInstanceOf[A]))
implicit val stringCreate = arbitraryCreate[String]
implicit val intCreate = arbitraryCreate[Int]
implicit val hnilCreate = instance(s => if (s.isEmpty) Some(HNil) else None)
implicit def hconsCreate[H: Creator, T <: HList: Creator]: Creator[H :: T] =
instance {
case Nil => None
case list => for {
h <- as[H](list)
t <- as[T](list.tail)
} yield h :: t
}
implicit def caseClassCreate[C, R <: HList](
implicit gen: Generic.Aux[C, R],
rc: Creator[R]): Creator[C] =
instance(s => rc(s).map(gen.from))
}
And
val foo:Option[Foo] = Creator.as[Foo](List(1, "hi"))
Suppose I have a few functions:
val f1: Int => String
val f2: (Int, Int) => String
val f3: (Int, Int, Int) => String
def fromList1(f: Int => String): List[Int] => Option[String] =
_ match {case x::_ => Some(f(x)); case _ => None}
def fromList2(f: (Int, Int) => String): List[Int] => Option[String] =
_ match {case x::y::_ => Some(f(x, y)); case _ => None}
Now I would like to write one generic fromList to work as follows:
val g1: List[Int] => String = fromList(f1) // as fromList1(f1)
val g2: List[Int] => String = fromList(f2) // as fromList2(f2)
Can I do that with shapeless ?
This may help:
import shapeless._
import syntax.std.traversable._
import shapeless.ops.traversable._
import syntax.std.function._
import ops.function._
def fromList[F, L <: HList, R](f: F)
(implicit fp: FnToProduct.Aux[F, L => R], tr: FromTraversable[L]) =
(p: List[Int]) => p.toHList[L] map f.toProduct
f.toProduct transforms regular function to function that takes HList as parameter - it requires FnToProduct implicit and actually just call it. FnToProduct.Aux is constructor (generated by macro) that creates FnToProduct from dunction F, hlist type HList and result type R. All of them are inferred from f parameter you passed.
Last one, toHList creates Some(HList) from regular List if it's possible, otherwise - None. It uses FromTraversable[L] implicit to do that, where L is already inferred from f. Shapeless2 is smart enough to recognize HList from Tuple (as there probably is implicit conversion).
Example:
scala> val f1: Int => String = _ => "a"
f1: Int => String = <function1>
scala> val f2: (Int, Int) => String = (_, _) => "a"
f2: (Int, Int) => String = <function2>
scala> val g1 = fromList(f1)
g1: List[Int] => Option[String] = <function1>
scala> g1(List(1))
res6: Option[String] = Some(a)
scala> val g2 = fromList(f2)
g2: List[Int] => Option[String] = <function1>
scala> g2(List(1, 2))
res7: Option[String] = Some(a)
scala> g2(List(1))
res8: Option[String] = None
Yes you can
import shapeless._
import shapeless.ops.traversable._
import syntax.std.traversable._
import ops.function._
def fromList[F, I <: HList, O](f: F)(implicit
ftp: FnToProduct.Aux[F, I => O],
ft: shapeless.ops.traversable.FromTraversable[I]): List[Int] => Option[O] =
{ x: List[Int] => x.toHList[I].map(ftp(f)) }
Explanation
We're using FnToProduct to transform any FunctionN to a Function1 that takes an HList as only argument.
So,
Int => String ----> Int :: HNil => String
(Int, Int) => String ----> Int :: Int :: HNil => String
...
Now that we abstracted over the arity of the input parameters for the function, we can simply convert the List[Int] to an HList that suits the transformed function's input.
In order to perform this conversion we need to a FromTraversable[I] in scope.
If everything succeeds we return and Option[O] where O is the return type of the function.
If the input List has the wrong shape, we simply fail returning None.
Usage
# val f1: Int => String = _.toString
f1: Int => String = <function1>
# val f2: (Int, Int) => String = (_, _).toString
f2: (Int, Int) => String = <function2>
# val fromList1 = fromList(f1)
fromList1: List[Int] => Option[String] = <function1>
# val fromList2 = fromList(f2)
fromList2: List[Int] => Option[String] = <function1>
# fromList1(List(1))
res22: Option[String] = Some(1)
# fromList2(List(1, 2))
res23: Option[String] = Some((1,2))
# fromList1(List())
res24: Option[String] = None
When I'm matching value of case classes, such as:
sealed abstract class Op
case class UOp[T, K](f: T => K) extends Op
case class BOp[T, Z, K](f: (T, Z) => K) extends Op
like this:
def f(op: Op): Int =
op match
{
case BOp(g) => g(1,2)
case UOp(g) => g(0)
}
the compiler infers it as
val g: (Nothing, Nothing) => Any
val g: Nothing => Any
Why am I getting Nothing as the type? Is it because of JVM type erasure? Are there elegant ways to match functions against variables?
I came up with this "hackish" solution, maybe there are other ways or cleaner ways to do this still without relying on reflection.
Define a few partial functions which will handle various args:
scala> val f: PartialFunction[Any, String] = { case (x: Int, y: String) => y * x }
f: PartialFunction[Any,String] = <function1>
scala> val g: PartialFunction[Any, String] = { case x: Int => x.toString }
g: PartialFunction[Any,String] = <function1>
scala> def h: PartialFunction[Any, BigDecimal] = { case (a: Int, b: Double, c: Long) => BigDecimal(a) + b + c }
h: PartialFunction[Any,BigDecimal]
scala> val l: List[PartialFunction[Any, Any]] = f :: g :: h :: Nil
l: List[PartialFunction[Any,Any]] = List(<function1>, <function1>, <function1>)
Check which functions can handle different inputs:
scala> l.map(_.isDefinedAt(1))
res0: List[Boolean] = List(false, true, false)
scala> l.map(_.isDefinedAt((1, "one")))
res1: List[Boolean] = List(true, false, false)
Given input find and apply a function:
scala> def applyFunction(input: Any): Option[Any] = {
| l find (_.isDefinedAt(input)) map (_ (input))
| }
applyFunction: (input: Any)Option[Any]
scala> applyFunction(1)
res1: Option[Any] = Some(1)
scala> applyFunction((2, "one"))
res2: Option[Any] = Some(oneone)
scala> applyFunction("one")
res3: Option[Any] = None
scala> applyFunction(1, 1.1, 9L)
res10: Option[Any] = Some(11.1)
This looks quite type unsafe and there must be better ways to do this.
I think magnet pattern should handle this well in more typesafe manner.