Translate
sábado, 21 de septiembre de 2019
Charla sobre Apache Cassandra
El día martes 24 de Septiembre Hexacta dictará a través de la plataforma de streaming provista por la Universidad Tecnológica Nacional Facultad Regional Paraná una charla sobre Apache Cassandra. Los esperamos!
Formulario de inscripción: https://bit.ly/2m3ZLsE
jueves, 19 de septiembre de 2019
Funciones con funciones como parámetros en Scala
En Scala una función puede tomar otra función como parámetro. Veamos un ejemplo:
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
Como vemos el parámetro puede ser cualquier función que reciba y devuelva un Doble. La función valueAtOneQuarter calcula el valor de esa función en 0.25.
Por ejemplo,
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5 (because 0.5 × 0.5 = 0.25)
¿Cuál es el tipo de valueAtOneQuarter? Es una función con un parámetro, por lo que su tipo se escribe como :
(parameterType) => resultType
El resultType es claramente Double, y el parameterType ya se encuentra en el encabezado de la función como (Double) => Double. Por lo tanto, el tipo de valueAtOneQuarter es :
((Double) => Double) => Double
Como valueAtOneQuarter es una función que recibe una función, se denomina función de orden superior.
Una función de orden superior también puede producir una función, es decir el resultado es una función. Veamos un ejemplo :
def mulBy(factor : Double) = (x : Double) => factor * x
Esta función nos permite obtener otra función que multiplica un número por el factor, veamos un ejemplo de uso :
val quintuple = mulBy(5)
quintuple(20) // 100
La función mulBy tiene un parámetro de tipo Double, y devuelve una función de tipo (Double) => Double. Por lo tanto, su tipo es
(Double) => ((Double) => Double)
Esta es una de las características que me gustan mucho de scala que contamos con funciones de orden superior de tipado estático y nos ayuda la inferencia de tipo.
def valueAtOneQuarter(f: (Double) => Double) = f(0.25)
Como vemos el parámetro puede ser cualquier función que reciba y devuelva un Doble. La función valueAtOneQuarter calcula el valor de esa función en 0.25.
Por ejemplo,
valueAtOneQuarter(ceil _) // 1.0
valueAtOneQuarter(sqrt _) // 0.5 (because 0.5 × 0.5 = 0.25)
¿Cuál es el tipo de valueAtOneQuarter? Es una función con un parámetro, por lo que su tipo se escribe como :
(parameterType) => resultType
El resultType es claramente Double, y el parameterType ya se encuentra en el encabezado de la función como (Double) => Double. Por lo tanto, el tipo de valueAtOneQuarter es :
((Double) => Double) => Double
Como valueAtOneQuarter es una función que recibe una función, se denomina función de orden superior.
Una función de orden superior también puede producir una función, es decir el resultado es una función. Veamos un ejemplo :
def mulBy(factor : Double) = (x : Double) => factor * x
Esta función nos permite obtener otra función que multiplica un número por el factor, veamos un ejemplo de uso :
val quintuple = mulBy(5)
quintuple(20) // 100
La función mulBy tiene un parámetro de tipo Double, y devuelve una función de tipo (Double) => Double. Por lo tanto, su tipo es
(Double) => ((Double) => Double)
Esta es una de las características que me gustan mucho de scala que contamos con funciones de orden superior de tipado estático y nos ayuda la inferencia de tipo.
domingo, 15 de septiembre de 2019
Slice en Go parte 2
Continuamos con Slice en go
Es común agregar nuevos elementos a un Slice, por lo que Go proporciona una función de adición:
func append (s [] T, vs ... T) [] T
El primer parámetro s de append es un slice de tipo T, y el resto son valores T para agregar al slice.
El valor resultante de append es un segmento que contiene todos los elementos del segmento original más los valores proporcionados.
Si la capacidad es menor a los elementos a agregar esta se ajustara automáticamente. El Slice devuelto apuntará a la matriz recién asignada.
Como podemos iterar por nuestro slice? para eso esta el for que itera sobre un slice o un mapa. Para esto tambien debemos utilizar la función range, veamos un ejemplo:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
Cuando se itera por un slice, se devuelven dos valores para cada iteración. El primero es el índice, y el segundo es una copia del elemento en ese índice.
Puede omitir el índice o el valor asignándole a _.
for i, _ := range pow
for _, value := range pow
Si solo desea el índice, puede omitir la segunda variable.
for i: = rango pow
veamos un ejemplo:
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
Es común agregar nuevos elementos a un Slice, por lo que Go proporciona una función de adición:
func append (s [] T, vs ... T) [] T
El primer parámetro s de append es un slice de tipo T, y el resto son valores T para agregar al slice.
El valor resultante de append es un segmento que contiene todos los elementos del segmento original más los valores proporcionados.
Si la capacidad es menor a los elementos a agregar esta se ajustara automáticamente. El Slice devuelto apuntará a la matriz recién asignada.
Como podemos iterar por nuestro slice? para eso esta el for que itera sobre un slice o un mapa. Para esto tambien debemos utilizar la función range, veamos un ejemplo:
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
Cuando se itera por un slice, se devuelven dos valores para cada iteración. El primero es el índice, y el segundo es una copia del elemento en ese índice.
Puede omitir el índice o el valor asignándole a _.
for i, _ := range pow
for _, value := range pow
Si solo desea el índice, puede omitir la segunda variable.
for i: = rango pow
veamos un ejemplo:
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
Slice en Go
No voy a escribir un conjunto de post de go que permitan iniciarse en el lenguaje por que para ser sincero, no me gusta. A mi forma de ver las cosas, es un c mejorado, es decir es bastante de bajo nivel, que esta bien para hacer un montón de cosas, salvo las que hago. Por lo tanto, voy a hablar de las características del lenguaje que me llamaron la atención. Ahora voy a ver Slice.
Slice es como vectores dinámicos de c pero con muchas más utilidades.
Slice es flexible y de tamaño dinámico. En la práctica, Slice son mucho más comunes que los vectores, dado que no necesitan una cantidad fija de elementos.
El tipo [] T es un Slice con elementos del tipo T.
Se puede definir un Slice con dos índices, el inicio y el final, separados por dos puntos:
a [bajo: alto]
Esto selecciona un rango que incluye el primer elemento, pero excluye el último.
La siguiente expresión crea un Slice que incluye los elementos 1 a 3 :
a [1: 4]
Veamos un ejemplo:
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
Lo que esta en negrita es la declaración del Slice.
Un Slice no almacena ningún dato, solo describe una sección de un vector subyacente. Se puede ver como un puntero o referencia.
Cambiar los elementos de un segmento modifica los elementos correspondientes del vector subyacente. Otros Slice que comparten el mismo vector subyacente verán esos cambios.
Veamos un ejemplo:
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
También podemos crear nuestros Slice por defecto usando { }, por ejemplo:
[] bool {verdadero, verdadero, falso}
Al cortar, puede omitir los límites superior o inferior para utilizar sus valores predeterminados.
El valor predeterminado es cero para el límite inferior y la longitud del segmento para el límite superior.
Para vector
var a [10] int
Estas expresiones de corte son equivalentes:
a [0:10]
a [: 10]
a [0:]
una[:]
El valor predeterminado es cero para el límite inferior y la longitud del segmento para el límite superior.
Para vector
var a [10] int
Estas expresiones de corte son equivalentes:
a [0:10]
a [: 10]
a [0:]
una[:]
Un Slice tiene tanto una longitud como una capacidad.
La longitud de un Slice es la cantidad de elementos que contiene.
La capacidad es el número de elementos en el vector subyacente (como tener un vector de respaldo para no tener que pedir memoria cada vez que necesitamos agregar un elemento) , contando desde el primer elemento del Slice.
La longitud y la capacidad de un Slice se pueden obtener utilizando las expresiones len y cap.
El valor cero de un slice es nulo. Y un slice nulo tiene una longitud y capacidad de 0 y no tiene un vector subyacente.
Se pueden crear Slices con una función de creación llamada make; así es como se crean vectores de tamaño dinámico.
La función make crea un Slice con valores 0:
a: = make ([] int, 5) // len (a) = 5
Para especificar una capacidad, pase un tercer argumento para hacer:
b: = make ([] int, 0, 5) // len (b) = 0, cap (b) = 5
b = b [: cap (b)] // len (b) = 5, cap (b) = 5
b = b [1:] // len (b) = 4, cap (b) = 4
Y por ahora eso es todo, continuará....
martes, 10 de septiembre de 2019
Libros de Java code geeks
Reforzando el aprendizaje con MATLAB: Entendiendo los conceptos básicos y configurando el entorno
Me llego un libro de Matlab sobre los conceptos básicos y la configuración del entorno, y como soy tan bueno lo comparto con ustedes :
|
Guía definitiva para Plataforma de contenedores empresariales o Guía de Docker para empresas
Me llego por mail la Guía definitiva para Plataforma de contenedores empresariales o en criollo Docker para empresas. Y lo comparto con ustedes :
| ||||||||||
| ||||||||||
| ||||||||||
|
domingo, 8 de septiembre de 2019
ENCUESTA DE SUELDOS 2019
La empresa openqube ha hecho una encuesta de sueldo de 2019 en argentina, hay muy buenos datos.
Dejo link: https://openqube.io/encuesta-sueldos-2019.02
viernes, 6 de septiembre de 2019
Scala mixea el mundo orientado a objeto y el mundo funcional.
Scala mixea el mundo orientado a objeto y el mundo funcional. Por esa razón en scala las funciones son ciudadanos de primera clase, es decir que una función es solo otro tipo de dato.
En scala podemos guardar una función en una variable :
import scala.math._
val num = 3.14
val fun = ceil _
Como vemos num vale 3.14 y fun vale ceil _ que es una función.
El _ indica que puede recibir cualquier parámetro, es decir que podemos llamarlo como queramos.
Si ejecutamos este ejemplo con el RELP num va ser de tipo Double y fun (Double) => Double, lo que significa que es una función que recibe un Double y retorna un Double.
Si utilizamos la sintaxis anterior podemos definir la función charAt de la siguiente forma :
val f = (_: String).charAt(_: Int) // (String, Int) => Char
Otra posible definición puede ser :
val f: (String, Int) => Char = _.charAt(_)
Que puedo hacer con mi variable fun?
Llamar la función :
fun(num) // 4.0
Pasarla por parámetros o retornarla desde una función.
Array(3.14, 1.42, 2.0).map(fun) // Array(4.0, 2.0, 2.0)
Funciones Anónimas: En scala, tambien tenemos funciones anónimas y estas se pueden definir de este modo:
(x: Double) => 3 * x
Por supuesto se puede guardar en una variable :
val triple = (x: Double) => 3 * x
O lo podemos definir así :
def triple(x: Double) = 3 * x
Pero si queremos utilizarla sin nombre podemos hacer :
Array(3.14, 1.42, 2.0).map((x: Double) => 3 * x)
// Array(9.42, 4.26, 6.0)
También podemos utilizar las llaves :
Array(3.14, 1.42, 2.0).map{ (x: Double) => 3 * x }
Otra forma de escribirlo de forma infija y sin utilizar el punto :
Array(3.14, 1.42, 2.0) map { (x: Double) => 3 * x }
Por ahora eso es totototodo amigos!!
martes, 3 de septiembre de 2019
Optional, la solución de Java 8 para NullPointerException, Parte 3
El patrón Optional es un patrón nacido en los lenguajes funcionales (sobre todo Scala), por lo que usarlo de una manera imperativa hace que no sea eficiente.
Optional<Double> getDurationOfAlbumWithName(String name) {
Optional<Double> duration = getAlbum(name)
.flatMap((album) -> getAlbumTracks(album.getName()))
.map((tracks) -> getTracksDuration(tracks));
return duration;
}
Usando las funciones map y flatMap acortaríamos un código relativamente complicado a solo 3 lineas, vamos a repasar paso por paso donde está el truco.
Para ello vamos a ver el método map de la clase Optional que es lo que hace:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
Dentro de esa signatura complicada se encuentra algo bastante sencillo, es un método que admite una función, la cual a su vez admite un Optional, esta función ha de devolver un valor del tipo que acepta. Lo que hace la función es más fácil de ver, comprueba si el Optional está vacío, si lo está devuelve un Optional vacío y si no, aplica la función que le hemos pasado por parámetro, pasándole el valor del Optional
Es decir, si el Optional está vacío, el método map no hace nada, esto es primordial para poder concatenar operaciones sin necesidad de comprobar a cada momento si el
Optional está vacío.
Antes de seguir con ejemplo, comentar el uso del método flatMap, y es que cuando queremos encadenar distintas operaciones que devuelvan Optional, es necesario usar
flatMap ya que si no acabaríamos teniendo un Optional<Optional<Double>>
Si lo extraemos paso a paso, podemos ver que no hay magia por ningún lado:
Optional<Album> albumOptional = getAlbum(name);
Optional<List<Track>> listOptional = albumOptional.flatMap((album) -> getAlbumTracks(album.getName()));
Optional<Double> durationOptional = listOptional.map((tracks) -> getTracksDuration(tracks));
Optional, la solución de Java 8 para NullPointerException, Parte 2
Vamos a ver como podemos usar Optional de manera imperativa, es decir como java fue concebido
Pongamos el caso siguiente, tenemos un método que obtiene un disco a partir de un nombre, puede darse el caso de que no se encuentre ningún disco con ese nombre, sin Optional tendríamos dos opciones:
Con Optional se nos abre la tercera opción:
A la hora de usar este método, de la manera imperativa haríamos lo siguiente.
Album album;
Optional<Album> albumOptional = getAlbum("Random Memory Access");
if(albumOptional.isPresent()){
album = albumOptional.get();
}else{
// Avisar al usuario de que no se ha encontrado el album
}
Esto ya es un avance respecto a los null, ya que estamos indicando explícitamente al usuario de la API de que es posible que no se encuentre el album y de que es necesario que actúe en caso de error.
El problema de esto viene cuando queremos ejecutar varias operaciones consecutivas que devuelvan null. Para ilustrar este caso imaginémonos que después de obtener el Album queremos obtener las canciones del album y finalmente obtener la duración total del album.
private static Optional<Double> getDurationOfAlbumWithName(String name) {
Album album;
Optional<Album> albumOptional = getAlbum(name);
if (albumOptional.isPresent()) {
album = albumOptional.get();
Optional<List<Track>> tracksOptional = getAlbumTracks(album.getName());
double duration = 0;
if (tracksOptional.isPresent()) {
List<Track> tracks = tracksOptional.get();
for (Track track : tracks) {
duration += track.getDuration();
}
return Optional.of(duration);
} else {
return Optional.empty();
}
} else {
return Optional.empty();
}
Como podemos observar esto se nos puede ir de las manos muy rápidamente, cada operación sucesiva que hagamos sobre un método que puede devolver un valor vacío se convierte en un nivel más de anidación.
Llegados a este punto podemos pensar en usar excepciones, las cuales al menos nos permiten tener todas las acciones a la misma altura dentro de un try y resolver los distintos errores en el catch. Pero esto no es tan flexible tampoco, es hora de pensar en funcional.
Pongamos el caso siguiente, tenemos un método que obtiene un disco a partir de un nombre, puede darse el caso de que no se encuentre ningún disco con ese nombre, sin Optional tendríamos dos opciones:
- Devolver null en caso de que no encontrásemos el disco.
- Lanzar una excepción indicando que no se ha encontrado el disco.
Con Optional se nos abre la tercera opción:
- public Optional<Album> getAlbum(String artistName)
A la hora de usar este método, de la manera imperativa haríamos lo siguiente.
Album album;
Optional<Album> albumOptional = getAlbum("Random Memory Access");
if(albumOptional.isPresent()){
album = albumOptional.get();
}else{
// Avisar al usuario de que no se ha encontrado el album
}
Esto ya es un avance respecto a los null, ya que estamos indicando explícitamente al usuario de la API de que es posible que no se encuentre el album y de que es necesario que actúe en caso de error.
El problema de esto viene cuando queremos ejecutar varias operaciones consecutivas que devuelvan null. Para ilustrar este caso imaginémonos que después de obtener el Album queremos obtener las canciones del album y finalmente obtener la duración total del album.
private static Optional<Double> getDurationOfAlbumWithName(String name) {
Album album;
Optional<Album> albumOptional = getAlbum(name);
if (albumOptional.isPresent()) {
album = albumOptional.get();
Optional<List<Track>> tracksOptional = getAlbumTracks(album.getName());
double duration = 0;
if (tracksOptional.isPresent()) {
List<Track> tracks = tracksOptional.get();
for (Track track : tracks) {
duration += track.getDuration();
}
return Optional.of(duration);
} else {
return Optional.empty();
}
} else {
return Optional.empty();
}
Como podemos observar esto se nos puede ir de las manos muy rápidamente, cada operación sucesiva que hagamos sobre un método que puede devolver un valor vacío se convierte en un nivel más de anidación.
Llegados a este punto podemos pensar en usar excepciones, las cuales al menos nos permiten tener todas las acciones a la misma altura dentro de un try y resolver los distintos errores en el catch. Pero esto no es tan flexible tampoco, es hora de pensar en funcional.
Optional, la solución de Java 8 para NullPointerException
Quien no ha sufrido un NullPointerException, no ha programado en Java, a veces en partes del código donde parece imposible que sucedan, posiblemente ese NullPointerException se ha estado gestando desde otras partes lejanas de la aplicación.
Para corregir estos errores, algunos lenguajes han decidido eliminar por completo los temidos null, pero para aquellos lenguajes que en su momento lo incluyeron, no es tan fácil. De ahí la existencia de alternativas cómo el patrón Optional, el cual nos permite mostrar de manera explicita (mediante el sistema de tipos) la posibilidad de que un método pueda no devolver el valor deseado. Esto nos obliga a controlar la posible ausencia de valor de manera explicita, permitiéndonos elegir un valor alternativo en caso de dicha ausencia o simplemente realizar otra acción.
En Java 8 este patrón se encapsula en la clase Optional, la cual incluye muchos de los métodos necesarios para trabajar con este patrón.
public final class Optional<T>{}
Lo más importante es la signatura de la clase, en la cual podemos ver que es una clase genérica que nos permite que el objeto que contenga (o no) sea de cualquier clase.
Optional tiene un constructor privado, y nos proporciona tres métodos factoría estáticos para instanciar la clase. Siendo el método .of el que nos permite recubrir cualquier objeto en un optional.
public static<T> Optional<T> empty()
public static <T> Optional<T> ofNullable(T value)
public static <T> Optional<T> of(T value)
Los otros dos métodos nos permiten recubrir un valor nulo o devolver un objeto Optional vacío en caso de que queramos avisar de la ausencia de valor. La opción de recubrir un nulo viene dada principalmente para permitirnos trabajar con APIs que hacen uso de nulos para avisar de estas ausencias de valor.
El método isPresent es el equivalente a variable == null y cómo el propio nombre indica nos dice si el objeto Optional contiene un valor o está vacío. Este método se usa principalmente si trabajamos de manera imperativa con Optional. Y el método get es el encargado de devolvernos el valor, devolviendo una excepción si no estuviera presente.
Estos tres métodos hacen que trabajar con Optional sea verdaderamente interesante, nos da la posibilidad de encadenar distintas operaciones que devuelvan Optional sin tener que estar comprobando si el valor está presente después de cada operación.
Finalmente estos métodos nos permiten finalizar una serie de operaciones, teniendo tres maneras:
Para corregir estos errores, algunos lenguajes han decidido eliminar por completo los temidos null, pero para aquellos lenguajes que en su momento lo incluyeron, no es tan fácil. De ahí la existencia de alternativas cómo el patrón Optional, el cual nos permite mostrar de manera explicita (mediante el sistema de tipos) la posibilidad de que un método pueda no devolver el valor deseado. Esto nos obliga a controlar la posible ausencia de valor de manera explicita, permitiéndonos elegir un valor alternativo en caso de dicha ausencia o simplemente realizar otra acción.
En Java 8 este patrón se encapsula en la clase Optional, la cual incluye muchos de los métodos necesarios para trabajar con este patrón.
public final class Optional<T>{}
Lo más importante es la signatura de la clase, en la cual podemos ver que es una clase genérica que nos permite que el objeto que contenga (o no) sea de cualquier clase.
Optional tiene un constructor privado, y nos proporciona tres métodos factoría estáticos para instanciar la clase. Siendo el método .of el que nos permite recubrir cualquier objeto en un optional.
public static<T> Optional<T> empty()
public static <T> Optional<T> ofNullable(T value)
public static <T> Optional<T> of(T value)
Los otros dos métodos nos permiten recubrir un valor nulo o devolver un objeto Optional vacío en caso de que queramos avisar de la ausencia de valor. La opción de recubrir un nulo viene dada principalmente para permitirnos trabajar con APIs que hacen uso de nulos para avisar de estas ausencias de valor.
El método isPresent es el equivalente a variable == null y cómo el propio nombre indica nos dice si el objeto Optional contiene un valor o está vacío. Este método se usa principalmente si trabajamos de manera imperativa con Optional. Y el método get es el encargado de devolvernos el valor, devolviendo una excepción si no estuviera presente.
- public Optional<T> filter(Function f)
- public<U> Optional<U> map(Function f)
- public<U> Optional<U> flatMap(Function f)
Estos tres métodos hacen que trabajar con Optional sea verdaderamente interesante, nos da la posibilidad de encadenar distintas operaciones que devuelvan Optional sin tener que estar comprobando si el valor está presente después de cada operación.
- public T orElse(T other)
- public T orElseGet(Function f)
- public <X extends Throwable> T orElseThrow(Function f)
Finalmente estos métodos nos permiten finalizar una serie de operaciones, teniendo tres maneras:
- La primera orElse nos devuelve el valor o si no devolverá el valor que le demos.
- orElseGet, nos devolverá el valor si está presente y si no, invocará la función que le pasemos por parámetro y devolverá el resultado de dicha función.
- Y finalmente orElseThrow, nos devolverá el valor si está presente y si invocará la función que le pasemos, la cual tiene que devolver una excepción y lanzará dicha excepción. Esto nos ayudará a trabajar en conjunción con APIs que todavía usen excepciones.
domingo, 1 de septiembre de 2019
Inyección de dependencia de tiempo de compilación parte 2
Y como dije en el post anterior vamos a ver el cake pattern.
En algún momento, crear todo el grafo de objetos de "todo el mundo" será poco práctico y el código será grande y difícil de leer. Entonces deberíamos dividirlo de alguna manera en pedazos más pequeños. Afortunadamente, los trait de Scala encajan perfectamente para esa tarea; se pueden usar para dividir el código de creación del grafo de objetos.
En cada trait, que para el propósito de esta tarea también se llama "módulo", se crea parte del grafo de objetos. Todo se vuelve a combinar más tarde al unir todos los traits necesarios.
Puede haber varias reglas sobre cómo dividir el código en módulos. Un buen lugar para comenzar es considerar crear un módulo precableado por paquete. Cada paquete debe contener un grupo de clases que compartan o implementen alguna funcionalidad específica. Lo más probable es que estas clases cooperen de alguna manera y, por lo tanto, pueden conectarse.
El beneficio adicional de enviar un paquete no solo con el código, sino también con un fragmento de grafo de objeto conectado, es que es más claro cómo se debe usar el código. No hay requisitos para usar el módulo.
Sin embargo, tales módulos generalmente no pueden existir de manera independiente: muy a menudo dependerán de algunas clases de otros módulos. Hay dos formas de expresar dependencias.
Expresar dependencias a través de miembros abstractos
Como cada módulo es un trait, es posible dejar algunas dependencias sin definir, como miembros abstractos. Dichos miembros abstractos se pueden usar al realizar la conección (ya sea manualmente o mediante la macro de MacWire), pero no es necesario dar la implementación específica.
Cuando todos los módulos se combinan en la aplicación final, el compilador verificará que todas las dependencias definidas como miembros abstractos estén definidas.
Tenga en cuenta que podemos declarar a todos los miembros abstractos como defs, ya que pueden implementarse más tarde como vals, vals lazy o dejarse como defs. Usar un def mantiene todas las opciones posibles.
La conexión para nuestro código de ejemplo lo vamos a dividir de la siguiente manera; las clases ahora se agrupan en paquetes:
package shunting {
class PointSwitcher()
class TrainCarCoupler()
class TrainShunter(
pointSwitcher: PointSwitcher,
trainCarCoupler: TrainCarCoupler)
}
package loading {
class CraneController()
class TrainLoader(
craneController: CraneController,
pointSwitcher: PointSwitcher)
}
package station {
class TrainDispatch()
class TrainStation(
trainShunter: TrainShunter,
trainLoader: TrainLoader,
trainDispatch: TrainDispatch) {
def prepareAndDispatchNextTrain() { ... }
}
}
Cada paquete tiene un módulo de trait correspondiente. Tenga en cuenta que la dependencia entre los paquetes de derivación y carga se expresa utilizando un miembro abstracto:
package shunting {
trait ShuntingModule {
lazy val pointSwitcher = wire[PointSwitcher]
lazy val trainCarCoupler = wire[TrainCarCoupler]
lazy val trainShunter = wire[TrainShunter]
}
}
package loading {
trait LoadingModule {
lazy val craneController = wire[CraneController]
lazy val trainLoader = wire[TrainLoader]
// dependency of the module
def pointSwitcher: PointSwitcher
}
}
package station {
trait StationModule {
lazy val trainDispatch = wire[TrainDispatch]
lazy val trainStation = wire[TrainStation]
// dependencies of the module
def trainShunter: TrainShunter
def trainLoader: TrainLoader
}
}
object TrainStation extends App {
val modules = new ShuntingModule
with LoadingModule
with StationModule
modules.trainStation.prepareAndDispatchNextTrain()
}
Para implementar dependencias de esta manera, se necesita una convención de nomenclatura coherente, ya que el miembro abstracto se concilia con el nombre de implementación. Nombrar los valores igual que las clases, pero con la letra inicial en minúscula es un buen ejemplo de tal convención.
Expresar dependencias a través de autotipos
Otra forma de expresar dependencias es mediante auto-tipos o extendiendo otros módulos de traits. De esta manera, se crea una conexión mucho más fuerte entre los dos módulos, en lugar del enfoque de miembro abstracto acoplado más flexible, sin embargo, en algunas situaciones es deseable (por ejemplo, cuando se tiene una interfaz de módulo con implementaciones múltiples).
Por ejemplo, podríamos expresar la dependencia entre los módulos de derivación y carga y el módulo de estación extendiendo el módulo de rasgos, en lugar de usar los miembros abstractos:
package shunting {
trait ShuntingModule {
lazy val pointSwitcher = wire[PointSwitcher]
lazy val trainCarCoupler = wire[TrainCarCoupler]
lazy val trainShunter = wire[TrainShunter]
}
}
package loading {
trait LoadingModule {
lazy val craneController = wire[CraneController]
lazy val trainLoader = wire[TrainLoader]
// dependency expressed using an abstract member
def pointSwitcher: PointSwitcher
}
}
package station {
// dependencies expressed using extends
trait StationModule extends ShuntingModule with LoadingModule {
lazy val trainDispatch = wire[TrainDispatch]
lazy val trainStation = wire[TrainStation]
}
}
object TrainStation extends App {
val modules = new ShuntingModule
with LoadingModule
with StationModule
modules.trainStation.prepareAndDispatchNextTrain()
}
Se lograría un efecto muy similar utilizando un auto-tipo.
Este enfoque también puede ser útil para crear módulos más grandes a partir de múltiples más pequeños, sin la necesidad de volver a expresar las dependencias de los módulos más pequeños. Simplemente defina un trait de módulo más grande que extienda un número de trait de módulo más pequeño.
Composing modules
Los módulos también se pueden combinar usando la composición, es decir, puede anidar módulos como miembros y usar dependencias definidas en los módulos anidados para conectar objetos.
Por ejemplo, podemos agregar un complemento a nuestra aplicación de gestión de trenes que permitirá recopilar estadísticas:
package stats {
class LoadingStats(trainLoader: TrainLoader)
class ShuntingStats(trainShunter: TrainShunter)
class StatsModule(
shuntingModule: ShuntingModule,
loadingModule: LoadingModule) {
import shuntingModule._
import loadingModule._
lazy val loadingStats = wire[LoadingStats]
lazy val shuntingStats = wire[ShuntingStats]
}
}
Tenga en cuenta las declaraciones de importación, que traen cualquier dependencia definida en los módulos anidados al alcance.
Esto se puede acortar aún más mediante el uso de una anotación experimental @Module para los rasgos / clases del módulo; Los miembros de módulos anidados con esa anotación se tendrán en cuenta automáticamente durante la inyección:
package loading {
@Module
trait LoadingModule { ... }
}
package shunting {
@Module
trait ShuntingModule { ... }
}
package stats {
class LoadingStats(trainLoader: TrainLoader)
class ShuntingStats(trainShunter: TrainShunter)
class StatsModule(
shuntingModule: ShuntingModule,
loadingModule: LoadingModule) {
lazy val loadingStats = wire[LoadingStats]
lazy val shuntingStats = wire[ShuntingStats]
}
}
En este escenario, no se necesitan importaciones.
Factor y pila
Muchas veces necesitamos cambiar la pila o reorganizarla para eso tenemos muchas palabras: dup , drop , nip , swap , over , rot , y pick. Veamos con ejemplos como podemos utilizarlas :
IN: scratchpad 1 dup ! duplica el valor
--- Data stack:
1
1
IN: scratchpad clear
IN: scratchpad 1 2 drop ! Elimina el ultimo valor (el tope de la pila)
--- Data stack:
1
IN: scratchpad clear
IN: scratchpad 1 2 nip ! borra el segundo valor
--- Data stack:
2
IN: scratchpad clear
IN: scratchpad 1 2 swap ! cambia de posición 2 valores
--- Data stack:
2
1
IN: scratchpad clear
IN: scratchpad 1 2 over ! duplica el segundo valor y lo pone al tope de la pila
--- Data stack:
1
2
1
IN: scratchpad clear
IN: scratchpad 1 2 3 rot ! rota los valores
--- Data stack:
2
3
1
IN: scratchpad 1 dup ! duplica el valor
--- Data stack:
1
1
IN: scratchpad clear
IN: scratchpad 1 2 drop ! Elimina el ultimo valor (el tope de la pila)
--- Data stack:
1
IN: scratchpad clear
IN: scratchpad 1 2 nip ! borra el segundo valor
--- Data stack:
2
IN: scratchpad clear
IN: scratchpad 1 2 swap ! cambia de posición 2 valores
--- Data stack:
2
1
IN: scratchpad clear
IN: scratchpad 1 2 over ! duplica el segundo valor y lo pone al tope de la pila
--- Data stack:
1
2
1
IN: scratchpad clear
IN: scratchpad 1 2 3 rot ! rota los valores
--- Data stack:
2
3
1
Citas y condiciones en Factor
Similar a otros lenguajes, las funciones pueden ser mantenidas en variables, bueno en la pila. Estas clausuras se denominan “quotation” o citas y se escribe delimitado por [ ] .
[ 42 + ]
Veamos un ejemplo :
IN: scratchpad 20
--- Data stack:
20
IN: scratchpad [ 42 + ]
--- Data stack:
20
[ 42 + ]
IN: scratchpad call
--- Data stack:
62
Las citas son importantes y se utilizan para las condiciones.
La palabra if recibe como parámetro 1 condición y 2 citas y ejecuta el primero si es verdadero y el segundo de lo contrario :
IN: scratchpad 10 0 > [ "pos" ] [ "neg" ] if .
"pos"
IN: scratchpad -5 0 > [ "pos" ] [ "neg" ] if .
"neg"
IN: scratchpad "cool" [ "yes" ] [ "no" ] if .
"yes"
Por si hay dudas el if tiene la forma :
<condition> <true branch> <false branch> if
También podemos utilizar la palabra ? :
IN: scratchpad 10 0 > "pos" "neg" ? .
"pos"
IN: scratchpad -5 0 > "pos" "neg" ? .
"neg"
La palabra when y unless se puede utilizar con una sola cita :
IN: scratchpad 10 0 > [ "pos" . ] when
"pos"
IN: scratchpad -5 0 > [ "neg" . ] unless
"neg"
Suscribirse a:
Entradas (Atom)