I am trying to apply a partial function to a generic collection while returning a specific collection instead of Traversable of the original type.
Here's what I've tried (among other things):
def filter[A, B, C <: Traversable[A],Y<:B, D <: Traversable[Y]](xs: Option[D] with TraversableLike[A,Option[D]])(pf:PartialFunction[A,B]): D = {
xs.collect(pf)
}
def filter[A, B, C <: Traversable[A], D <: Traversable[B]](xs: D with TraversableLike[A,D])(pf:PartialFunction[A,B])(implicit ev: B =:= Option[Double]): D = {
xs.collect(pf)
}
Any ideas why implicit ev: B =:= Option[Double] doesn't find the correct types?
I got inspiration from these posts:
Returning original collection type in generic method
How do I apply the enrich-my-library pattern to Scala collections?
Writing my own generic map functioni
You can do this:
import scala.collection.Iterable
import scala.collection.generic.CanBuildFrom
def collect[C[_], A, B](col: C[A])
(pf: PartialFunction[A, B])
(implicit ev: C[A] <:< Iterable[A],
cbf: CanBuildFrom[Nothing, B, C[B]]): C[B] =
col.iterator.collect(pf).to[C]
Related
I have an Animal trait and a few case classes as follows
sealed trait Animal
trait Domestic extends Animal
trait Wild extends Animal
case class Dog(id: UUID = UUID.randomUUID()) extends Domestic
case class Lion(id: UUID = UUID.randomUUID()) extends Wild
Here is my Herd class which can contain a list of a single type of animals
case class Herd[T <: Animal](get: T*)
I want to create is a herd of a single type of animal.
val herd1 = Herd(Cat(), Cat())
val herd2 = Herd(Cat(), Lion())
In Scala both are valid, but if you look at the meaning a herd of cats and lions it doesn't make sense. Is there a way to restrict Herd to be a single type?
Try introducing two type parameters A and B and then relate them with a generalised type constraint A =:= B
case class Herd[A <: Animal, B](x: A, y: B*)(implicit ev: A =:= B)
Herd(Lion()) // ok
Herd(Lion(), Lion()) // ok
Herd(Cat(), Lion()) // compile-time error
what exactly is =:=
Consider the following method with two type parameters A and B where we aim to convey that they should be equal or at least A should be a subtype of B
scala> def f[A, B](a: A, b: B): B = {
| val x: B = a
| x
| }
val x: B = a
^
On line 2: error: type mismatch;
found : a.type (with underlying type A)
required: B
The two type parameters are totally unrelated in the above definition and the method body cannot influence the type inference of type parameters so it errors. Now let's try to relate them with a type bound A <: B
scala> def f[A <: B, B](a: A, b: B): B = {
| val x: B = a
| x
| }
def f[A <: B, B](a: A, b: B): B
So this compiles, however compiler will always try to satisfy the type bounds by calculating the least upper bound of the given arguments
scala> f(Lion(), Dog())
val res32: Product with Animal with java.io.Serializable = Lion(...)
We need something more to get around compiler's tendency to deduce a least upper bound, and this is where generalised type equality constraint comes into play
scala> def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
| val x: B = a
| x
| }
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B
scala> f(Lion(), Cat())
^
error: Cannot prove that Lion =:= Product with Animal with java.io.Serializable.
Now compiler still has to try to generate the least upper bound of given arguments, however it also has to satisfy the additional requirement of being able to generate the witness ev for two types A and B being equal. (Note the witness ev will be automagically instantiated by the compiler if it is possible.)
Once we have the witness ev we can freely move between types A and B via its apply method, for example, consider
scala> type A = Lion
type A
scala> type B = Lion
type B
scala> val a: A = Lion()
val a: A = Lion(...)
scala> val ev: =:=[A, B] = implicitly[A =:= B]
val ev: A =:= B = generalized constraint
scala> ev.apply(a)
val res44: B = Lion(...)
Note how ev.apply(a) types to B. The reason we can apply =:= in this way is because it is actually a function
scala> implicitly[(A =:= B) <:< Function1[A, B]]
val res43: A =:= B <:< A => B = generalized constraint
so the implicit parameter list
(implicit ev: A =:= B)
is in effect specifying an implicit conversion function
(implicit ev: A => B)
so now compiler is able to inject automatically implicit conversion wherever it is needed so the following
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = a
x
}
is automatically expanded to
def f[A <: B, B](a: A, b: B)(implicit ev: A =:= B): B = {
val x: B = ev.apply(a)
x
}
In summary, just like type bounds, the generalised type constraints are an additional way of asking the compiler to do further checks at compile-time on our codebase.
this is a direct translation of my scala2 code to scala3
trait Narrow[F[_], A, B <: A: ClassTag]:
def apply(fa: F[A]): F[B]
extension [F[_], A] (fa: F[A]):
def narrow[B: ClassTag] (using op: Narrow[F, A, B]): F[B] = op(fa)
I need to specify the type of the narrow operation at the call site, however extension methods do not allow that syntax. what are there the best workarounds for this limitation?
the purpose of this is to be able to narrow the type in a collection / try / whatever. the narrow type class will flatmap whatever is inside, compare the runtime type, if it matches wrap that B in an F, or otherwise return an empty F
trait A
trait B extends A
object A extends A
object B extends B
val bb: List[B] = List(A, A, B, B, A)
.narrow[B]
assert(bb == List(B, B))
You could use a polymorphic function, if you can handle the ugliness:
extension [F[_], A] (fa: F[A]):
def narrow() = [B <: A] => (using op: Narrow[F, A, B]) => op(fa)
You can then call it with foo.narrow()[String]. Here it is in Scastie.
The narrow() is necessary, because without it, the type argument would go to the extension and not the polymorphic function.
In the future, Scala 3 may allow type arguments directly to methods inside extensions, but right now, you can keep using your Scala 2 implicit class and change it after the next release:
implicit class NarrowOps[F[_], A](fa: F[A]):
def narrow[B <: A](using op: Narrow[F, A, B]) = op(fa)
Scastie
Side note: You don't need B: ClassTag again in your extension, although I believe you do need to use the bound B <: A.
I wasnt able to live with the () on the call site. I decided trying a implicit conversion to a type with an apply method with just a type parameter.
trait NarrowTypeClass[F[_], A, B <: A: ClassTag]:
def apply(fa: F[A]): F[B]
given [F[_], A] as Conversion[F[A], Narrowable[F, A]] = Narrowable(_)
sealed class Narrowable [F[_], A] (fa: F[A]):
def narrow[B <: A: ClassTag] (using op: NarrowTypeClass[F, A, B]): F[B] = op(fa)
this seems to do the trick
How can I create a method that takes a generic Collection M[A] and a function from A to B and returns a Collection M[B], using the map method?
Something like:
def convert[A, M[X] <: Traversable[X], B](in: M[A], f: A => B): M[B] =
in.map(f)
The method above fails to compile with: type mismatch; found : Traversable[B] required: M[B]. Since the static type of Traversable[A].map(f: A => B) is Traversable[B] as pointed by Oleg Pyzhcov
Note: The purpose of this method is not just mapping the collection, this is just a simplification.
The full signature of map is
def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That
So you need to provide that CanBuildFrom from the call site and make sure that Repr is inferred to your concrete type M[A] by using collection traits that end on Like and have two type parameters
import scala.collection.generic.CanBuildFrom
import scala.collection.TraversableLike
import scala.language.higherKinds
def convert[M[x] <: TraversableLike[x, M[x]], A, B](
in: M[A],
f: A => B
)(implicit
cbf: CanBuildFrom[M[A], B, M[B]]
): M[B] = in.map(f)(cbf)
I have kind of a complex type hierarchy, but to break it down there are two base traits: Convertable and Conversion[A <: Convertable, B <: Convertable, e.g. there is a Conversion which can convert a Mealy-automaton to a Moore-automaton.
Every Conversion[A,B] has a convert(automaton: A) : B method.
Now I want to introduce the concept of smart Conversions, which are basically a List of normal Conversions, which will be performed one after another.
Therefore I have introduced an AutoConversion trait, extending a Conversion, which has a val path : HList parameter, to represent the chain of conversions, and should implement the convert method, so that AutoConversions just have to provide the list of actual Conversions to take.
I think you could implement this with a fold over the path, so here is my first try:
package de.uni_luebeck.isp.conversions
import shapeless._
import shapeless.ops.hlist.LeftFolder
trait AutoConversion[A <: Convertable, B <: Convertable] extends Conversion[A, B] {
val path: HList
object combiner extends Poly {
implicit def doSmth[C <: Convertable, D <: Convertable] =
use((conv : Conversion[C, D] , automaton : C) => conv.convert(automaton))
}
override def convert(startAutomaton: A): B = {
path.foldLeft(startAutomaton)(combiner)
}
}
This won't work, because no implicit Folder can be found, so I'm guessing I have to provide more Type information for the Compiler somewhere, but don't know where
You're right about needing more type information, and in general if you have a value with HList as a static type, it's likely you'll need to change your approach. There's essentially nothing you can do with an HList if all you know is that it's an HList (besides prepend values to it), and you'll usually only ever write HList as a type constraint.
In your case what you're describing is a kind of type-aligned sequence. Before you move forward with this approach, I'd suggest being really sure that you actually need to. One of the nice things about functions (and function-like types like your Conversion) is that they compose: you have an A => B and a B => C and you compose them into an A => C and can forget about B forever. You get a nice clean black box, which is generally exactly what you want.
In some cases, though, it can be useful to be able to compose function-like things in such a way that you can reflect on the pieces of the pipeline. I'm going to assume that this is one of those cases, but you should confirm that for yourself. If it's not, you're in luck, because what's coming is kind of messy.
I'll assume these types:
trait Convertable
trait Conversion[A <: Convertable, B <: Convertable] {
def convert(a: A): B
}
We can define a type class that witnesses that a specific HList is composed of one or more conversions whose types line up:
import shapeless._
trait TypeAligned[L <: HList] extends DepFn1[L] {
type I <: Convertable
type O <: Convertable
type Out = Conversion[I, O]
}
L contains all of the type information about the pipeline, and I and O are the types of its endpoints.
Next we need instances for this type class (note that this must be defined together with the trait above for the two to be companioned):
object TypeAligned {
type Aux[L <: HList, A <: Convertable, B <: Convertable] = TypeAligned[L] {
type I = A
type O = B
}
implicit def firstTypeAligned[
A <: Convertable,
B <: Convertable
]: TypeAligned.Aux[Conversion[A, B] :: HNil, A, B] =
new TypeAligned[Conversion[A, B] :: HNil] {
type I = A
type O = B
def apply(l: Conversion[A, B] :: HNil): Conversion[A, B] = l.head
}
implicit def composedTypeAligned[
A <: Convertable,
B <: Convertable,
C <: Convertable,
T <: HList
](implicit
tta: TypeAligned.Aux[T, B, C]
): TypeAligned.Aux[Conversion[A, B] :: T, A, C] =
new TypeAligned[Conversion[A, B] :: T] {
type I = A
type O = C
def apply(l: Conversion[A, B] :: T): Conversion[A, C] =
new Conversion[A, C] {
def convert(a: A): C = tta(l.tail).convert(l.head.convert(a))
}
}
}
And now you can write a version of your AutoConversion that keeps track of all of the type information about the pipeline:
class AutoConversion[L <: HList, A <: Convertable, B <: Convertable](
path: L
)(implicit ta: TypeAligned.Aux[L, A, B]) extends Conversion[A, B] {
def convert(a: A): B = ta(path).convert(a)
}
And you can use it like this:
case class AutoA(i: Int) extends Convertable
case class AutoB(s: String) extends Convertable
case class AutoC(c: Char) extends Convertable
val ab: Conversion[AutoA, AutoB] = new Conversion[AutoA, AutoB] {
def convert(a: AutoA): AutoB = AutoB(a.i.toString)
}
val bc: Conversion[AutoB, AutoC] = new Conversion[AutoB, AutoC] {
def convert(b: AutoB): AutoC = AutoC(b.s.lift(3).getOrElse('-'))
}
val conv = new AutoConversion(ab :: bc :: HNil)
And conv will have the expected static type (and implement Conversion[AutoA, AutoC]).
import shapeless._
trait Something[T <: Something[T, R], R] {}
class Test[T <: Something[T, R], R, T1 <: Something[T1, _] <:!< T](t: T, t1: T1) {}
but I get:
type arguments [T1,?] do not conform to trait Something's type parameter bounds [T <: Something[T,R],R]
Which makes sense except I would expect this to work:
class Test1[T <: Something[T, R], R, T1 <: Something[T1, R1] <:!< T, R1](t: T, t1: T1)
But it's requesting the same bound on T1 <: Something[T, R].
What I want is to say this class takes 4 type arguments, each pair describing a different descendent of Something[T <: Something[T, R], R]. Simple put, enforce that T != T1.
What is the correct way to do that?
A general example
import shapeless._
class Foo[A, B](a: A, b: B)(implicit ev: A =:!= B)
val x = new Foo(1, "hello")
// x: Foo[Int,String] = Foo#4015d0b9
val y = new Foo(1, 2)
// error: ambiguous implicit values:
// both method neqAmbig1 in package shapeless of type [A]=> shapeless.=:!=[A,A]
// and method neqAmbig2 in package shapeless of type [A]=> shapeless.=:!=[A,A]
// match expected type shapeless.=:!=[Int,Int]
// new Foo(1, 2)
// ^
in your specific case
class Test[A, B, T <: Something[T, A], T1 <: Something[T1, B]](t: T, t1: T1)(implicit ev: T =:!= T1)