I want to add some helpful implicits to both mutable and immutable TreeMaps and TreeSets in Scala.
Here is my attempt:
First try to define the least upper bound of TreeMap and TreeSet that has headOption/lastOption (from GenTraversableLike) and from/to/until (from Sorted):
type SortedCollection[A, Repr <: SortedCollection[A, Repr]] = collection.generic.Sorted[A, Repr] with collection.GenTraversableLike[A, Repr]
Write my util:
implicit class RichSortedCollection[A, Repr <: SortedCollection[A, Repr]](s: SortedCollection[A, Repr]) {
def greaterThanOrEqualTo(a: A): Option[A] = s.from(a).headOption
def lessThan(a: A): Option[A] = s.until(a).lastOption
def lessThanOrEqualTo(a: A): Option[A] = s.to(a).lastOption
}
This only works partially: SortedSet#greaterThan compiles but TreeMap#greaterThan does not. How do I fix it?
TreeMap[A, B] (transitively) extends GenTraversableLike[(A, B), TreeMap[A, B]] and Sorted[A, TreeMap[A, B]], so you could say it's a:
Sorted[A, TreeMap[A, B]] with GenTraversableLike[(A, B), TreeMap[A, B]]
This is close to your type alias, but the first type parameter of Sorted and GenTraverableLike in the type alias SortedCollection must be the same, which they are not above. They simply aren't compatible. That is, Repr = TreeMap[A, B] is fine, but A = (A, B) doesn't make sense.
You're going to have the same issue with all map types, and your only real choice is to re-implement RichSortedCollection for maps as well.
Related
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
I want to write a function
def doSomething(m: Map[X, Y]): Z = ???
for some given types X, Y, Z.
The function will do the same thing for both immutable.Map and mutable.Map.
Is there any way I can write that?
Just declare the argument m to be of type collection.Map[X, Y].
Here is a quick way to find out what the least upper bound of the two types is:
import collection._
def f(a: mutable.Map[Int, Int], b: immutable.Map[Int, Int]) = List(a, b).head
The REPL with tell you that the return type is collection.Map[Int, Int].
Both immutable.Map and mutable.Map extends scala.collection.Map.
package scala
package collection
trait Map[A, +B] extends Iterable[(A, B)] with GenMap[A, B] with MapLike[A, B, Map[A, B]] {
def empty: Map[A, B] = Map.empty
override def seq: Map[A, B] = this
}
looking above you will see this is a generic interface in Scala to describe a Map. therefore your function could be def doSomething(m: scala.collection.Map[X, Y]): Z = ???
you will still have majority of Map interface functions to use, however, those ones that's not shared between mutable and immutable won't be there.
I've been wracking my head against this and I can't figure out if there is a way to properly do this.
I feel I know what the problem is, but don't know how to solve it.
I have a method:
implicit def combineAlg[A: Alg, B: Alg]: Alg[A with B] = ...
if I call it explicitly it works fine, however it never gets implied properly.
// works
implicit val comb: Alg[A with B] = combineAlg[A, B]
// doesn't work
implicit val comb: Alg[A with B] = implicitly[Alg[A with B]]
Through my debugging with -Xlog-implicits, I believe its calling combineAlg[A with B, Nothing].
I'm looking to find a way to do something like:
implicit def combineExpAlg[AB, A >: AB, B >: AB]
or
implicit def combineExpAlg[AB, A, B](implicit ev1: AB <:< A, ev2: AB <:< B)
so that it understands that it needs to split apart the "with", but neither helps.
Not sure if there is a way to do this, really its an experiment I'm doing for "object algebras" in Scala and I'm trying to see how to remove boilerplate.
It'd be awesome if there was a solution.
A dotty solution would also be acceptable, as I'm also implementing it there to see if some of the new features make it simpler.
In case more information is needed you can view the repository here
What I'm trying to change is algebra.combineExpAlg.
It will look like it's working because I define specific implicits in algebra.interpreters.package that specifically spell out each interpreter pair, which is what I'm trying to generalize.
The following code compiles:
trait Alg[T]
trait LowPriorityAlg {
implicit def bAlg: Alg[B0] = ???
}
object Alg extends LowPriorityAlg {
implicit def aAlg: Alg[A0] = ???
implicit def combineAlg[AB, A: Alg, B: Alg](implicit ev1: AB <:< A, ev2: AB <:< B): Alg[AB] = ???
}
trait A0
trait B0
val comb: Alg[A0 with B0] = Alg.combineAlg[A0 with B0, A0, B0]
val comb1: Alg[A0 with B0] = implicitly[Alg[A0 with B0]]
I am dipping my toes in higher kinded types, exploring a very basic Scala example:
trait Mappable[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
object Mappable {
implicit object MappableOption extends Mappable[Option] {
def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
}
implicit object MappableSeq extends Mappable[Seq] {
def map[A, B](fa: Seq[A])(f: A => B): Seq[B] = fa.map(f)
}
}
def bananaTuple[F[_], T](f: F[T])(implicit F: Mappable[F]): F[(String, T)] =
F.map(f)(("banana", _))
This works:
bananaTuple(Option(42)) // Some((banana,42))
bananaTuple(Seq(42)) // List((banana,42))
But this does not compile:
bananaTuple(Some(42))
bananaTuple(List(42))
The compile errors I get:
could not find implicit value for parameter F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some] bananaTuple(Some(42))
not enough arguments for method bananaTuple: (implicit F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some])Some[(String, Int)]. Unspecified value parameter F. bananaTuple(Some(42))
How can I bring variance into the game?
We can make this work with a little more parameteric polymorphism:
object MappableExample {
trait Mappable[F[_]] {
type Res[_]
def map[A, B](f: A => B)(c: F[A]): Res[B]
}
implicit def seqMappable[C[X] <: Seq[X]] = new Mappable[C] {
type Res[X] = Seq[X]
override def map[A, B](f:A => B)(c: C[A]): Seq[B] = c.map(f)
}
implicit def optionMappable[C[X] <: Option[X]]: Mappable[C] = new Mappable[C] {
type Res[X] = Option[X]
override def map[A, B](f: A => B)(c: C[A]): Option[B] = c.map(f)
}
def map[A, B, C[_]](xs: C[A])(f: A => B)(implicit mappable: Mappable[C]): mappable.Res[B] = {
mappable.map(f)(xs)
}
def main(args: Array[String]): Unit = {
println(map(List(1,2,3))(("banana", _)))
println(map(Some(1))(("banana", _)))
}
}
Yields:
List((banana,1), (banana,2), (banana,3))
Some((banana,1))
The compiler now infers Some as Mappable[Some]#Res[Int] and Mappable[List]#Res[Int] which is quite ugly. One would expect the compiler to actually be able to infer the right type without needing for any co/contravariance on the Mappable trait, which we can't do since we're using it in an invariant position.
Subtype polymorphism allows us to pass values of a certain type or any of its subtypes to a method. If a method takes a value of type Fruit, we can also pass an Apple inside (an apple is a fruit after all). So if you want to be able to pass a Mappable.MappableOption to your bananaTuple method, you have to make that MappableOption a subtype of MappableSome (since the type of your first parameter of bananaTuple dictates the implicit one). This means that you want your Mappable contravariant (if Some <: Option, then Mappable[Some] >: Mappable[Option]).
But you cannot have Mappable[F[_]] contravariant in F because F appears in covariant position of map (as a function parameter). Note that F also appears in contravariant position of map (as a return value).
If you manage to make Mappable[F[_]] contravariant in F, it should work, but I'm not sure if making it contravariant makes sense. That is, if you want a subtype relationship such as e.g. Apple <: Fruit to result in Mappable[Apple] >: Mappable[Fruit] (this would not compile since Apple and Fruit are not type constructors, but I'm just using simple types to make a point here).
Making a type contravariant in its type and solving the problem of contravariant type appearing in covariant position is a common problem and perhaps it's better if you search for it elsewhere (here is one example). I still think that it's better to provide an implicit object for every type you want to use, that is, to provide separate implicit objects for e.g. Seq and List.
As title of this question suggests: my question is more about form (idiomatic convention) than function. Put Succinctly:
What is the semantic difference between MyCollectionLike and MyCollection?
As examples: What is the difference between StringLike and String or MapLike and Map. Looking closely at the Scala API docs, I can tell that XLike is typically a super-type of X. But, beyond that, I am not clear on the semantic difference between these layers of abstraction.
In practice, If I were creating a new class/trait, understanding this distinction would be helpful when I am choosing names for said class.
My specific problem, where this has come up is as follows:
I want to create the trait: SurjectiveMap[K, T] which can be mixed-in with either Map[K, Set[T]] or MapLike[K, SetLike[T]]. Given that I do not know the semantic difference between *Like and *, I am not sure which to go with.
The same difference as between IFoo and Foo, Bar and BarImpl (except the fact that TraversableLike is super-trait which contains implementation):
The Scala collection library avoids code duplication and achieves the
"same-result-type" principle by using generic builders and traversals
over collections in so-called implementation traits. These traits are
named with a Like suffix; for instance, IndexedSeqLike is the
implementation trait for IndexedSeq, and similarly, TraversableLike is
the implementation trait for Traversable. Collection classes such as
Traversable or IndexedSeq inherit all their concrete method
implementations from these traits.
from Scala collections architecture
I think using the Like traits lets you refine the return (representation) types. This involves a lot more work. Compare:
import collection.generic.CanBuildFrom
object FooMap {
type Coll = FooMap[_, _]
implicit def canBuildFrom[A, B]: CanBuildFrom[Coll, (A, B), FooMap[A, B]] = ???
}
trait FooMap[A, +B] extends Map[A, B] {
def foo = 33
}
def test(f: FooMap[Any, Any]) {
f.map(identity).foo // nope, we ended up with a regular `Map`
}
versus
object FooMap extends collection.generic.ImmutableMapFactory[FooMap] {
override type Coll = FooMap[_, _]
implicit def canBuildFrom[A, B]: CanBuildFrom[Coll, (A, B), FooMap[A, B]] = ???
def empty[A, B]: FooMap[A, B] = ???
}
trait FooMap[A, +B] extends Map[A, B]
with collection.immutable.MapLike[A, B, FooMap[A, B]] {
def foo = 33
override def empty: FooMap[A, B] = FooMap.empty[A, B]
}
def test(f: FooMap[Any, Any]) {
f.map(identity).foo // yes
}
The MapLike trait must be mixed in after the Map trait for the correct return types to kick in.
Still you don't get everything for free it seems, e.g. you will need to override more methods:
override def +[B1 >: B](kv: (A, B1)): FooMap[A, B1] // etc.