'getStrLn in ZIO working with flatMap but not inside for comprehension

val askNameFlatMap: ZIO[Console, IOException, Unit] = putStrLn("What is your Name? ") *>
    getStrLn.flatMap(name => putStrLn(s"Hello $name"))

val askNameFor: ZIO[Console, IOException, Unit] = {
    val getName: ZIO[Console, IOException, String] = getStrLn
    for {
      _ <- putStrLn("What is your Name? ")
      name: String <- getName
      _ <- putStrLn(s"Hello $name")
    } yield ()
  }

The version using flatMap is successfully working but when I try to run the for comprehension version then I am getting this error

Pattern guards are only supported when the error type is a supertype of NoSuchElementException. However, your effect has java.io.IOException for the error type. name: String <- getName



Solution 1:[1]

You need to remove type ascription from your name argument. Type ascription has special meaning in for comprehensions. By saying

  for
    name: String <- getStrLn

it translates to is something like getStrLn.withFilter(_.isInstanceOf[String])

withFilter is provided by ZIO implicit but only if Error channel allows to signal about missing elements. Basically your E should be supertype of NoSuchElementException, i.e. Exception or Throwable. You have IOException and that's why it's not resolving properly.

Solution 2:[2]

A for-comprehension is a chain of flatMap and map. Let's go line by line and follow the arrows.

for {
  _ <- putStrLn("What is your Name? ")
  name <- getName
  _ <- putStrLn(s"Hello $name")
} yield ()

Here is what it would look like in a flatMap.

Note that the flow is top to bottom, right to left (follow the arrows). The arrow point towards the the parameter in the anonymous function that you put inside the next flatMap.

Also note that underscore _ means we discard the parameter.

putStrLn("What is your Name? ")
  .flatMap { _ => getName
    .flatMap { name => putStrLn(s"Hello $name")
      .map(_ => Unit)
    }
  } 

A for-comprehension ends with a yield. That is the map inside the nested flatMap. Note that () is the same as Unit. We could also a useful value.

Before we look into how flatMap and map is implemented in ZIO, let's first look briefly at the ZIO type.

ZIO[R, E, A]

A ZIO effect depends on an enviroment R and can either fail with an error E or succeed with a value A. A is the most important in your example, so let's focus on that.

putStrLn("What is your Name? ") can succeed with a value Unit. It is expected after-all that printing to the console would not return any meaningful value. getName on the other hand, succeeds with a String. It makes sense that successfully getting using input from the console results in a String.

So how does the flatMap work?

def flatMap[R1 <: R, E1 >: E, B](k: (A) ? ZIO[R1, E1, B])(implicit trace: Trace): ZIO[R1, E1, B]

From ZIO[R, E, A] we must give a function (A) ? ZIO[R1, E1, B]. It means we take the success value of the previous ZIO effect and turn it into a new ZIO effect. Sometimes we care about A will give our transforming function a named parameter like:

putStrLn("What is your Name? ").flatMap(name => putStrLn(s"Hello $name").

Sometimes we don't care about A and we discard it:

putStrLn("What is your Name? ").flatMap(_ => getName /* code */ )

Finally there is the map

def map[B](f: (A) ? B)(implicit trace: Trace): ZIO[R, E, B]

At the end of our for-comprehension, we have to decide what to yield. It is optional to use the yield. Here is another example where we yield something useful:

case class Person(firstName: String, lastName: String)

val signUpForm = for {
  _ <- putStrLn("What is your first name? ")
  firstName <- getStrLn
  _ <- putStrLn(s"What is your last name? ")
  lastName <- getStrLn
} yield Person(firstName, lastName)

As a final note, you used *>, also called zipRight in the first code snippet.

final def *>[R1 <: R, E1 >: E, B](that: ? ZIO[R1, E1, B])(implicit trace: Trace): ZIO[R1, E1, B]

A variant of flatMap that ignores the value produced by this effect.

It is quite common to values when chaining effects, like when prompting a user for input. Too many nested flatMaps can be hard to read. *> is a good alternative here and there are many more combinators for different scenarios.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 SimY4
Solution 2 Alexander Wiklund