How yield in for-comprehension works in Scala? - scala

Reading this article and realized that roughly all the functions after yield keyword is a map function. While all the statements within the for are flatMap functions. Why is that so? Why can the function after the yield be a flatMap function and also the functions within for are map functions?

Your first point is roughly correct. The statement
for (x <- list) yield f(x)
Is equivalent to
list.map(x => f(x))
But you are not quite right about how flatMap is used when there is more than one list.
The flatMap call is used if you want to iterate over another list at the same time:
for (
x <- list1
y <- list2
) yield f(x, y)
This is equivalent to
list1.flatMap(x => list2.map(y => f(x, y)))
The map call is wrapped in a flatMap call so that the result is a simple list. If the outer call was map then the result would be a nested list.
The inner call is always a map call, all the outer calls are flatMap. So
for (
x <- list1
y <- list2
z <- list3
) yield f(x, y, z)
Is
list1.flatMap(x => list2.flatMap(y => list3.map(z => f(x, y, z))))
In the end the best thing to do with for is to experiment with it until it does what you want, and after a while it will become intuitive.

Related

Fold function scala's immutable list

I have created an immutable list and try to fold it to a map, where each element is mapped to a constant string "abc". I do it for practice.
While I do that, I am getting an error. I am not sure why the map (here, e1 which has mutable map type) is converted to Any.
val l = collection.immutable.List(1,2,3,4)
l.fold (collection.mutable.Map[Int,String]()) ( (e1,e2) => e1 += (e2,"abc") )
l.fold (collection.mutable.Map[Int,String]()) ( (e1,e2) => e1 += (e2,"abc") )
<console>:13: error: value += is not a member of Any
Expression does not convert to assignment because receiver is not assignable.
l.fold (collection.mutable.Map[Int,String]()) ( (e1,e2) => e1 += (e2,"abc") )
At least three different problem sources here:
Map[...] is not a supertype of Int, so you probably want foldLeft, not fold (the fold acts more like the "banana brackets", it expects the first argument to act like some kind of "zero", and the binary operation as some kind of "addition" - this does not apply to mutable maps and integers).
The binary operation must return something, both for fold and foldLeft. In this case, you probably want to return the modified map. This is why you need ; m (last expression is what gets returned from the closure).
The m += (k, v) is not what you think it is. It attempts to invoke a method += with two separate arguments. What you need is to invoke it with a single pair. Try m += ((k, v)) instead (yes, those problems with arity are annoying).
Putting it all together:
l.foldLeft(collection.mutable.Map[Int, String]()){ (m, e) => m += ((e, "abc")); m }
But since you are using a mutable map anyway:
val l = (1 to 4).toList
val m = collection.mutable.Map[Int, String]()
for (e <- l) m(e) = "abc"
This looks arguably clearer to me, to be honest. In a foldLeft, I wouldn't expect the map to be mutated.
Folding is all about combining a sequence of input elements into a single output element. The output and input elements should have the same types in Scala. Here is the definition of fold:
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1
In your case type A1 is Int, but output element (sum type) is mutable.Map. So if you want to build a Map throug iteration, then you can use foldLeft or any other alternatives where you can use different input and output types. Here is the definition of foldLeft:
def foldLeft[B](z: B)(op: (B, A) => B): B
Solution:
val l = collection.immutable.List(1, 2, 3, 4)
l.foldLeft(collection.immutable.Map.empty[Int, String]) { (e1, e2) =>
e1 + (e2 -> "abc")
}
Note: I'm not using a mutabe Map

quicksort in ML

Without using case expressions (that comes in the next section of the class), I can't see why the following doesn't do a quicksort. It goes into a loop somewhere and never ends.
splitAt and append have already been thorughly tested, but here are the codes for them.
fun append(xs, ys) =
if null xs
then ys
else (hd xs) :: append(tl xs, ys)
fun splitAt(xs : int list, x : int) =
let
fun sp(ys : int list, more : int list, less : int list) =
if null ys
then (more, less)
else
if hd ys < x
then sp(tl ys, more, append(less, [hd ys]))
else sp(tl ys, append(more, [hd ys]), less)
in
sp(xs, [], [])
end
fun qsort(xs : int list) =
if length xs <= 1
then xs
else
let
val s = splitAt(xs, hd xs)
in
qsort(append(#2 s, #1 s))
end
And I get the same problem using append(qsort(#2 s), qsort(#1 s)), but I though the former was better style since it only require a single recursion with each round.
I guess I should say that 'splitAt' divides the list into greater than or equal to the second argument, and less than, and creates a tuple). Append concatenates 2 lists.
PS: This is only a practice problem, not a test or homework.
It goes into a loop somewhere and never ends.
Your problem is most likely qsort being called on a list that does not reduce in size upon recursive call. Perhaps go with append(qsort (#2 s), qsort (#1 s)). But even then, can you be sure that each of #1 s and #2 s will always reduce in size?
Ideally you should supply splitAt and append since they're not library functions. You might consider using the built-in append called # and the built-in List.partition to form splitAt.
Compare against this one found somewhere on the interwebs:
fun quicksort [] = []
| quicksort (x::xs) =
let
val (left, right) = List.partition (fn y => y < x) xs
in
quicksort left # [x] # quicksort right
end
Since this isn't homework ...
Note that if xs = [1,2] then splitAt(xs hd xs) returns ([1,2],[]) so the attempt to sort [1,2] by this version of qsort reduces to ... sorting [1,2] again. That will never terminate.
Instead, I would advocate applying splitAt to the tail of the xs. A minimal tweak to your code is to leave append and splitAt alone but to rewrite qsort as:
fun qsort(xs : int list) =
if length xs <= 1
then xs
else
let
val s = splitAt(tl xs, hd xs)
in
append(qsort(#2 s), (hd xs) :: qsort(#1 s))
end;
Then, for example,
- qsort [4,2,1,2,3,6,4,7];
val it = [1,2,2,3,4,4,6,7] : int list
It is crucial that you apply qsort twice then append the results. Tryng to apply qsort once to the appended split would be trying to reduce qsort to applying qsort to a list which is the same size as the original list.
SML really becomes fun when you get to pattern matching. You should enjoy the next chapter.

scala parallel collections not consistent

I am getting inconsistent answers from the following code which I find odd.
import scala.math.pow
val p = 2
val a = Array(1,2,3)
println(a.par
.aggregate("0")((x, y) => s"$y pow $p; ", (x, y) => x + y))
for (i <- 1 to 100) {
println(a.par
.aggregate(0.0)((x, y) => pow(y, p), (x, y) => x + y) == 14)
}
a.map(x => pow(x,p)).sum
In the code the a.par ... computes 14 or 10. Can anyone provide an explanation for why it is computing inconsistently?
In your "seqop" function, that is the first function you pass to aggregate, you define the logic that is used to combine elements within the same partition. Your function looks like this:
(x, y) => pow(y, p)
The problem is that you don't accumulate the results of a partition. Instead, you throw away your accumulator x. Every time you get 10 as a result, the calculation 2^2 was dropped.
If you change your function to take the accumulated value into account, you will get 14 every time:
(x, y) => x + pow(y, p)
The correct way to use aggregate is
a.par.aggregate(0.0)(
(acc, value) => acc + pow(value, 2), (acc1, acc2) => acc1 + acc2
)
By using (x,y) => pow(y,2) , you did not accumulate the item to the accumulator but just replaced the accumulator by pow(y,2).

Confused about behavior on Option between single-level and nested for comprehension

I'm new to Scala so please bear with me.
I'm confused about the behaviors below:
val l = List(Option(1))
for (i <- l; x <- i) yield x //Example 1: gives me List(1)
for(x <- Option(1)) yield x //Example 2: gives me Some(1)
Why doesn't the second for comprehension give me 1 instead? Because that would look more consistent to me, intuitively, since the second for comprehension in the first example x <- i looks like it should behave exactly the same way as the second example, as the second example basically has extracted the option out of the list to begin with.
Simply put, for comprehension wraps into the type that was used the first time.
for (x <- Option(1)) yield x // Returns Option
for (x <- List(1)) yield x // Returns List
for (x <- Array(1)) yield x // Returns Array
This:
for (i <- List(Some(1)); x <- i) yield x
Desugares into this:
List(Some(1)).flatMap { case i => i.map { case x => x } }
flatMap of List returns List[T], that's why it behaves like that

composition of the functions

I need to write some function NTimesComposition(f:(int * int -> int), n:int) which receives some function f and integer n and after doing composition of f, n times, like this f(x,(f(x,f(x,y)))) <- (here for example n = 3) I began to write it on smlnj, but it seems more complicated than I thought thanks in advance for any idea:
NTimesComposition(f:(int * int -> int), n:int)
if n = 1 then fn(x,y) => f(x, y ) else NTimesComposition...//here I'm stuck, must be recurstion
You already got it for n = 1 and you most likely just forgot to pass the (x, y) in the recursive call for n > 1. Obviously here it needs to be something of the form fn (x,y) => f (x, ...) where the ... part is where your recursive calls is going to be.
If you had forgot the (x,y) in the recursive part making it fn (x,y) => NTimesComposition (f, n-1) then you would end up building a chain of anonymous functions as "long" as your argument n describes. That would result in a different type of your NTimesComposition function depending on what n you supply which is not valid due to the way SML's type system works (Hindley-Milner).
The following two functions will do the job for you
fun foo (f, 1) = (fn xy => f xy)
| foo (f, n) = (fn (x,y) => f(x, foo (f, n-1) (x,y)))
and
fun baz (f, 1) xy = f xy
| baz (f, n) (x,y) = f(x, foo (f, n-1) (x,y))
where the first resembles your code the most using the anonymous function.