Scala: Convert heterogeneous Tuple to List - scala

On the Scala command line, there is no problem writing:
List((1,2),(1,'a'))
But I can't seem to write a function that would convert a Tuple2 to a List, because Tuple2 takes 2 type parameters, but List only one. Any attempt such as:
def tuple2ToList[T1, T2](pair: (T1, T2)): List[Any] = List(pair._1, pair._2)
seems to be bound to lose type information. Is there anything we can do to preserve some type information in the process?

This is a solution proposed by #BenReich in a comment
Simply use one type parameter instead of two, the compiler will
automatically select the least upper bound of the types of the two
tuple elements:
def tupleToList[T](p: (T, T)): List[T] = List(p._1, p._2)
Examples:
scala> tupleToList((Some(42), None))
res4: List[Option[Int]] = List(Some(42), None)
scala> tupleToList((0.9999, 1))
res5: List[AnyVal] = List(0.9999, 1)
scala> tupleToList((Set(1), List(1)))
res6: List[scala.collection.immutable.Iterable[Int] with Int => AnyVal] = List(Set(1), List(1))
This is the old suboptimal solution, I'll leave it here as context for #BenReich's comment.
Define the return type as the least upper bound of T1 and T2:
def tupleToListRescueTypeInfo[R, T1 <: R, T2 <: R](p: (T1, T2)): List[R] =
List(p._1, p._2)
Little test:
scala> tupleToListRescueTypeInfo((2, 3))
res0: List[Int] = List(2, 3)
scala> tupleToListRescueTypeInfo((Some[Int](3), None))
res1: List[Option[Int]] = List(Some(3), None)
scala> tupleToListRescueTypeInfo((List(1,2), Set(1,2)))
res2: List[scala.collection.immutable.Iterable[Int] with Int => AnyVal] =
List(List(1, 2), Set(1, 2))
It obviously cannot preserve all type information, but it at least attempts to rescue as much as possible.

Related

Scala: map(f) and map(_.f)

I thought in scala map(f) is the same as map(_.f) as map(x => x.f), but turns out it is not
scala> val a = List(1,2,3)
val a: List[Int] = List(1, 2, 3)
scala> a.map(toString)
val res7: List[Char] = List(l, i, n)
scala> a.map(_.toString)
val res8: List[String] = List(1, 2, 3)
What happenes when a.map(toString) is called? Where did the three charaacters l, i, and n come from?
map(f) is not the same as map(_.f()). It's the same as map(f(_)). That is, it's going to call f(x), not x.f(), for each x in the list.
So a.map(toString) should be an error because the normal toString method does not take any arguments. My guess is that in your REPL session you've defined your own toString method that takes an argument and that's the one that's being called.

Readable FoldRight via FoldLeft in Scala

In Functional Programming in Scala, the author asks to express FoldRight via FoldLeft. And then the author offers the following implementation:
def foldRightViaFoldLeftAuthor[A, B](l: List[A], z: B)(f: (A, B) => B): B = {
foldLeft(l, (b: B) => b)((g, a) => b => g(f(a, b)))(z)
}
There have been a couple of questions like this asking to explain the author's solution. And probably a lot of people are still struggling to understand it.
While I was thinking about the task I came up with a different implementation that seems much more readable and easier to grasp at least for me
def foldRightViaFoldLeftMy[A, B](l: List[A], z: B)(f: (A, B) => B): B = {
foldLeft(l, z)(((g: (A, B) => B) => (b: B, a: A) => g(a, b)) (f))
}
So I basically prepare a function that converts f(a,b) to f(b,a) and now I'm able to call foldLeft that is tail-recursive.
So my questions are:
Is there any reason to implement it in the way the author did?
Are there any drawbacks in my implementation in comparison to the author's?
You've written an implementation that has the same signature as foldRight, but it doesn't have the right semantics when the combination operation isn't commutative. To take one example, a right fold with the empty list as zero and cons as the combination operation should be identity:
scala> val input = List(1, 2, 3)
input: List[Int] = List(1, 2, 3)
scala> val f: (Int, List[Int]) => List[Int] = _ :: _
f: (Int, List[Int]) => List[Int] = $$Lambda$1912/991363637#5e9bf744
scala> foldRightViaFoldLeftAuthor(input, List.empty[Int])(f)
res0: List[Int] = List(1, 2, 3)
But your implementation reverses the list:
scala> foldRightViaFoldLeftMy(input, List.empty[Int])(f)
res1: List[Int] = List(3, 2, 1)
This is because you're still folding from left to right, even though you've switched the order of the combination function's arguments. I find the diagrams on the Wikipedia page about fold useful for visualizing the difference. In your implementation the applications happen like this:
scala> f(3, f(2, f(1, Nil)))
res2: List[Int] = List(3, 2, 1)
While in the book's implementation you have something like this:
((b3: List[Int]) =>
((b2: List[Int]) =>
((b1: List[Int]) => identity(f(1, b1)))(f(2, b2)))(f(3, b3)
)
)(Nil)
Which boils down to:
scala> f(1, f(2, f(3, Nil)))
res3: List[Int] = List(1, 2, 3)
So the answer to both of your questions is "yes", there is an important difference between your implementation and the book's.

Prepend to an Iterable?

How can I prepend, i.e. cons, to an Iterable?
scala> val xs: Iterable[Int] = Seq(1)
xs: Iterable[Int] = List(1)
scala> xs :: 5
<console>:15: error: value :: is not a member of Int
xs :: 5
^
I looked at the docs, but didn't figure it out.
:: is specific to List. It is a List, in fact.
There is no concept of prepending to an Iterable, as not all Iterables will guarantee order (Set does not, for example). You may want Seq instead, which would use +: to prepend.
Iterable doesn't have a prepend method, but you can use "++" to join two iterables:
scala> val xs: Iterable[Int] = Seq(1)
xs: Iterable[Int] = List(1)
scala> List(5) ++ xs
res0: List[Int] = List(5, 1)
Seq does have a prepend method, +:, and you could convert to a seq:
scala> 5 +: xs.toSeq
res1: Seq[Int] = List(5, 1)

How scala orders tuples?

I'm trying to understand how scala handles ordering and sorting of tuples
For example, if I got the list
val l = for {i <- 1 to 5} yield (-i,i*2)
Vector((-1,2), (-2,4), (-3,6), (-4,8), (-5,10))
scala knows how to sort it:
l.sorted
Vector((-5,10), (-4,8), (-3,6), (-2,4), (-1,2))
But tuple don't have a '<' method:
l.sortWith(_ < _)
error: value < is not a member of (Int, Int)
l.sortWith(_ < _)
How does scala know how to sort those tuples?
Because sorted have an implicit parameter ord:
def sorted[B >: A](implicit ord: math.Ordering[B]): List[A] Sorts
this sequence according to an Ordering.
The sort is stable. That is, elements that are equal (as determined by
lt) appear in the same order in the sorted sequence as in the
original.
ord the ordering to be used to compare elements.
and there is an implicit conversion defined in scala.math.Ordering:
implicit def Tuple2[T1, T2](implicit ord1: Ordering[T1],
ord2: Ordering[T2]): Ordering[(T1, T2)]
So l.sorted will be transformed to l.sorted(scala.math.Ordering.Tuple2[Int, Int]()).
Test it:
scala> def catchOrd[A](xs: A)(implicit ord: math.Ordering[A]) = ord
catchOrd: [A](xs: A)(implicit ord: scala.math.Ordering[A])scala.math.Ordering[A]
scala> catchOrd((1,2))
res1: scala.math.Ordering[(Int, Int)] = scala.math.Ordering$$anon$11#11bbdc80
And of course, you can defined your own Ordering:
scala> implicit object IntTupleOrd extends math.Ordering[(Int, Int)] {
| def compare(x: (Int, Int), y: (Int, Int)): Int = {
| println(s"Hi, I am here with x: $x, y: $y")
| val a = x._1*x._2
| val b = y._1*y._2
| if(a > b) 1 else if(a < b) -1 else 0
| }
| }
defined object IntTupleOrd
scala> Seq((1, 10), (3, 4), (2, 3)).sorted
Hi, I am here with x: (1,10), y: (3,4)
Hi, I am here with x: (3,4), y: (2,3)
Hi, I am here with x: (1,10), y: (2,3)
res2: Seq[(Int, Int)] = List((2,3), (1,10), (3,4))
EDIT There is a short way to make Tuple[Int, Int] support all the following methods: <, <=, >, >=.
scala> implicit def mkOps[A](x: A)(implicit ord: math.Ordering[A]): ord.Ops =
| ord.mkOrderingOps(x)
mkOps: [A](x: A)(implicit ord: scala.math.Ordering[A])ord.Ops
scala> (1, 2) < (3, 4)
res0: Boolean = true
scala> (1, 2) <= (3, 4)
res1: Boolean = true
scala> (1, 2, 3) <= (1, 2, 4)
res2: Boolean = true
scala> (3, 3, 3, 3) >= (3, 3, 3, 4)
res3: Boolean = false
#Eastsun's answer nicely explains the first part of your question "how does Scala sort tuples".
Regarding the second part "why doesn't tuple have a < method": In Scala, comparators like < either translate to native JVM comparisons for basic types (when comparing Int or Double etc) or to member functions of someClass with the type <(that: someClass): Boolean. This case is actually just syntactic sugar: someObject < otherObject translates to someObject.<(otherObject). If you want to have this functionality for tuples, you can bring an implicit class into scope, and map the comparison member function to the comparators provided by the Ordering:
implicit class ProvideComparator[T](t1: T)(implicit ord: Ordering[T]) {
def <(t2: T) = ord.lt(t1, t2)
def >(t2: T) = ord.gt(t1, t2) // and so on
}
Now you can just write:
scala> (1,2) < (2,2)
res2: Boolean = true

Why does this complex for comprehension fail? [duplicate]

Why does this construction cause a Type Mismatch error in Scala?
for (first <- Some(1); second <- List(1,2,3)) yield (first,second)
<console>:6: error: type mismatch;
found : List[(Int, Int)]
required: Option[?]
for (first <- Some(1); second <- List(1,2,3)) yield (first,second)
If I switch the Some with the List it compiles fine:
for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))
This also works fine:
for (first <- Some(1); second <- Some(2)) yield (first,second)
For comprehensions are converted into calls to the map or flatMap method. For example this one:
for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
becomes that:
List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
Therefore, the first loop value (in this case, List(1)) will receive the flatMap method call. Since flatMap on a List returns another List, the result of the for comprehension will of course be a List. (This was new to me: For comprehensions don't always result in streams, not even necessarily in Seqs.)
Now, take a look at how flatMap is declared in Option:
def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
Keep this in mind. Let's see how the erroneous for comprehension (the one with Some(1)) gets converted to a sequence of map calls:
Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
Now, it's easy to see that the parameter of the flatMap call is something that returns a List, but not an Option, as required.
In order to fix the thing, you can do the following:
for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
That compiles just fine. It is worth noting that Option is not a subtype of Seq, as is often assumed.
An easy tip to remember, for comprehensions will try to return the type of the collection of the first generator, Option[Int] in this case. So, if you start with Some(1) you should expect a result of Option[T].
If you want a result of List type, you should start with a List generator.
Why have this restriction and not assume you'll always want some sort of sequence? You can have a situation where it makes sense to return Option. Maybe you have an Option[Int] that you want to combine with something to get a Option[List[Int]], say with the following function: (i:Int) => if (i > 0) List.range(0, i) else None; you could then write this and get None when things don't "make sense":
val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None
for (i <- Some(5); j <- f(i)) yield j
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4))
for (i <- None; j <- f(i)) yield j
// returns: Option[List[Int]] = None
for (i <- Some(-3); j <- f(i)) yield j
// returns: Option[List[Int]] = None
How for comprehensions are expanded in the general case are in fact a fairly general mechanism to combine an object of type M[T] with a function (T) => M[U] to get an object of type M[U]. In your example, M can be Option or List. In general it has to be the same type M. So you can't combine Option with List. For examples of other things that can be M, look at subclasses of this trait.
Why did combining List[T] with (T) => Option[T] work though when you started with the List? In this case the library use a more general type where it makes sense. So you can combine List with Traversable and there is an implicit conversion from Option to Traversable.
The bottom line is this: think about what type you want the expression to return and start with that type as the first generator. Wrap it in that type if necessary.
It probably has something to do with Option not being an Iterable. The implicit Option.option2Iterable will handle the case where compiler is expecting second to be an Iterable. I expect that the compiler magic is different depending on the type of the loop variable.
I always found this helpful:
scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))
scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
foo.flatten
^
scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))
scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)
scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)
Since Scala 2.13 Option was made IterableOnce
sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable
so the following for comprehension works without the use of option2Iterable implicit conversion
scala> for {
| a <- List(1)
| b <- Some(41)
| } yield (a + b)
val res35: List[Int] = List(42)
scala> List(1).flatMap
final override def flatMap[B](f: Int => scala.collection.IterableOnce[B]): List[B]
where we see List#flatMap takes a function to IterableOnce. Desugaring above for comprehension we get something like
List(1).flatMap(a => Some(41).map(b => a + b))
which show the absence of the implicit conversion.
However in Scala 2.12 and before Option was not a traversable/iterable entity
sealed abstract class Option[+A] extends Product with Serializable
so the above for comprehension would desugar to something like
List(1).flatMap(a => option2Iterable(Some(41)).map(b => a + b))(List.canBuildFrom[Int])
where we see the implicit conversion.
The reason it does not work the other way around where for comprehension begins with Option and then we try to chain a List
scala> for {
| a <- Option(1)
| b <- List(41)
| } yield (a + b)
b <- List(41)
^
On line 3: error: type mismatch;
found : List[Int]
required: Option[?]
scala> Option(1).flatMap
final def flatMap[B](f: Int => Option[B]): Option[B]
is because Option#flatMap takes a function to Option and converting a List to Option probably does not make sense because we would lose elements for Lists with more than one element.
As szeiger explains
I think the recent Option changes actually make the for comprehensions
use case easier to understand because you do not need an implicit
conversion anymore. Option can be used on the RHS of a flatMap of any
collection type because it is IterableOnce (but not the opposite
because the RHS of Option#flatMap requires Option).