Inicio una serie de entradas de la librería ZIO. En la presente entrada, ZIO I: presentación, realizaré una presentación y realizaré unos ejemplos básicos introductorios.
ZIO es aquella librería en Scala para ejecutar tareas asíncronas y tareas de programación concurrente la cual es una librería funcional pura. La librería está inspirada en la mónada IO de Haskell.

El tipo de dato ZIO está compuesta por tres parámetros como sigue: ZIO[R, E, A]. Los tipos tienen la siguiente definición semántica:
- R , Tipo de entorno.- El efecto requiere un tipo de entorno representado por R. Si el tipo está definido como Any, significa que no tiene requerimiento porque no necesitas un valor.
- E , Tipo de fallo.- El efecto puede terminar en error con un tipo definido en E. Si puede terminar con error, se define con el tipo Throwable; si no puede terminar con error, se define con el tipo Nothing.
- A, Tipo de éxito.- El efecto puede terminar con un tipo de éxito representado por el tipo A. Si el tipo es Unit, significa que el efecto no retorna información; si el tipo es Nothing, significa que el efecto está corriendo de forma indefinida
Unos ejemplos de definición de un tipo ZIO pueden ser los siguientes:
- ZIO[Any, IOException, String].- Definición de un tipo que no tiene un requerimiento, retorna un valor de tipo String y, si se produce un error, retorna un elemento de tipo IOException.
- ZIO[String, Throwable, Int].- Definición de un tipo con un requerimiento de tipo String, retorna un valor de tipo entero y, si se produce un error, retorna un elemento de tipo Throwable.
El requerimiento hay que visualizarlo como el valor de entrada al efecto para que sea procesado.
La librería ZIO define un conjunto de alias para poder trabajar de forma sencilla. Los alias definidos son las siguientes:
- IO[E, A].- IO es el alias de ZIO[Any, E, A]. Define un efecto que no tiene requerimientos, el error puede ser de tipo E y el resultado es de tipo A.
- UIO[A].- UIO es el alias de ZIO[Any, Nothing, A]. Define un efecto que no tiene requerimientos,
- URIO[R, A].- URIO es el alias de ZIO[R, Nothing, A]. Define un efecto que tiene un requerimiento de tipo R, no puede tener un error y el resultado es de tipo A.
- Task[A].- Task es el alias de ZIO[Any, Throwable, A]. Define un efecto que no tiene un requerimiento, el tipo de error es de tipo Throwable y el resultado es de tipo A.
- RIO[R, A].- RIO es el alias de ZIO[R, Throwable, A]. Define un efecto que tiene un requerimiento de tipo R, el tipo de error es de tipo Throwable y el resultado de de tipo A.
La definición de las dependencias de la librería ZIO en un proyecto gestionado con sbt son las siguientes:
val zio = "1.0.3"
lazy val zio_core = "dev.zio" %% "zio" % Versions.zio
lazy val zio_streams = "dev.zio" %% "zio-streams" % Versions.zio
lazy val zio_test = "dev.zio" %% "zio-test" % Versions.zio % "test"
lazy val zio_test_sbt = "dev.zio" %% "zio-test-sbt" % Versions.zio % "test"
lazy val zio_test_magnolia = "dev.zio" %% "zio-test-magnolia" % Versions.zio % "test"
La estructura de la entrada está compuesta de los siguientes apartados:
- Creación de efectos.
- Operaciones básicas.
1.- Creación de efectos
En el presente apartado, mostraré ejemplos básicos para la definición de efectos con ZIO. Son ejemplos muy simples pero son aclaratorios para dar los primeros pasos. La definición de efectos se muestra en los siguientes puntos:
- Efecto succeed.- empleamos la función succeed para crear un efecto cuyo resultado es exitoso.
val int42 = for {
intS1 <- ZIO.succeed(42)
} yield (intS1)
val resultInt42 = Runtime.default.unsafeRun(int42)
assert(42 === resultInt42)
- Efecto fail.- empleamos la función fail para crear un efecto cuyo resultado no es satisfactorio.
val f1: zio.URIO[Any, Either[String, Nothing]] = ZIO.fail("Uh oh!").either
val resultFailf1: Either[String, Nothing] = Runtime.default.unsafeRun(f1)
assertResult(resultFailf1)(Left("Uh oh!"))
- Efecto effectTotal.- empleamos la función effectTotal cuando estamos seguro que el efecto no tiene un efecto de lado.
val effectTotal: Task[Long] = ZIO.effectTotal(System.currentTimeMillis())
val resultEffectTotal: Long = Runtime.default.unsafeRun(effectTotal)
assert(resultEffectTotal > 0)
- Efecto fromOption.- empleamos la función fromOption para crear un efecto a partir de un tipo Option.
val zoption: IO[Option[Nothing], Int] = ZIO.fromOption(Some(2))
val resultZOption: Int = Runtime.default.unsafeRun(zoption)
assert(2 === resultZOption)
- Efecto fromEither.- empleamos la función fromEither para crear un efecto a partir de un tipo Either.
val zeither: IO[Either[Exception, String], String] = ZIO.fromEither(Right("Success"))
val resultZeither: String = Runtime.default.unsafeRun(zeither)
assert("Success" === resultZeither)
- Efecto fromTry.- empleamos la función fromTry para crear un efecto a partir de un tipo Try.
val ztry: Task[Int] = ZIO.fromTry(Try(40 / 2))
val resultZTry: Int = Runtime.default.unsafeRun(ztry)
assert(20 === resultZTry)
- Efecto fromFunction.- empleamos la función fromFuction para crear un efecto a partir de una función.
val zfun: URIO[Int, Int] = ZIO.fromFunction((i: Int) => i * i)
val resultZfun: Int = Runtime.default.unsafeRun(zfun.provide(5))
assert(25 === resultZfun)
- Efecto fromFuture.- empleamos la función fromFuture para crear un efecto a partir de un Future.
lazy val future = Future.successful("Hi!")
val zfuture: Task[String] = ZIO.fromFuture { implicit ec =>
future.map(_ => "Goodbye!")
}
val resultZFuture: String = Runtime.default.unsafeRun(zfuture)
assert("Goodbye!" === resultZFuture)
- Efecto desde un efecto de lado.- Definimos una función con un efecto de lado, en el ejemplo, escribir un texto en la consola.
def putStrLn(line: String): UIO[Unit] =
ZIO.effectTotal(println(line))
val resultPut: Unit = Runtime.default.unsafeRun(putStrLn("Test"))
assert(resultPut === ())
Para el lector interesado, puede acceder al código de los ejemplos del apartado mediante el siguiente enlace.
2.- Operaciones básicas
Una vez visto cómo podemos crear efectos en ZIO, estamos en disposición de mostrar ejemplos de operaciones básicas. Las operaciones consisten en la declaración de un programa con un conjunto de operaciones; esas operaciones, pueden ser puras, o bien, pueden tener efectos de lado, por ejemplo: la lectura desde consola, o bien, escribir en la salida estándar. El primer ejemplo que voy a mostrar es la definición de una función que muestre un mensaje por pantalla y la lectura de consola. Para realizar el programa, definiremos dos funciones: getStrlLn, función que realiza la lectura por consola; putStrLn, función que escribe un mensaje en la consola. La definición de las funciones son las siguientes:
val getStrLn: Task[String] = ZIO.effect(StdIn.readLine())
def putStrLn(line: String): UIO[Unit] = ZIO.effectTotal(println(line))
Una vez definido las funciones básicas, definimos el programa que realiza la concatenación de las funciones anteriores mediante la función exampleChaining y, como segunda opción, definimos el mismo programa utilizando for comprehension. Los snippet de las funciones son las siguientes:
def exampleChaining(): Unit = {
val operation1 = getStrLn.flatMap(input => putStrLn(s"-->${input}"))
Runtime.default.unsafeRun(operation1)
}
def exampleForComprenhensions(): Unit = {
val program = for {
_ <- putStrLn("Nombre")
name <- getStrLn
_ <- putStrLn(s"Value=${name}")
} yield ()
Runtime.default.unsafeRun(program)
}
Otra forma de encadenar efectos es utilizando la función zip, zipRight, o bien, zipLeft. En el siguiente ejemplo, se muestan ejemplos parecidos a los anteriores con la función zipRight y su alias *>. Hay que destacar que la función zipRight realiza la concatenación de efectos y, además, ejecuta una función map para tratar el resultado del primer efecto. El snippet del código es el siguiente:
def exampleZipping(): Unit = {
val zipRight1 = putStrLn("Name Right 1?").zipRight(getStrLn)
val resultZipRight1: String = Runtime.default.unsafeRun(zipRight1)
println(s"=>${resultZipRight1}")
val zipRight2 = putStrLn("Name Right 2?") *> getStrLn
val resultZipRight2: String = Runtime.default.unsafeRun(zipRight2)
println(s"=>${resultZipRight2}")
}
Para el lector interesado, puede acceder al código de los ejemplos del apartado mediante el siguiente enlace.
En la siguiente entrada, ZIO II: manejo de errores y recursos, continuaremos describiendo la librería ZIO centrándonos en cómo se manejan errores y recursos mediante.