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]]
Related
I am reading the Scrap Your Boilerplate paper and trying to follow along by implementing the ideas in scala as best I can. However, I'm stuck on the very first function, the cast, which is used to take a value and attempt to cast it to another type in an Option, obviously a Some if the cast is successful, and None otherwise. I have a version of it working with the following code:
trait Cast[A, B]:
def apply(a: A): Option[B]
object Cast:
given cSome[A, B](using t: A =:= B): Cast[A, B] with
def apply(a: A) = Some(t(a))
given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] with
def apply(a: A) = None
This works when types are statically known. Trying the examples from the paper:
val maybeChar = summon[Cast[Char, Char]]('a') // Some(a)
val maybeBool = summon[Cast[Char, Boolean]]('a') // None
val maybeBool2 = summon[Cast[Boolean, Boolean]](true) // Some(true)
However, when I try to clean up the ergonomics a bit so that we can rely on type inference as in the examples in the paper with a generic helper defined as such:
def cast[A, B](a: A): Option[B] = summon[Cast[A, B]](a)
I'm getting only Nones, meaning the types are never being seen as the same:
val mc: Option[Char] = cast('a') // None
val mb: Option[Boolean] = cast('a') // None
val mb2: Option[Boolean] = cast(true) // None
The same happens even when I'm being explicit with the types:
val mc: Option[Char] = cast[Char, Char]('a') // None
val mb: Option[Boolean] = cast[Char, Boolean]('a') // None
val mb2: Option[Boolean] = cast[Boolean, Boolean](true) // None
I'm using scala 3.2. Is there any way to achieve this cast function with the less verbose ergonomics? I'm even more curious why what I have isn't working, especially with the explicit casting? I'm pretty sure shapeless is able to provide an SYB implementation in scala 2, albeit probably relying on macros. Can we do this in scala 3 without macros?
You missed an implicit parameter
def cast[A, B](a: A)(using Cast[A, B]): Option[B] = summon[Cast[A, B]](a)
// ^^^^^^^^^^^^^^^^
When doing implicit resolution with type parameters, why does val placement matter? (implicitly[X] vs. (implicit x: X) in Scala 2, summon[X] vs. (using X) in Scala 3)
Can someone explain me what the difference between these two approaches for typeclass instance derivation (specifically for Option[A])?
1.
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
}
trait LowPriority extends LowestPriority {
final implicit def generic[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
// special instances for Options
trait LowestPriority {
implicit def genericOption[A, Repr <: HList](
implicit gen: Generic.Aux[A, Repr],
hEncoder: Lazy[MyTrait[Option[Repr]]]
): MyTrait[Option[A]] = ???
implicit val hnilOption: MyTrait[Option[HNil]] = ???
implicit def productOption1[H, T <: HList](
implicit
head: Lazy[MyTrait[Option[H]]],
tail: Lazy[MyTrait[Option[T]]],
notOption: H <:!< Option[Z] forSome { type Z }
): MyTrait[Option[H :: T]] = ???
implicit def product2[H, T <: HList](
implicit
head: Lazy[MyTrait[Option[H]]],
tail: Lazy[MyTrait[Option[T]]
): MyTrait[Option[Option[H] :: T]] = ???
}
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
}
trait LowPriority {
// deriving instances for options from existing non-option instances
final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ??? // <<<----
final implicit def generic[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
I tried both and they worked correctly, but i'm not sure that they will produce the same results for all cases (maybe i've missed something).
Do we really need LowestPriority instances for this?
Am i right if i would say that the first approach gives us just a little bit more flexibility?
I assuming that by "worked correctly" you mean "compiled" or "worked for some simple use case".
Both of your examples deal with generic product types, but not with generic sum types, so there is no risk that e.g. Option[A] could get derived using Some[A] :+: None :+: CNil, which would enforce some ambiguity. So (as far as I can tell) you could write the second version like:
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
// deriving instances for options from existing non-option instances
final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ???
}
trait LowPriority {
// <<<----
final implicit def hcons[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
and it would derive things correctly.
But how 1. and 2. differs?
In second version you can derive MyTrait[Option[A]] if you can derive for A, and you can derive for any A which is primitive/option/product - so Option[Option[String]], Option[String] and Option[SomeCaseClass] should all work. It should also work if this SomeCaseClass contains fields which are Options, or other case classes which are Options, etc.
Version 1. is slightly different:
at first you are looking for primitives
then you try to derive for a product (so e.g. Option would not be handled here)
then you do something weird:
genericOption assumes that you created a Option[Repr], and then I guess map it using Repr
in order to build that Repr you take Option[HNil] and prepend types inside Option using productOption, which would break if someone used Option as a field
so you "fix" that by prepending an Option in a special case product2
I guess, you tested that only against case classes, because the first version would not work for:
Option for primitives (Option[String], Option[Int] or whatever you defined as primitive)
nested options (Option[Option[String]])
options for custom defined types which are not case classes but have manually defined instances:
class MyCustomType
object MyCustomType {
implicit val myTrait: MyTrait[MyCustomType]
}
implicitly[Option[MyCustomType]]
For that reason any solution with implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] is simpler and more bulletproof.
Depending on what you put directly into companion low-priority implicits might be or might not be needed:
if you defined coproducts then manual support for e.g. Option, List, Either could conflict with shapeless derived ones
if you manually implemented MyTrait implicit for some type in its companion object then it would have the same priority as implicits directly in MyTrait - so if it could be derived using shapeless you could have conflicts
For that reason it makes sense to put shapeless implicits in LowPriorityImplicits but primitives, and manual codecs for List, Option, Either, etc directly in companion. That is, unless you defined some e.g. Option[String] implicits directly in companion which could clash with "Option[A] with implicit for A".
Since I don't know your exact use case I cannot tell for sure, but I would probably go with the seconds approach, or most likely with the one I implemented in the snippet above.
Actually it's hard to say without right hand sides and actual implementations.
From information you provided it doesn't follow that the two type classes behave equivalently.
For example in the 1st approach you consider some special cases, so theoretically it's possible that you redefine some general behavior in special case differently.
By the way, Option[A] is a coproduct of Some[A] and None.type (List[A] is a coproduct of scala.::[A] and Nil.type) and sometimes it's easier to derive a type class for coproducts than for Option[A] (or List[A]).
Lets say I have two abstract, unrelated types A and B (sealed traits with no implementation details if it makes any difference). I would like to be able to return As in places that are expecting Bs, and vice-versa.
this:
implicit def aToB(a: A): B = a.asInstanceOf[B]
implicit def bToA(b: B): A = a.asInstanceOf[A]
implicit def convertSeq[T, U](s: Seq[T]): Seq[U] = s.map(_.asInstanceOf[U])
will work because I know that all types that extend A also extend B, but the compiler doesn't know this. Is there a type-safe way to do this?
Edit to add more concrete detail:
trait A
trait B
class X extends A with B
class Y extends A with B
class Z extends A with B
def doSomething(): Seq[A] = Seq(new X(), new Y(), new Z())
def publicFacingMethod1(): Seq[A] = doSomething()
// how can I do this?
def publicFacingMethod2(): Seq[B] = doSomething()
You can write
implicit def convertSeq[T <: A, U >: B](s: Seq[T])(implicit ev: A => B): Seq[U] = s.map(ev)
instead of your definition of convertSeq.
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.
Referring to a previous answer of mine on stackoverflow
The core of the complexity is illustrated in just one method:
implicit def traversableToFilterOps[CC[X] <: Traversable[X], T]
(xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) =
new MoreFilterOperations[CC[T], T](xs)
With two questions:
Is there any way to give the compiler a hint that Map meets the signature CC[X] <: Traversable[X]?
I would expect it to match as a Traversable[Tuple2[_,_]] but this doesn't happen. In the end, I had to write a second method taking CC[KX,VX] <: Map[KX,VX], but that feels redundant
witness: CC[T] <:< TraversableLike[T,CC[T]] also seems redundant given the first type parameter, my gut feeling is that this is enforced by the type of Traversable and must always hold true for any possible subclass or value of X, so there should be no reason to explicitly require it as a witness.
If I test this using an existential type in the REPL, then the compiler seems to agree with me:
scala> implicitly[Traversable[X] <:< TraversableLike[X,Traversable[X]] forSome { type X }]
res8: <:<[Traversable[X],scala.collection.TraversableLike[X,Traversable[X]]] forSome { type X } = <function1>
Is there therefore any way to do away with the boilerplate?
I'm a Scala noob, so please don't shoot me down if this doesn't help.
Assuming this:
class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) {}
Would something like this work?
// t2fo is short for traversableToFilterOps
implicit def t2fo[Repr <% TraversableLike[T, Repr], T](xs: Repr) =
new MoreFilterOperations[Repr, T](xs)
// m2fo is short for mapToFilterOps
implicit def m2fo[Repr <% Map[K, V] <% TraversableLike[(K,V), Repr], K, V]
(xs: Repr) = new MoreFilterOperations[Repr, (K, V)](xs)
This should work because (according to the book I have.. Programming Scala, p264) the following method definition with a view bound:
def m [A <% B](arglist): R = ...
It is effectively the same as this method definition:
def m [A](arglist)(implicit viewAB: A => B): R = ...