Scastie version
With this infra-structure:
trait Pat[A]
object Pat {
def apply[A](elems: A*): Pat[A] = ???
}
implicit class PatOps[A](p: Pat[A]) {
def ++ (that: Pat[A]): Pat[A] = ???
def bubble: Pat[Pat[A]] = ???
def grouped(size: Pat[Int]): Pat[Pat[A]] = ???
}
implicit class PatPatOps[A](p: Pat[Pat[A]]) {
def map[B](f: Pat[A] => Pat[B]): Pat[Pat[B]] = ???
def flatMap[B](f: Pat[A] => Pat[B]): Pat[B] = ???
def flatten: Pat[A] = ???
}
It is possible to write the following for-comprehension:
trait Test1 {
val lPat = Pat(1, 2, 3)
val xs = for {
len <- lPat.bubble
cantus <- Pat(4, 40, 3).grouped(len)
} yield {
cantus ++ Pat(-1)
}
xs.flatten
}
But this one, using an intermediate variable, fails:
trait Test2 {
val lPat = Pat(1, 2, 3)
val xs = for {
len <- lPat.bubble // XXX
brown = Pat(4, 40, 3)
cantus <- brown.grouped(len)
} yield {
cantus ++ Pat(-1)
}
xs.flatten
}
The error for the line marked XXX is:
type mismatch; found : (Playground.this.Pat[Int], Playground.this.Pat[Int]) required: Playground.this.Pat[?]
Scala is 2.12.4
This happens when you define map
with overly restrictive signature map[B](f: Pat[A] => Pat[B])
. Recall that usually, it is supposed to accept functions with arbitrary result type B
, that is, it's supposed to be rather something like:
map[B](f: A => B): <stuff>
Now, your for-comprehension with intermediate helper variable brown
val xs = for {
len <- lPat.bubble
brown = Pat(4, 40, 3)
cantus <- brown.grouped(len)
} yield {
cantus ++ Pat(-1)
}
is rewritten using a map
into
val xs = lPat.bubble.map(((len) => {
val brown = Pat(4, 40, 3);
scala.Tuple2(len, brown)
})).flatMap(((x$1) => x$1: @scala.unchecked match {
case scala.Tuple2((len @ _), (brown @ _)) =>
brown.
grouped(len).
map(((cantus) => cantus.$plus$plus(Pat(-1))))
}))
as described in the documentation or in my overly detailed answer here.
Note how the return type of the implicitly generated map
is now something like (Pat[A], Pat[Int])
(the type of the tuple (len, brown)
), and doesn't match the pattern Pat[B]
from your declaration.
I don't see any workarounds. Just do whatever you can to avoid defining map
as map[B](f: Pat[A] => Pat[B])
, otherwise it will behave way too strangely. Avoid breaking functoriality of map
. If your Pat[X]
cannot map
f: X => Y
to a Pat[Y]
for arbitrary X
and Y
, then don't call it map
.
Edit: there is always a work-around...
One thing you could do is to introduce some kind of implicitly supplied CanPatFrom
:
trait CanPatFrom[X, A] extends (X => Pat[A])
and then
...
def map[X, B](f: Pat[A] => X)(implicit cpf: CanPatFrom[X, B]) = {
val pb: Pat[B] = cpf(f(...))
/* do your stuff here with `Pat[B]` instead of
* generic `X`
*/
...
}
Assuming that your Pat
carries some kind of cartesian-monoidal structure, you could define
CanPatFrom[Pat[A], Pat[A]]
,CanPatFrom[(Pat[A], Pat[B]), Pat[(A, B)]]
,CanPatFrom[(Pat[A], Pat[B], Pat[C]), Pat[(A, B, C)]]
,and thereby obtain a map
that can at least cope with the case that the return type is a tuple.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With