user3825558
user3825558

Reputation: 585

Scala: How to return a Some or Option

I have the following piece of code that I am trying to enhance: I am using the java.nio.file package to represent a directory or a file as a Path. So here goes:

import java.nio.file.{Paths,DirectoryStream,Files,
                      Path,DirectoryIteratorException}

val path: Path = Paths.get(directoryPath) 
var directoryStream: Option[DirectoryStream[Path]] = None

// so far so good

try {
    directoryStream = Some(Files.newDirectoryStream(pathO))

    // this is where i get into trouble
    def getMeDirStream: DirectoryStream[Path] =
        if (!directoryStream.isEmpty && directoryStream.isDefined)
            getMeDirStream.get
        else
            None

    // invoke the iterator() method of dstream here
}

The above piece of code will not compile because I do not know what to return in the else, and right now, for the life of me, I can only come up with None, which the compiler simply does not like and I would like to learn what should be its replacement. I want this example to be a learning lesson of Option and Some for me.


Okay, this is where I choke. I would like to check if the directoryStream is not empty and is defined, and then if this is the case, I would like to invoke getMeDirStream.get to invoke the iterator() method on it. The API for Option tells me that invoking the get() method could result in a java.util.NoSuchElementException if the option is empty.

If the directoryStream is empty I want to return something and not None, because IntelliJ is telling me that "Expression of type None.type doesn't conform to expected type DirectoryStream[Path]".

Now, I am being all naive about this.

I would like to know the following:

  1. What should I return in the else other than None?

  2. Should I wrap the getMeDirStream.get in a try-catch with a java.util.NoSuchElementException, even though I am checking if the directoryStream is empty or not.?

  3. What is the purpose of a try-catch in the getMeDirStream.get, if there is indeed such a need?

  4. How can I clean up the above piece of code to incorporate correct checks for being isDefined and for catching appropriate exceptions?

Once I know what to return in the else (and after putting in the appropriate try-catch block if necessary), I would like to invoke the iterator() method on getMeDirStream to do some downstream operations.

Upvotes: 1

Views: 2304

Answers (2)

Chris Martin
Chris Martin

Reputation: 30736

Your specific questions are difficult to address because it's unclear exactly what you're trying to achieve. In particular, when you ask what the purpose of the try block is... Well, you wrote it, so only you can answer that.

In general, you never call get on an Option. You either use pattern matching:

option match {
    case Some(value) => /* ... */
    case None =>        /* ... */
}

or you use methods like map, flatMap, and foreach (or the equivalent comprehension syntax that gpampara's code uses).


My revision of gpampara's answer:

import scala.collection.convert.wrapAll._
import scala.util.Try
import java.nio.file.{Paths, Files, Path}

val getMeDirStream: Option[Iterator[Path]] =
  for {
    path <- Try(Paths.get("")).toOption
    directoryStream <- Try(Files.newDirectoryStream(path)).toOption
  } yield directoryStream.iterator

Changes:

  • Using Try(...).toOption instead of Either
  • Using implicits in scala.collection.convert to return the result as a Scala Iterator.

Try is similar to Option. Instead of Some and None, it has Success and Failure subtypes, and the failure case includes a Throwable, whereas None is just a singleton with no additional information.

Upvotes: 1

gpampara
gpampara

Reputation: 12049

Some and None are subtypes of Option, but to be more correct, they are actually two different cases of Option or data constructors. In other words, even though Scala allows you to directly invoke a Some or a None you should still regard their type to be Option. The more important thing to take from this is that you should never under any circumstance invoke Option#get as it is unsafe.

The intention of Option is to indicate the possibility that a value does not exist. If you care about the errors, then you should probably look at using Either instead (or Scalaz's Either called \/).

You can keep the computation within the Option context and then only extract the value later, or provide a default.

def fromTryCatch[A](a: => A): Either[Throwable, A] = try { Right(a) } catch { case e: Throwable => Left(e) }

val getMeDirStream: Option[java.util.Iterator[Path]] =
  for {
    path <- fromTryCatch(Paths.get(directoryPath)).toOption
    directoryStream <- fromTryCatch(Files.newDirectoryStream(pathO)).toOption
  } yield directoryStream.iterator()

Later, or right after, you can get the iterator, or provide a default value:

val iterator = getMeDirStream.getOrElse(java.util.Collections.emptyIterator[Path])

Upvotes: 2

Related Questions