W.P. McNeill
W.P. McNeill

Reputation: 17026

Scala for comprehension of sequence inside a Try

I am writing a Scala program in which there is an operation that creates a sequence. The operation might fail, so I enclose it inside a Try. I want to do sequence creation and enumeration inside a for comprehension, so that a successfully-created sequence yields a sequence of tuples where the first element is the sequence and the second is an element of it.

To simplify the problem, make my sequence a Range of integers and define a createRange function that fails if it is asked to create a range of an odd length. Here is a simple for comprehension that does what I want.

import scala.util.Try

def createRange(n: Int): Try[Range] = {
  Try {
    if (n % 2 == 1) throw new Exception
    else Range(0, n)
  }
}

def rangeElements(n: Int) {
  for {
    r <- createRange(n)
    x <- r
  } println(s"$r\t$x")
}

def main(args: Array[String]) {
  println("Range length 3")
  rangeElements(3)

  println("Range length 4")
  rangeElements(4)
}

If you run this it correctly prints.

Range length 3
Range length 4
Range(0, 1, 2, 3)   0
Range(0, 1, 2, 3)   1
Range(0, 1, 2, 3)   2
Range(0, 1, 2, 3)   3

Now I would like to rewrite my rangeElements function so that instead of printing as a side-effect it returns a sequence of integers, where the sequence is empty if the range was not created. What I want to write is this.

def rangeElements(n: Int):Seq[(Range,Int)] = {
  for {
    r <- createRange(n)
    x <- r
  } yield (r, x)
}
// rangeElements(3) returns an empty sequence
// rangeElements(4) returns the sequence (Range(0,1,2,3), 0), (Range(0,1,2,3), 1) etc.

This gives me two type mismatch compiler errors. The r <- createRange(n) line required Seq[Int] but found scala.util.Try[Nothing]. The x <- r line required scala.util.Try[?] but found scala.collection.immutable.IndexedSeq[Int].

Presumably there is some type erasure with the Try that is messing me up, but I can't figure out what it is. I've tried various toOption and toSeq qualifiers on the lines in the for comprehension to no avail.

If I only needed to yield the range elements I could explicitly handle the Success and Failure conditions of createRange myself as suggested by the first two answers below. However, I need access to both the range and its individual elements.

I realize this is a strange-sounding example. The real problem I am trying to solve is a complicated recursive search, but I don't want to add in all its details because that would just confuse the issue here.

How do I write rangeElements to yield the desired sequences?

Upvotes: 0

Views: 1918

Answers (2)

Brian
Brian

Reputation: 20285

The Try will be Success with a Range when n is even or a Failure with an Exception when n is odd. In rangeElements match and extract those values. Success will contain the valid Range and Failure will contain the Exception. Instead of returning the Exception return an empty Seq.

import scala.util.{Try, Success, Failure}

def createRange(n: Int): Try[Range] = {
  Try {
    if (n % 2 == 1) throw new Exception
    else Range(0, n)
  }
}

def rangeElements(n: Int):Seq[Tuple2[Range, Int]] = createRange(n) match {
  case Success(s) => s.map(xs => (s, xs))
  case Failure(f) => Seq()
}    

scala> rangeElements(3)
res35: Seq[(Range, Int)] = List()

scala> rangeElements(4)
res36: Seq[(Range, Int)] = Vector((Range(0, 1, 2, 3),0), (Range(0, 1, 2, 3),1), (Range(0, 1, 2, 3),2), (Range(0, 1, 2,3),3))

Upvotes: 1

Fynn
Fynn

Reputation: 4873

The problem becomes clear if you translate the for comprehension to its map/flatMap implementation (as described in the Scala Language Spec 6.19). The flatMap has the result type Try[U] but your function expects Seq[Int].

for {
  r <- createRange(n)
  x <- r
} yield x

createRange(n).flatMap {
  case r => r.map {
    case x => x
  }
}

Is there any reason why you don't use the getOrElse method?

def rangeElements(n: Int):Seq[Int] =
  createRange(n) getOrElse Seq.empty

Upvotes: 3

Related Questions