En la entrada anterior, Monocle III: lente Optional , realicé una descripción de la lente Optional, así como, la la descripción de unos ejemplos de uso. En la presente entrada, Monocle IV: lente Prism, me centraré en la lente Prism.
La lente Prism tiene un uso óptica para la selección de parte de un CoProducto (Suma); por ejemplo, sealed trait o Enum. La suma corresponde con la herencia de clases y objetos.
Prism tiene dos tipos de parámetros: Prism[S, A], S representa la Suma y A una parte de la suma. La lente está definida en el paquete monocle.Prism
Para los ejemplos, definiremos la siguiente jerarquía de clases y objetos
sealed trait Json case object JNull extends Json case class JStr(v: String) extends Json case class JNum(v: Double) extends Json case class JObj(v: Map[String, Json]) extends Json
Definición de un prisma
La definición de un prisma se puede realizar de dos formas posibles: la primera, utilizando pattern matching; y, la segunda, utilizando funciones parciales. Para los dos casos, se define el tipo de entrada y el tipo de salida. Así, tenemos los siguiente ejemplos definidos, respectivamente, con pattern matching y de forma parcial.
// Forma 1 val jStrForma1 = Prism[Json, String]{ case JStr(v) => Some(v) case _ => None }(JStr)
// Forma 2 val jStrForma2 = Prism.partial[Json, String]{case JStr(v) => v}(JStr)
Operaciones básicas
Creación de un objeto
Dada la estructura jerárquica Json definida previamente y los prismas definidos, se puede realizar la creación de los elementos de la siguiente forma descritos en los siguientes ejemplos:
println(s"1 Json String=${jStrForma1("hello")} ") println(s"2 Json String=${jStrForma2("hello")} ")
La salida por consola es la siguiente:
1 Json String=JStr(hello) 2 Json String=JStr(hello
Operación Set
Dada la estructura jerárquica Json definida previamente y los prismas definidos, la operación de asignación de un valor se realiza de la siguiente forma:
println(s"3 Set Json=${jStrForma1.set("Bar")(JStr("Hello"))}") println(s"5 Set 'Bar' en un tipo JNum=${jStrForma1.set("Bar")(JNum(10))}")
La salida por consola es la siguiente:
3 Set Json=JStr(Bar) 5 Set 'Bar' en un tipo JNum=JNum(10.0)
Operación Get
Dada la estructura jerárquica Json definida previamente y los prismas definidos, la operación de obtención de un valor se realiza de la siguiente forma:
println(s"1 Json String (JStr) a Option=${jStrForma1.getOption(JStr("Hello"))}") println(s"1 Json Double (JNum) a Option=${jStrForma1.getOption(JNum(3.2))}") // JNum no está definido en jStrForma1. println(s"2 Json String (JStr) a Option=${jStrForma2.getOption(JStr("Hello"))}") println(s"2 Json Double (JNum) a Option=${jStrForma2.getOption(JNum(3.2))}") // JNum no está definido en jStr.
La salida por consola es la siguiente:
1 Json String (JStr) a Option=Some(Hello) 1 Json Double (JNum) a Option=None 2 Json String (JStr) a Option=Some(Hello) 2 Json Double (JNum) a Option=None
Operación modify
Dada la estructura jerárquica Json definida previamente y los prismas definidos, la operación de modificación de un valor se realiza de la siguiente forma:
println(s"4 Modify Json=${jStrForma1.modify(_.reverse)(JStr("Hello"))}") println(s"6 Modify reverse de JNum(10)=${jStrForma1.modify(_.reverse)(JNum(10))}") println(s"7 ModifyOption String=${jStrForma1.modifyOption(_.reverse)(JStr("Hello"))}") println(s"8 ModifyOption Num=${jStrForma1.modifyOption(_.reverse)(JNum(10))}")
La salida por consola es la siguiente:
4 Modify Json=JStr(olleH) 6 Modify reverse de JNum(10)=JNum(10.0) 7 ModifyOption String=Some(JStr(olleH)) 8 ModifyOption Num=None
Composición de prismas
Monocle tiene definidos prismas de tipos básicos, como por ejemplo: double, bigInt, bigDecimal,…definidos en el paquete monocle.std.xxx
Con los primas existentes y con los prismas que definimos, podemos realizar composición de los mismos con la función composePrism. En el siguiente ejemplo, defino dos primas, siendo uno de ellos, una composición de un prisma que definido;y, además, el prisma de transformación de un elemento de tipo Double y un entero.
import monocle.std.double.doubleToInt // Prism[Double, Int] defined in Monocle val jNum: Prism[Json, Double] = Prism.partial[Json, Double]{case JNum(v) => v}(JNum) val jInt: Prism[Json, Int] = jNum composePrism doubleToInt println(s"9 Entero =${jInt(5)} ") println(s"9 Entero con Option=${jInt.getOption(JNum(5.0))}") println(s"9 Double con Option=${jInt.getOption(JNum(5.2))}") println(s"9 String con Option=${jInt.getOption(JStr("Hello"))}")
La salida por consola es la siguiente:
9 Entero =JNum(5.0) 9 Entero con Option=Some(5) 9 Double con Option=None 9 String con Option=None
Generadores de Prismas
Los generadores de prismas son macros existentes que facilitan la creación de Prismas. Los generadores de prismas están en el paquete monocle.macros.GenPrism. Así, la definición de un prisma mediante un generador, se realiza de la siguiente forma:
import monocle.Prism import monocle.macros.GenPrism val rawJNum: Prism[Json, JNum] = GenPrism[Json, JNum]
Para obtener un valor utilizando el prisma anterior, se realiza de la siguiente forma:
println(s"1 GenPrism JNum(10.0)=${rawJNum.getOption(JNum(10.0))}") println(s"2 GenPrism JStr('Prueba')=${rawJNum.getOption(JStr("Prueba"))}")
La salida por consola es la siguiente:
1 GenPrism JNum(10.0)=Some(JNum(10.0)) 2 GenPrism JStr('Prueba')=None
Además de los prismas, podemos utilizar otras lentes, como por ejemplo el generador de la lente Iso. En el siguiente ejemplo, se muestra un ejemplo de uso de las lentes prisma e Iso utilizando composición de lentes:
import monocle.macros.GenIso val jNum: Prism[Json, Double] = GenPrism[Json, JNum] composeIso GenIso[JNum, Double] val jNull: Prism[Json, Unit] = GenPrism[Json, JNull.type] composeIso GenIso.unit[JNull.type] println(s"3 GenPrism-GenIso=${jNum.getOption(JNum(10.0))}") println(s"4 GenPrism-GenIso=${jNull.getOption(JNum(10.0))}") println(s"5 GenPrism-GenIso=${jNum.getOrModify(JNum(10.0))}") println(s"6 GenPrism-GenIso=${jNum.getOrModify(JNum(10.0)).getOrElse(0.0)}")
La salida por consola es la siguiente:
3 GenPrism-GenIso=Some(10.0) 4 GenPrism-GenIso=None 5 GenPrism-GenIso=\/-(10.0) 6 GenPrism-GenIso=10.0
En la siguiente entrada, Monocle V: lente Travesal, describiremos la lente Traversal de la librería Monocle así como unos ejemplos prácticos.
Para el lector interesado, las entradas que he realizado sobre el tema son las siguientes: