Scala 3 match type reduction - scala

Let's say I want to represent a vector using a matched type like this:
type V[I <: Int, N] = I match
case 2 => (N, N)
case 3 => (N, N, N)
case 4 => (N, N, N, N)
so now I can declare my vectors using tuples:
val v2: V[2, Int] = (2, 2)
val v3: V[3, Int] = (3, 3, 3)
val v4: V[4, Int] = (4, 4, 4, 4)
and then I can define a tuple-matrix and a tuple-square-matrix:
type Mx[L <: Int, C <: Int, N] = V[L, V[C, N]]
type Mxq[T <: Int, N] = Mx[T, T, N]
type M3I = Mxq[3, Int]
everything is fine until now:
//Compiles
val m3: M3I = (
(1, 0, 0),
(0, 1, 0),
(0, 0, 1)
)
//Doesn't compile
val m3Err: M3I = (
(1, 0, 0, 1),
(0, 1, 0),
(0, 0, 1)
)
finally I define extension methods to manipulate the matrices
extension[L <: Int, C <: Int, N] (essa: Mx[L, C, N])
#implicitNotFound(msg = "Não é possível multiplicar as matrizes pois ${C} != ${LB}")
def *[LB <: Int, CB <: Int](outra: Mx[LB, CB, N])(using ev: C =:= LB) = ???
but when I try to use it with m1 * m2 (which are both 3x3 matrices) i get this compiler error:
value * is not a member of (TestesKoinos.V[(3 : Int), Int], TestesKoinos.V[(3 : Int), Int],
TestesKoinos.V[(3 : Int), Int]
).
An extension method was tried, but could not be fully constructed:
TestesKoinos.*[L, C, N](m1)[LB, CB] failed with
Found: (TestesKoinos.m1 : (TestesKoinos.V[(3 : Int), Int],
TestesKoinos.V[(3 : Int), Int]
, TestesKoinos.V[(3 : Int), Int]))
Required: TestesKoinos.Mx[L, C, N]
where: C is a type variable with constraint <: Int
L is a type variable with constraint <: Int
N is a type variable
Note: a match type could not be fully reduced:
trying to reduce TestesKoinos.Mx[L, C, N]
trying to reduce TestesKoinos.V[L, TestesKoinos.V[C, N]]
failed since selector L
does not match case (2 : Int) => (TestesKoinos.V[C, N], TestesKoinos.V[C, N])
and cannot be shown to be disjoint from it either.
Therefore, reduction cannot advance to the remaining cases
case (3 : Int) => (TestesKoinos.V[C, N], TestesKoinos.V[C, N], TestesKoinos.V[C, N])
case (4 : Int) => (TestesKoinos.V[C, N], TestesKoinos.V[C, N], TestesKoinos.V[C, N],
TestesKoinos.V[C, N]
)
m1 * m2
What is wrong with my code/logic here?

Related

Create function to group sequence at breakpoints

I'm trying to create a function that takes two sequences and groups the elements first based on the "breakpoints" included in the second. An example:
val ls = ('a' to 'z').map(_.toString)
// IndexedSeq[String] = Vector(a, b, c, d, e, f, g, h, i, j, k, l, m, ...)
val breakpoints = Seq("c", "f", "j")
grouper(ls, breakpoints)
// Seq(Seq("a", "b"), Seq("c", "d", "e"), Seq("f", "g", "h", "i"), Seq("j", ...))
I've tried to do this with recursive calls to takeWhile and dropWhile (as I might in a language like Haskell), but as my current function doesn't use tail recursion, I receive a java.lang.StackOverflowError. Here's the function:
def grouper(strings: Seq[String], breaks: Seq[String]): Seq[Seq[String]] = strings match {
case Nil => Seq()
case s => s.takeWhile(breaks.contains(_)) +: grouper(s.dropWhile(breaks.contains(_)), breaks)
}
Is there a better way to approach this?
You're on the right track. takeWhile and dropWhile can be replaced by span.
def grouper(xs: Seq[String], breaks: Seq[String]): Seq[Seq[String]] = breaks match{
case Nil => Seq(xs)
case h::t => {
val split = xs.span(x => x != h);
split._1 +: grouper(split._2, t);
}
}
scala> grouper(ls, breakpoints)
res5: Seq[Seq[String]] = List(Vector(a, b), Vector(c, d, e), Vector(f, g, h, i),
Vector(j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z))
From the API on span: Note: c span p is equivalent to (but possibly more efficient than) (c takeWhile p, c dropWhile p), provided the evaluation of the predicate p does not cause any side-effects.
For this kind of problems, I always prefer to write my own tail-recursive function, operating o Lists.
import Ordering.Implicits._
def group[A : Ordering](data: List[A], breakPoints: List[A]): List[List[A]] = {
def takeUntil(list: List[A], breakPoint: A): (List[A], List[A]) = {
#annotation.tailrec
def loop(remaining: List[A], acc: List[A]): (List[A], List[A]) =
remaining match {
case x :: xs if (x < breakPoint) =>
loop(remaining = xs, x :: acc)
case _ =>
(acc.reverse, remaining)
}
loop(remaining = list, acc = List.empty)
}
#annotation.tailrec
def loop(remainingElements: List[A], remainingBreakPoints: List[A], acc: List[List[A]]): List[List[A]] =
remainingBreakPoints match {
case breakPoint :: remainingBreakPoints =>
val (group, remaining) = takeUntil(remainingElements, breakPoint)
loop(
remainingElements = remaining,
remainingBreakPoints,
group :: acc
)
case Nil =>
(remainingElements :: acc).reverse
}
loop(
remainingElements = data.sorted,
remainingBreakPoints = breakPoints.sorted,
acc = List.empty
)
}
You can use it like this:
group(data = ('a' to 'z').toList, breakPoints = List('c', 'f', 'j'))
//res: List[List[Char]] = List(
// List('a', 'b'),
// List('c', 'd', 'e'),
// List('f', 'g', 'h', 'i'),
// List('j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z')
// )
This function will generate always a list of length = length(breakPoints) + 1.
If there are no more elements, it will generate empty lists.
(you can edit the code for your concrete requirements)
You can try a bit different approach:
def grouper(strings: Seq[String], breaks: Seq[String]): Seq[Seq[String]] = {
var i = 0
(for(x <- strings) yield {if (breaks.contains(x)) {i=i+1}; (x,i)})
.groupBy(_._2).map(_._2.map(_._1)).toList
}
grouper(ls,breakpoints).foreach(println(_))
Vector(f, g, h, i)
Vector(c, d, e)
Vector(j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)
Vector(a, b)

Cartesian product function in Scala

I'm trying to write a generic cross function that would yield the cartesian product of two iterables. Here is my attempt:
def cross[a, b, A <: Iterable[a], B <: Iterable[b]](a: A, b: B): Iterable[(a, b)] =
for (i <- a; j <- b) yield (i, j)
However, the signature is not quite right. I get:
Error:(147, 15) inferred type arguments [Nothing,Nothing,List[Int],Array[String]] do not conform to method cross's type parameter bounds [a,b,A <: Iterable[a],B <: Iterable[b]]
println(cross(List(1, 2, 3), Array("a", "b", "c")))
What is the correct way to write this signature, where I want to take 2 Iterable on different types of elements?
def cross[A, B](a: Iterable[A], b: Iterable[B]): Iterable[(A, B)] =
for (i <- a; j <- b) yield (i, j)

How to implement generic function in Scala with two argument types?

I'd like to implement a function in Scala that computes the dot product of two numeric sequences as follows
val x = Seq(1,2,3.0)
val y = Seq(4,5,6)
val z = (for (a <- x; b <- y) yield a*b).sum
scala> z : Double = 90.0
val x = Seq(1,2,3)
val y = Seq(4,5,6)
val z = (for (a <- x; b <- y) yield a*b).sum
scala> z : Int = 90
Notice that if the two sequences are of different types, the result is an Double. If the two sequences are of the same type (e.g. Int), the result is an Int.
I came up with two alternatives but neither meets the requirement as defined above.
Alternative #1:
def dotProduct[T: Numeric](x: Seq[T], y: Seq[T]): T = (for (a <- x; b <- y) yield implicitly[Numeric[T]].times(a, b)).sum
This returns the result in the same type as the input, but it can't take two different types.
Alternative #2:
def dotProduct[A, B](x: Seq[A], y: Seq[B])(implicit nx: Numeric[A], ny: Numeric[B]) = (for (a <- x; b <- y) yield nx.toDouble(a)*ny.toDouble(b)).sum
This works for all numeric sequences. However, it always return a Double, even if the two sequences are of the type Int.
Any suggestion is greatly appreciated.
p.s. The function I implemented above is not "dot product", but simply sum of product of two sequences. Thanks Daniel for pointing it out.
Alternative #3 (slightly better than alternatives #1 and #2):
def sumProduct[T, A <% T, B <% T](x: Seq[A], y: Seq[B])(implicit num: Numeric[T]) = (for (a <- x; b <- y) yield num.times(a,b)).sum
sumProduct(Seq(1,2,3), Seq(4,5,6)) //> res0: Int = 90
sumProduct(Seq(1,2,3.0), Seq(4,5,6)) //> res1: Double = 90.0
sumProduct(Seq(1,2,3), Seq(4,5,6.0)) // Fails!!!
Unfortunately, the View Bound feature (e.g. "<%") will be deprecated in Scala 2.10.
You could create a typeclass that represents the promotion rules:
trait NumericPromotion[A, B, C] {
def promote(a: A, b: B): (C, C)
}
implicit object IntDoublePromotion extends NumericPromotion[Int, Double, Double] {
def promote(a: Int, b: Double): (Double, Double) = (a.toDouble, b)
}
def dotProduct[A, B, C]
(x: Seq[A], y: Seq[B])
(implicit numEv: Numeric[C], promEv: NumericPromotion[A, B, C])
: C = {
val foo = for {
a <- x
b <- y
} yield {
val (pa, pb) = promEv.promote(a, b)
numEv.times(pa, pb)
}
foo.sum
}
dotProduct[Int, Double, Double](Seq(1, 2, 3), Seq(1.0, 2.0, 3.0))
My typeclass-fu isn't good enough to eliminate the explicit type parameters in the call to dotProduct, nor could I figure out how to avoid the val foo inside the method; inlining foo led to compiler errors. I chalk this up to no having really internalized the implicit resolution rules. Maybe somebody else can get you further.
It's also worth mentioning that this is directional; you couldn't compute dotProduct(Seq(1.0, 2.0, 3.0), Seq(1, 2, 3)). But that's easy to fix:
implicit def flipNumericPromotion[A, B, C]
(implicit promEv: NumericPromotion[B, A, C])
: NumericPromotion[A, B, C] =
new NumericPromotion[A, B, C] {
override def promote(a: A, b: B): (C, C) = promEv.promote(b, a)
}
It's also worth mentioning that your code doesn't compute a dot product. The dot product of [1, 2, 3] and [4, 5, 6] is 4 + 10 + 18 = 32.

Transposing arbitrary collection-of-collections in Scala

I have to often transpose a "rectangular" collection-of-collections in Scala, e.g.: a list of maps, a map of lists, a map of maps, a set of lists, a map of sets etc. Since collections can be uniformly viewed as a mapping from a specific domain to a co-domain (e.g.: a List[A]/Array[A] is a mapping from the Int domain to the A co-domain, Set[A]is a mapping from the A domain to the Boolean co-domain etc.), I'd like to write a clean, generic function to do a transpose operation (e.g.: turn a map of lists to the transposed list of maps). However, I'm having trouble because other than the () operator, Scala doesn't seem to have a unified API to view collections abstractly as mappings ?
So I end up writing a separate transpose for each type of collection-of-collections as follows:
def transposeMapOfLists[A,B]( mapOfLists: Map[A,List[B]] ) : List[Map[A,B]] = {
val k = ( mapOfLists keys ) toList
val l = ( k map { mapOfLists(_) } ) transpose;
l map { v => ( k zip v ) toMap }
}
def transposeListOfMaps[A,B]( listOfMaps: List[Map[A,B]]) : Map[A,List[B]] = {
val k = ( listOfMaps(0) keys ) toList
val l = ( listOfMaps map { m => k map { m(_) } } ) transpose;
( k zip l ) toMap
}
def transposeMapOfMaps[A,B,C]( mapOfMaps: Map[A,Map[B,C]] ) : Map[B,Map[A,C]] = {
val k = ( mapOfMaps keys ) toList
val listOfMaps = k map { mapOfMaps(_) }
val mapOfLists = transposeListOfMaps( listOfMaps )
mapOfLists map { p => ( p._1, ( k zip p._2 ) toMap ) }
}
Can someone help me unify these methods into one generic collection-of-collections transpose ? It will also help me (and I am sure others) learn some useful Scala features in the process.
ps: I have ignored exception handling and have assumed the input collection-of-collections is rectangular, i.e., all of the inner collections' domain elements constitute the same set.
I'm sure the following messy version using type classes could be cleaned up a lot, but it works as a quick proof-of-concept. I don't see an easy way to get the return types right without dependent method types (I'm sure it's possible), so you'll have to use -Xexperimental:
trait Mapping[A, B, C] {
type M[D] <: PartialFunction[A, D]
def domain(c: C): Seq[A]
def fromPairs[D](ps: Seq[(A, D)]): M[D]
def codomain(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
domain(c).map(c)
def toPairs(c: C)(implicit ev: C <:< PartialFunction[A, B]) =
domain(c).map(a => (a, c(a)))
}
implicit def seqMapping[A, B <: Seq[A]] = new Mapping[Int, A, B] {
type M[C] = Seq[C]
def domain(c: B) = 0 until c.size
def fromPairs[C](ps: Seq[(Int, C)]) = ps.sortBy(_._1).map(_._2)
}
implicit def mapMapping[A, B, C <: Map[A, B]] = new Mapping[A, B, C] {
type M[D] = Map[A, D]
def domain(c: C) = c.keys.toSeq
def fromPairs[D](ps: Seq[(A, D)]) = ps.toMap
}
def transpose[A, B, C, M, N](m: M)(implicit
pev: M <:< PartialFunction[A, N],
qev: N <:< PartialFunction[B, C],
mev: Mapping[A, N, M],
nev: Mapping[B, C, N]
) = nev.fromPairs(nev.domain(mev.codomain(m).head).map(b =>
b -> mev.fromPairs(mev.toPairs(m).map { case (a, c) => a -> c(b) })
))
And now for some tests:
scala> println(transpose(List(Map("a" -> 1, "b" -> 13), Map("b" -> 99, "a" -> 14))))
Map(a -> Vector(1, 14), b -> Vector(13, 99))
scala> println(transpose(Map('a' -> List(1, 2, 3), 'z' -> List(4, 5, 6))))
Vector(Map(a -> 1, z -> 4), Map(a -> 2, z -> 5), Map(a -> 3, z -> 6))
scala> println(transpose(Map("x" -> Map(4 -> 'a, 99 -> 'z), "y" -> Map(4 -> 'b, 99 -> 's))))
Map(4 -> Map(x -> 'a, y -> 'b), 99 -> Map(x -> 'z, y -> 's))
So it's working as desired.

Scala: Defining a function to be the correct type

I've been playing around with Scala code and have come up against a compiler error which I don't understand. The code generates a vector of pairs of Ints and then tries to filter it.
val L = for (x <- (1 to 5)) yield (x, x * x)
val f = (x: Int, y: Int) => x > 3
println(L.filter(f))
The compiler complains about trying to use f as an argument for the filter method with the compiler error message being:
error: type mismatch;
found : (Int, Int) => Boolean
required: ((Int, Int)) => Boolean
How do I define the function f correctly to satisfy the required function type? I tried to add extra parentheses around (x: Int, y: Int) but this gave:
error: not a legal formal parameter
val f = ((x: Int, y: Int)) => x > 3
^
f has type Function2[Int, Int, Boolean]. L's type is IndexedSeq[Tuple2[Int, Int]] and so filter expects a function of type Function1[Tuple2[Int, Int], Boolean]. Every FunctionN[A, B, .., R] trait has a method tupled, which returns a function of type Function1[TupleN[A, B, ..], R]. You can use it here to transform f to the type expected by L.filter.
println(L.filter(f.tupled))
> Vector((4,16), (5,25))
Alternatively you can redefine f to be a Function1[Tuple2[Int, Int], Boolean] as follows and use it directly.
val f = (t: (Int, Int)) => t._1 > 3
println(L.filter(f))
> Vector((4,16), (5,25))
val f = (xy: (Int, Int)) => xy._1 > 3
println (L.filter (f))
If you do
val f = (x: Int, y: Int) => x > 3
you define a function which takes two ints, which is not the same as a function which takes a pair of ints as parameter.
Compare:
scala> val f = (x: Int, y: Int) => x > 3
f: (Int, Int) => Boolean = <function2>
scala> val f = (xy: (Int, Int)) => xy._1 > 3
f: ((Int, Int)) => Boolean = <function1>
If you don't want to rewrite your function to explicitely useing Tuple2 (as suggested by missingfaktor and user unknown), you can define a implicit method to do it automatically. This lets the function f untouched (you aren't forced to always call it with a Tuple2 parameter) and easier to understand, because you still use the identifiers x and y.
implicit def fun2ToTuple[A,B,Res](f:(A,B)=>Res):((A,B))=>Res =
(t:(A,B)) => f(t._1, t._2)
val L = for (x <- (1 to 5)) yield (x, x * x)
val f = (x: Int, y: Int) => x > 3
val g = (x: Int, y: Int) => x % 2 > y % 3
L.filter(f) //> Vector((4,16), (5,25))
L.filter(g) //> Vector((3,9))
f(0,1) //> false
f((4,2)) //> true
Now every Function2 can also be used as a Function1 with an Tuple2 as parameter, because it uses the implicit method to convert the function if needed.
For functions with more than two parameters the implicit defs looks similiar:
implicit def fun3ToTuple[A,B,C,Res](f:(A,B,C)=>Res):((A,B,C))=>Res =
(t:(A,B,C)) => f(t._1, t._2, t._3)
implicit def fun4ToTuple[A,B,C,D,Res](f:(A,B,C,D)=>Res):((A,B,C,D))=>Res =
(t:(A,B,C,D)) => f(t._1, t._2, t._3, t._4)
...