Mostrando las entradas con la etiqueta Scala for the Impatient. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Scala for the Impatient. Mostrar todas las entradas

miércoles, 28 de febrero de 2018

Un resumen de Scala for the Impatient, parte 38


Extractors con un argumento o ninguno.

Si un unapply método extrae un solo valor este debe retornar un Optional por ejemplo: 


object Number {
def unapply(input: String): Option[Int] =
try {
Some(input.trim.toInt)
} catch {
case ex: NumberFormatException => None
}
}

Con este extractor podemos extraer el numero de un string :

val Number(n) = "1729"

Un extractor puede chequear un valor en este caso debe retornar un booleano:

object IsCompound {
def unapply(input: String) = input.contains(" ")
}


Se puede utilizar un extractor para agregar un test en una expresión pattern matching:


author match {
    case Name(first, IsCompound()) => …
    case Name(first, last) => …

 }

El método unapplySeq

Si utilizamos el extract en una secuencia, estaremos llamando a unapplySeq que retorna Option[Seq[A]] , donde A es el valor extraído:

object Name {
def unapplySeq(input: String): Option[Seq[String]] =
if (input.trim == "") None else Some(input.trim.split("\\s+"))
}


Dado este método ahora podemos utilizar un conjunto de parámetros:

author match {
   case Name(first, last) => …
   case Name(first, middle, last) => …
   case Name(first, “van”, “der”, last) => ...
}

Ojo, no se puede tener un método unapply y un unapplySeq con similar parámetros. 

Invocación dinámica

Scala es un lenguaje de tipado estático y fuertemente tipado.  Es decir que informa los errores de tipo en momento de compilación.

Si un tipo extiende de el trait scala.Dynamic, entonces el método llamadas, getters y setters se reescriben como llamadas a métodos especiales que pueden inspeccionar el nombre de la llamada original y los parámetros, y luego tomar acciones arbitrarias.

Veamos algunos ejemplos. Supongamos que la persona es una instancia de un tipo que se extiende
de Dynamic. Una declaración:

person.lastName = "Doe"

se puede remplazar:

person.updateDynamic ("lastName") ("Doe")

La clase Person debe tener dicho método:

clase Persona {
    ...
    def updateDynamic (campo: String) (newValue: String) {...}
}

Depende de nosotros si implementamos el método updateDynamic.

Otro ejemplo:

val name = person.lastName

puede ser remplazado por:

val name = name.selectDynamic("lastName")

El metodo selectDynamic debe retornar un valor simple. 

En scala existe DynamicProps que extiende a Dynamic y sobre escribe sus métodos:

class DynamicProps(val props: java.util.Properties) extends Dynamic {
    def updateDynamic(name: String)(value: String) {
        props.setProperty(name.replaceAll("_", "."), value)
    }

    def selectDynamic(name: String) =
        props.getProperty(name.replaceAll("_", "."))
    }
}

martes, 20 de febrero de 2018

Un resumen de Scala for the Impatient, parte 37


Los métodos apply y update

En scala podemos extender la sintaxis de llamada de funciones :

f(n1, n2, n3, …)

Si f es una función o un método, se ejecuta. De lo contrario, se llama al método apply por lo tanto es equivalente a llamar a

f.apply(n1, n2, n3, …)

De la misma manera si se utiliza la notación de función con asignación se llama al método update :

f(n1, n2, n3, …) = value

Es similar a llamar a :

f.update(n1, n2, n3, …, value)

Este mecanismo es utilizado en arrays y maps :

val scores = new scala.collection.mutable.HashMap[String, Int]
scores("Bob") = 100 // es como llamar a scores.update("Bob", 100)
val bobsScore = scores("Bob") // es como llamar a scores.apply("Bob")

El método apply también es usado para retornar instancias de objetos, sin tener que llamar al constructor con el new :

class Fraction(n: Int, d: Int) {

}

object Fraction {
def apply(n: Int, d: Int) = new Fraction(n, d)
}

De esta manera podemos hacer lo siguiente:

val result = Fraction(3, 4) * Fraction(2, 5)

Extractors

Un extractor es un objeto que puede desaplicar un método. Se puede pensar esto como el método contrario a el apply. Por ejemplo si el apply agrega un elemento a una lista el extractor debería quitar este elemento.

Por ejemplo el objeto Fraction tiene un método apply que retorna una Fraction a partir de un numerador y un denominador. El extractor debería retornar un numerador y un denominador a partir de una Fraction.

object Fraction {
def unapply(input: Fraction) =
if (input.den == 0) None else Some((input.num, input.den))
}

Por lo general el método unapply retorna un Option dado que puede haber un error cuando queremos desaplicar un método. En el ejemplo si el denominador es 0 retorna None.

val Fraction(a, b) = f;
val tupleOption = Fraction.unapply(f)
if (tupleOption == None) throw new MatchError
// tupleOption is Some((t1 , t2))

En el ejemplo el método apply y unapply son inversos pero esto no es obligatorio.

viernes, 2 de febrero de 2018

Un resumen de Scala for the Impatient, parte 36


Operadores de asignación.

Los operadores de asignación tienen la forma operador= y se utilizan de la siguiente forma:

a operator= b

que es igual a decir :

a = a operator b

Por ejemplo a+= b es equivalente a escribir a = a + b

Hay unos detalles técnicos :
<=, >= y != no son operadores de asignación
Un operador que comienza con = nunca es un operador de asignación (==, ===, =/=, etc)
Si a tiene un operador llamado operator= entonces este sera llamado directamente.

Precedencia.

Cuando tenemos más de un operador, sin paréntesis debemos definir las precedencias de los operadores. Dado que esto define el orden de resolución, por ejemplo :

1 + 2 * 3

El operador * se evalúa primero y luego la suma, esto fue definido por conversión. Lenguajes como C++ y Java tienen un numero fijo de operadores por lo tanto es más sencillo definir la precedencias (se pueden enumerar). En cambio en Scala tenemos un numero arbitrario de operadores por lo que se debe definir esto de forma general.

  • La precedencia más alta esta dada por un caracter operador que no sea los antesesores. 
  • * / %
  • + -
  • :
  • < >
  • ! =
  • &
  • ^
  • |
  • Un carácter que no es un operador
  • La menor precedencia es el operador de asignación

Los caracteres que estan en similar fila tiene similar precedencia. Por ejemplo + y -

Los operadores infijos tienen menor precedencia, por lo tanto :

a infixOp b postfixOp es similar a (a infixOp b)postfixOp
 
Asociatividad

Luego de definir la precedencia, es importante la asociatividad dado que exiten operadores que son asociativos. Por ejemplo 17 – 2 – 9 es similar a (17 – 2) – 9. El operador – es asociativo por la izquierda.

En scala todos los operadores son asociativos por la izquierda excepto :

  • Los operadores que finalizan en : 
  • Los operadores de asignación. 

Por ejemplo el operador :: construye listas con asosciatividad a la derecha por ejemplo :

1 :: 2 :: Nil esto es similar a 1 :: (2 :: Nil)

Esto generara una lista con elemento 1 y 2 es ese orden. Es decir el operador :: es un metodo de la lista que ingresa un elemento por la izquierda, convirtiendolo en el primer elemento.

Es decir 2 :: Nil es igual a Nil.::(2)

 

jueves, 21 de diciembre de 2017

Un resumen de Scala for the Impatient, parte 35

Operadores de asignación.

Los operadores de asignación tienen la forma operador= y se utilizan de la siguiente forma:

a operator= b

que es igual a decir :

a = a operator b

Por ejemplo a+= b es equivalente a escribir a = a + b

Hay unos detalles técnicos :
<=, >= y != no son operadores de asignación
Un operador que comienza con = nunca es un operador de asignación (==, ===, =/=, etc)
Si a tiene un operador llamado operator= entonces este sera llamado directamente.

Precedencia.

Cuando tenemos más de un operador, sin paréntesis debemos definir las precedencias de los operadores. Dado que esto define el orden de resolución, por ejemplo :

1 + 2 * 3

El operador * se evalúa primero y luego la suma, esto fue definido por conversión. Lenguajes como C++ y Java tienen un numero fijo de operadores por lo tanto es más sencillo definir la precedencias (se pueden enumerar). En cambio en Scala tenemos un numero arbitrario de operadores por lo que se debe definir esto de forma general.

Excepto para los operadores de asignación, la precedencia está determinada por el primer carácter del operador.

Precedencia más alta: un carácter de operador que no sea los de abajo
* / %
+ -
:
< >
! =
&
^
|

Un carácter que no es un carácter de operador
Prioridad más baja: operadores de asignación

Los caracteres en la misma fila producen operadores con la misma precedencia. Por ejemplo, + y -> tienen la misma precedencia.

Los operadores de Postfijos tienen una precedencia menor que los operadores de infijo:

a infixOp b postfixOp es igual a escribir : (a infixOp b)postfixOp


Asociatividad

Cuando hay un conjunto de operadores con la misma precedencia entra en juego la asociatividad, es decir si resuelve de derecha a izquierda o izquierda a derecha. En scala todos los operadores son asociativos por la izquierda excepto:

  • Los operadores que terminan en :
  • Los operadores de asignación


Por ejemplo el operador :: construye una lista por medio de asociatividad por la derecha:

1 :: 2 :: 3 :: Nil

esto significa

1 :: (2 :: (3 :: Nil))

Esto es así porque necesita construir primero la lista 3 :: Nil para que este parámetro sea usado por le expresión 2 :: (3 :: Nil)

La expresión 3 :: Dicha expresión es igual a la expresión Nil.::(3)




martes, 19 de diciembre de 2017

Un resumen de Scala for the Impatient, parte 34

Si hemos programado en C++ recordaremos que nos permite sobrescribir operadores, pero esto requería una muy pesada definición. Esto es muy diferente en Scala, las definiciones son mucho más fáciles y claras.

Sobrescribir operadores nos da una sintaxis más clara de nuestro código y nos acerca a la notación matemática.

En esta parte veremos como definir operadores binarios y unarios. Pero antes debemos aclarar algunos conceptos.

Identificadores

El nombre de las variables, funciones, clases, etc. se denominan identificadores. En Scala tenes más opciones de nombres para los identificadores que en java. Por supuesto podemos hacerlo igual que java, una letra seguida de letra o dígito. Como Java también permite caracteres unicode, es decir :


 es una variable valida.

También se puede utilizar caracteres de operadores en identificadores como :

  • Caracteres ASCII como ! # % & * + - / : < = > ? @ \ ^ | ~ , dígitos, . , ; marcas de puntuación, guión bajo, paréntesis () [] {} o comillas “ ' `     
  • Caracteres de símbolos matemáticos


Por ejemplo





es un identificador valido y se puede hacer:



de esta manera podemos escribir



de forma valida.

Note que las palabras :  @ # : = _ => <- <: <% >: fi  ̈ son palabras reservadas.

Por lo dicho anteriormente este es una variable valida:

val happy_birthday_!!! = "Bonne anniversaire!!!"

Por ultimo se puede incluir cualquier conjunto de palabras en `` como por ejemplo:

val `val` = 42

Si bien no parece útil, lo es por ejemplo si deseamos llamar a un método java que sea palabra reservada en scala, de esta forma podremos hacerlo.

Operador infijo

Se puede escribir:

a identificador b

Donde identificador es un método con 2 parámetros. Uno implícito y el otro explicito. Por ejemplo:

1 to 10

Que es igual a llamar 1.to(10)

Esto se denomina una expresión infija porque se encuentra entre los 2 parámetros. El operador puede contener letras o no, como el caso de -> :

1 -> 10 es igual a 1.->(10)

Para definir un operador en una clase, solo debe definir un método con el nombre del operador. Por ejemplo la clase Fraction:

class Fraction(n: Int, d: Int) {
    private val num = ...
    private val den = ...
    ...
    def *(other: Fraction) = new Fraction(num * other.num, den * other.den)
}

Operadores unarios

Los operadores infijos son binarios, es decir necesitan 2 parámetros. Un operador con un parámetro se lo denomina unario.

Los cuatro operadores: -, +, !, ~ se pueden utilizar como operadores prefijos, es decir se puede escribir el operador y luego el parámetro como por ejemplo :

 -a es idual a llamar a : a.unary_-

Si es postfijo, en ese caso no hay problema, se puede llamar el método con . o espacio, por ejemplo :

12 toString

es igual a llamar a : 12.toString()

Cuando utilizamos el operador unario postfijo con espacio y sin paréntesis nos puede dar un error de compilación. Dado que no queda expresado que no estamos utilizando parámetros. Veamos un ejemplo:

val result = 42 toString
println(result)

En este caso el compilador interpreta que se esta escribiendo lo siguiente :

val result = 42.toString(println(result))

Por esta razón nos lanzara un error de compilación, que dirá que el método tiene muchos parámetros. Por esta razón el compilador, lanza un warning cuando utilizamos esta notación, esto se puede desactivar con el parámetro del compilador -language:postfixOps o agregando el import :

import scala.language.postfixOps




lunes, 11 de diciembre de 2017

Un resumen de Scala for the Impatient, parte 33

Self type


Cuando un trait extiende de una clase, hay una garantía de que la superclase está presente en cualquier clase que se mezcle con el trait. Scala tiene un mecanismo alternativo para garantizar esto,  se denomina self type.

Cuando un trait comienza con :

this: Type =>

entonces solo se puede mezclar en una subclase del tipo dado.

trait LoggedException extends ConsoleLogger {
    this: Exception =>
    def log() { log(getMessage()) }
}

Note que el trait no extiende de Exception, en cambio, tiene un tipo propio Excepción. Eso significa que solo se puede mezclar en subclases de Excepción.

En los métodos del rasgo, podemos llamar a cualquier método del self type. Por ejemplo, la llamada a getMessage() en el método de registro es válida, ya que sabemos que debe ser un trait extendido por una excepción.

Como se puede suponer si una clase que no sea una Excepción quiere utilizar LoggedException eso no va a compilar.

También se puede utilizar type self con un método determinado, sin especificar una clase.

trait LoggedException extends ConsoleLogger {
    this: { def getMessage() : String } =>
        def log() { log(getMessage()) }
}

Por lo tanto este trait podrá mezclarse con cualquier clase que implemente getMessage() .

Que pasa en la jvm?

Scala necesita pasar un trait a una clase o interfaz java para que esto pueda ser comprendido por la JVM. Y es muy útil entender como trabajan los traits.

Un trait con todos los métodos abstractos es convertido a una interfaz. Un trait con un método es como una interfaz con un método por defecto:

trait ConsoleLogger {
    def log(msg: String) { println(msg) }
}

Se convierte:

public interface ConsoleLogger {
    default void log(String msg) { ... }
}

Si un trait tiene campos, es transformado a una interfaz con los métodos getters y setters:

trait ShortLogger extends Logger {
    val maxLength = 15 // A concrete field
    ...
}

es transformado a:

public interface ShortLogger extends Logger {
    int maxLength();
    void weird_prefix$maxLength_$eq(int);
    default void log(String msg) { ... } // Calls maxLength()
    default void $init$() { weird_prefix$maxLength_$eq(15); }
}

Por supuesto las interfaces en java no tienen campos, por lo que llama a los metodos getters o setters cuando quiera acceder o cambiar este valor. El setter es necesario tambien para inicializar el campo. Esto sucede en el método $init$.

Cuando el rasgo se mezcla en una clase, la clase obtiene un campo maxLength, y el getter y el setter se definen para obtener y establecer el campo. Los constructores de esa clase invocan el método $init$ del trait. Por ejemplo:

class SavingsAccount extends Account with ConsoleLogger with ShortLogger

seria en java:

public class SavingsAccount extends Account
 implements ConsoleLogger, ShortLogger {
   private int maxLength;
   public int maxLength() { return maxLength; }
   public void weird_prefix$maxLength_$eq(int arg) { maxLength = arg; }
 
   public SavingsAccount() {
      super();
      ConsoleLogger.$init$();
      ShortLogger.$init$();
   }
...
}

martes, 5 de diciembre de 2017

Un resumen de Scala for the Impatient, parte 32

Orden de construcción de trait

Como las clases, los traits pueden ser construidos y estos pueden construir sus propiedades y ejecutar métodos:

trait FileLogger extends Logger {
val out = new PrintWriter("app.log") // Part of the trait’s constructor
out.println(s"# ${java.time.Instant.now()}") // Also part of the constructor
def log(msg: String) { out.println(msg); out.flush() }
}


Esto se ejecutara en el constructor de cualquier objeto que incorpore este Trait.

El orden sera el siguiente: 
  1. El constructor de la super clase se ejecutara primero. 
  2. Luego el constructor de los traits, de izquierda a derecha. 
  3. Por cada traits, el constructor del padre (si lo tuviera) primero. 
  4. Si los traits tienen un constructor común, no ejecuta 2 veces el mismo constructor. 
  5. Luego de que todos los traits son construidos, la subclase es construida. 

Por ejemplo: 

class SavingsAccount extends Account with FileLogger with ShortLogger

El orden de construcción sería: 
  1. Account
  2. Logger (padre de FileLogger)
  3. FileLogger
  4. ShortLogger (tiene el padre en comun con FileLogger pero no vuelve a construirlo)
  5. SavingsAccount

Inicializando campos en los Traits

Los traits no pueden tener constructores con parámetros. Cada trait tiene un constructor sin parámetros. Esta limitación puede ser un problema para traits que necesitan ser adaptados para ser útiles. Por ejemplo el trait FileLogger seria útil poder definir el nombre del archivo.

val acct = new SavingsAccount with FileLogger("myapp.log")

Una posible solución es que el nombre de archivo sea un campo abstracto.

trait FileLogger extends Logger {
val filename: String
val out = new PrintStream(filename)
def log(msg: String) { out.println(msg); out.flush() }
}

La clase puede sobreescribir el filename pero el problema es que el constructor del trait se ejecuta antes por lo tanto no va funcionar. Esto se puede resolver con lo que se indico en el siguiente post

Aplicando esto nuestra clase sería:

val acct = new { // Early definition block after new
val filename = "myapp.log"
} with SavingsAccount with FileLogger

Otra alternativa es hacer que out sea lazy (es decir que se llame al constructor de out la primera vez que se utilice)

trait FileLogger extends Logger {
val filename: String
lazy val out = new PrintStream(filename)
def log(msg: String) { out.println(msg) } // No override needed
}

Cuando se llame al constructor de out el campo filename ya va estar seteado. Pero los valores lazy son un poco más ineficientes dado que se deben chequear al ser utilizados.

 Traits que extienden clases :

Como se puede ver un trait puede extender otro trait de esta manera podremos tener nuestra jerarquía de traits. Menos común un traits puede extender una clase. Esa clase se convierte en una superclase de cualquier clase que se mezcle con el trait.

Veamos un ejemplo: 

trait LoggedException extends Exception with ConsoleLogger {
    def log() { log(getMessage()) }
}

Una LoggerException tiene un método log que logea el mensaje. Por lo que vemos puede acceder al método getMessage que es de la clase Exception. 

Ahora podemos mix esto con el trait: 

class UnhappyException extends LoggedException { // This class extends a trait
    override def getMessage() = "arggh!"
}

¿Qué pasa si nuestra clase ya extiende otra clase? Eso está bien, siempre que sea una subclase de la superclase del trait. Por ejemplo,

class UnhappyException extends IOException with LoggedException

Pero si nuestra clase extiende de otra clase que no es hija o no es la clase padre del trait, esta clase no puede mezclarse con este trait. Por ejemplo: 

class UnhappyFrame extends JFrame with LoggedException
// Error: Unrelated superclasses

La clase no puede extender de Jframe y de Exception. 

jueves, 5 de octubre de 2017

Un resumen de Scala for the Impatient, parte 31


Sobre escribir métodos abstractos en Traits

Del ejemplo anterior, podemos deducir que TimestampLogger y ShortLogger extienden de ConsoleLogger, la cual extiende del trait Logger el cual no tiene implementación para el método log

trait Logger {
    def log(msg: String) // This method is abstract
}

Por lo tanto

trait TimestampLogger extends Logger {
    override def log(msg: String) { // Overrides an abstract method
        super.log(s"${java.time.Instant.now()} $msg") // Is super.log defined?
    }
}

Esto no debería compilar porque super.log no fue definido pero scala lo toma como que la clase TimestampLogger es abstracta y solo se puede utilizar si se mixea con una clase que implemente este método

Traits para interfaces ricas.

Un trait puede tener muchos métodos útiles que dependan de unos pocos abstractos. Un ejemplo de esto es el Iterator que define docenas de métodos en términos de los métodos abstractos next y hasnext. Vamos a construir métodos que nos permitan imprimir diferentes tipos de logs     

trait Logger {
def log(msg: String)
def info(msg: String) { log(s"INFO: $msg") }
def warn(msg: String) { log(s"WARN: $msg") }
def severe(msg: String) { log(s"SEVERE: $msg") }
}

Note que estamos combinando métodos abstractos con implementaciones concretas. 

abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) severe("Fondos insuficientes")
else …
}
}

Un campo concreto en Traits

Un campo de un traits puede ser concreto o abstracto. Si se asigna un valor al campo este será concreto, 

trait ShortLogger extends Logger {
val maxLength = 15 // A concrete field
abstract override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg
else s"${msg.substring(0, maxLength – 3)}...")
}
}

 Una clase puede mixear con el campo maxLenght

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
     var interest = 0.0
     def withdraw(amount: Double) {
          if (amount > balance) log("Fondos insuficientes")
          else …
     }
}

Note que la clase SavingsAccount tiene la variable interest, pero no balance, balance viene de Account

class Account {
     var balance = 0.0
}

La maquina virtual java no permite heredar campos de diferentes interfaces o super clases, por lo que el compilador scala debe copiarlos. 

Campos abstractos en Traits

Los campos que no son inicializados son abstractos, y deben ser sobreescritos por la subclase concreta. 

Por ejemplo: 

trait ShortLogger extends Logger {
     val maxLength: Int // An abstract field
     
      abstract override def log(msg: String) { ... }

      super.log(
                if (msg.length <= maxLength) msg
                else s"${msg.substring(0, maxLength – 3)}...")
                // El campo maxLength es utilizado en la implementación. 
}

Cuando se utiliza este trait se debe asignar un valor a maxLength: 

class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
     val maxLength = 20 // No es necesario escribir override
}

Ahora todos los mensajes de registro se truncan después de 20 caracteres.

Esta forma de proporcionar valores para los parámetros de rasgo es particularmente útil cuando se construyen objetos sobre la marcha. Volvamos a nuestra cuenta de ahorros original:

class SavingsAccount extends Account with Logger { ... }

Ahora, podemos truncar los mensajes en una instancia de la siguiente manera:

val acct = new SavingsAccount with ConsoleLogger with ShortLogger {
      val maxLength = 20
}


sábado, 19 de agosto de 2017

Como va el resumen de Scala for the Impatient? 2

Siguiendo con el post "Como va el resumen de Scala for the Impatient?"

Puff vamos a un ritmo sostenido...

Dejo los links:
http://emanuelpeg.blogspot.com.ar/2017/03/un-resumen-de-scala-for-impatient-parte_26.html
http://emanuelpeg.blogspot.com.ar/2017/04/un-resumen-de-scala-for-impatient-parte.html
http://emanuelpeg.blogspot.com.ar/2017/04/un-resumen-de-scala-for-impatient-parte_15.html
http://emanuelpeg.blogspot.com.ar/2017/04/un-resumen-de-scala-for-impatient-parte_30.html
http://emanuelpeg.blogspot.com.ar/2017/05/un-resumen-de-scala-for-impatient-parte.html
http://emanuelpeg.blogspot.com.ar/2017/05/un-resumen-de-scala-for-impatient-parte_28.html
http://emanuelpeg.blogspot.com.ar/2017/06/un-resumen-de-scala-for-impatient-parte.html
http://emanuelpeg.blogspot.com.ar/2017/06/un-resumen-de-scala-for-impatient-parte_20.html
http://emanuelpeg.blogspot.com.ar/2017/06/un-resumen-de-scala-for-impatient-parte_61.html
http://emanuelpeg.blogspot.com.ar/2017/06/un-resumen-de-scala-for-impatient-parte_25.html
http://emanuelpeg.blogspot.com.ar/2017/07/un-resumen-de-scala-for-impatient-parte.html
http://emanuelpeg.blogspot.com.ar/2017/07/un-resumen-de-scala-for-impatient-parte_24.html
http://emanuelpeg.blogspot.com.ar/2017/08/un-resumen-de-scala-for-impatient-parte.html
http://emanuelpeg.blogspot.com.ar/2017/08/un-resumen-de-scala-for-impatient-parte_6.html
http://emanuelpeg.blogspot.com.ar/2017/08/un-resumen-de-scala-for-impatient-parte_31.html
http://emanuelpeg.blogspot.com.ar/2017/08/un-resumen-de-scala-for-impatient-parte_13.html
http://emanuelpeg.blogspot.com.ar/2017/08/un-resumen-de-scala-for-impatient-parte_19.html

Un resumen de Scala for the Impatient, parte 30

Traits con implementaciones concretas. 

En scala, los métodos de los Trais pueden tener implementaciones. Por ejemplo:

trait ConsoleLogger {
    def log(msg: String) { println(msg) }
}

ConsoleLogger provee una implementación, en este caso imprime el log en consola.

Si deseamos utilizar este trait:

class SavingsAccount extends Account with ConsoleLogger {
    def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
            else balance -= amount
    }

}

Como se puede ver SavingsAccount es una clase concreta y utiliza el método por defecto de ConsoleLogger. En Java 8, también se puede realizar esto dado que se agregaron los métodos por defecto en las interfaces. Pero en scala una interfaz puede tener estado cosa que no se puede hacer en java.

En scala (y también en otros lenguajes que permiten esto) decimos que la función de ConsoleLog  es “mixed in” con la clase SavingsAccount.

Objetos con Trait

Se puede agregar un Trait con una implementación determinada a un objeto cuando se lo instancia. Veamos un ejemplo:

abstract class SavingsAccount extends Account with Logger {
    def withdraw(amount: Double) {
        if (amount > balance) log("Insufficient funds")
        else …
    }
...
}

Esta clase es abstracta no tiene implementación para el método log por lo tanto o no podríamos instanciarla o (en java por ejemplo) podemos implementar esto en una clase anónima pero en scala tenemos otra opción podemos agregar una interfaz que implemente este método con la palabra with :

val acct = new SavingsAccount with ConsoleLogger

Por supuesto podemos llamar a otra instancia con diferente traits:

val acct2 = new SavingsAccount with FileLogger

Traits en capas

Puede agregar, a una clase o a un objeto, múltiples rasgos que se invocan entre sí a partir de la última llamada. Esto es útil cuando se necesita transformar un valor en etapas.

Por ejemplo:

trait TimestampLogger extends ConsoleLogger {
    override def log(msg: String) {
        super.log(s"${java.time.Instant.now()} $msg")
   }
}

Supongamos que queremos acotar el mensaje:

trait ShortLogger extends ConsoleLogger {
    override def log(msg: String) {
        super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
    }
}

Como puede ver cada método log es modificado en las traits . La expresión super.log no tiene el mismo significado que en las clases. En los traits la expresión super.log no llama al método del padre sino que importa el orden con que se han agregado. Veamos 2 ejemplos:

val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ShortLogger with TimestampLogger

Si utilizamos acct1 el log sera:

Sun Feb 06 17:45:45 ICT 2011 Insufficient...

Como podemos ver primero recorta el texto y luego agrega la fecha y si utilizamos acct2 :

Sun Feb 06 1...

Si utilizamos acct2 primero agrega la fecha y luego recorta el texto, con lo que no nos imprime ni siquiera la fecha completa.

Note que se puede indicar con super[ConsoleLog].log(...) de que padre vamos a llamar a este método.

domingo, 13 de agosto de 2017

Un resumen de Scala for the Impatient, parte 29


Traits o rasgos

En este capitulo vamos a ver como se trabaja con traits. Los traits son similares a las interfaces de java solo que estas puede proveer estados y comportamiento, lo que las hace más útiles.

Una clase puede implementar muchos traits
Un traits puede requerir implementar campos, métodos o súper clases
A diferencia de Java, los traits pueden implementar métodos o campos.
Cuando se superponen varios traits, el orden es importante: el trait cuyos métodos se ejecutan primero van en la parte posterior.

Por que no herencia múltiple?

Scala al igual que Java no permite que una clase herede de varias clases, esto al parecer es una restricción muy grande. Porque no debemos extender de múltiples clases. Si por ejemplo lenguajes como C++ lo permiten.

La herencia múltiple trabaja muy bien cuando se combinan clases que no tienen nada en común, pero cuando combinamos clases que tienen métodos en común se presentan diferentes problemas.
Veamos un ejemplo un asistente del profesor o alumno auxiliar :

class Student {
def id: String = …
}

class Employee {
def id: String = …
...
}

class TeachingAssistant extends Student, Employee { // Ojo que esto no compila en scala
}

Desafortunadamente TeachingAssistant tiene 2 métodos id, por lo tanto que debe hacer cuando llamamos a myTA.id, siendo myTA una instancia de TeachingAssistant. (en c++ necesitamos redefinir este método para clarificar la situación)

Ahora supongamos que tanto el empleado como el estudiante heredan de persona:

class Person {
var name: String = _
}

class Student extends Person { ... }
class Employee extends Person { ... }

Esto se denomina el problema del diamante, ahora TeachingAssistant tiene 2 campos nombre pero nosotros solo queremos uno, como se debería combinar?

En Java se decidió un camino más restrictivo para resolver estos problemas, no se permite herencia múltiple de clases pero se invento el concepto de interfaz que no permite que se implementen métodos y no puede contener atributos pero puede ser heredado de forma múltiple.

Scala no permite herencia múltiple pero tiene los traits o rasgos. Un trait puede contener métodos abstractos o concretos y estados y una clase puede implementar múltiples traits. Ahora veremos como scala resuelve los conflictos de implementación.

Traits como interfaces Java

Los traits puede ser utilizadas de igual manera que las interfaces de Java:

trait Logger {
def log(msg: String) // Método abstracto
}

No es necesario indicar que este método es abstracto, si no se implementa en un trait es automáticamente abstracto.

Y una clase puede proveer una implementación:

class ConsoleLogger extends Logger { // Se usa extends y no implements
def log(msg: String) { println(msg) } // No es necesario el override
}
No es necesario agregar override cuando se sobrescribir un método abstracto de un trait.

Si es necesario agregar más interfaces, se debe hacerlo con la palabra clave with :

class ConsoleLogger extends Logger with Cloneable with Serializable


Como se puede ver se están utilizando interfaces Cloneable y Serializable que son de Java, sin ningún problema.  

domingo, 6 de agosto de 2017

Un resumen de Scala for the Impatient, parte 28

Expresiones Regulares

Cuando tomamos un texto como entrada podemos utilizar expresiones regulares para analizarlos. La clase scala.util.matching.Regex nos puede facilitar las cosas, para construir este objeto, se puede utilizar el método r de la clase String:

val numPattern = "[0-9]+".r

Si la expresión regular contiene barras invertidas o comillas, entonces es una buena idea usar la sintaxis de cadena "raw", "" "..." "". Por ejemplo:

val wsnumwsPattern = """\s+[0-9]+\s+""".r
// A bit easier to read than "\\s+[0-9]+\\s+".r

El metodo findAllIn retorna un iterador que itera por todas las coincidencias. Y esto se puede utilizar en un for :

for (matchString <- numPattern.findAllIn("99 bottles, 98 bottles"))
   process matchString

O podemos retornar un arreglo con toArray :

val matches = numPattern.findAllIn("99 bottles, 98 bottles").toArray
// Array(99, 98)

Si buscamos solo el primer elemento que coincida, podemos utilizar el método findFirstIn. Que retorna un Option[String]

val m1 = wsnumwsPattern.findFirstIn("99 bottles, 98 bottles")
// Some(" 98 ")

Para comprobar si el principio de una cadena coincide, utilice findPrefixOf:

numPattern.findPrefixOf("99 bottles, 98 bottles")
// Some(99)
wsnumwsPattern.findPrefixOf("99 bottles, 98 bottles")
// None

Si se desea remplazar la primera coincidencia o todas las coincidencias:

numPattern.replaceFirstIn("99 bottles, 98 bottles", "XX")
// "XX bottles, 98 bottles"
numPattern.replaceAllIn("99 bottles, 98 bottles", "XX")
// "XX bottles, XX bottles"

Grupos de expresiones regulares.

Los grupos son útiles para obtener subexpresiones de expresiones regulares. Añada paréntesis alrededor de las subexpresiones que desea extraer, por ejemplo:

val numitemPattern = "([0-9]+) ([a-z]+)".r

Para hacer coincidir los grupos, utilice el objeto de expresión regular como un "extractor":

val numitemPattern(num, item) = "99 bottles"
// Sets num to "99", item to "bottles"

Si desea extraer grupos de coincidencias múltiples, utilice una instrucción for de este modo:

for (numitemPattern(num, item) <- numitemPattern.findAllIn("99 bottles, 98 bottles"))
    process num and item

Un resumen de Scala for the Impatient, parte 27



Revisar un directorios.

No existe una clase oficial para revisar directorios en carpetas pero vamos a ver algunas alternativas.

Es muy simple escribir una función que recorra todas las carpetas y subcarpetas:

import java.io.File
def subdirs(dir: File): Iterator[File] = {
    val children = dir.listFiles.filter(_.isDirectory)
    children.toIterator ++ children.toIterator.flatMap(subdirs _)
}

Con esta función se puede visitar todos los subdirectorios:

for (d <- subdirs(dir)) process d

Si utilizamos Java 7, se puede utilizar el metodo walkFileTree de la clase java.nio.file.Files. Esta clase hace uso de la interfaz FileVisitor. En scala se prefiere utilizar funciones de objetos y no interfaces. Por lo tanto podemos adaptar esta funcionalidad a una visión más scala:

import java.nio.file._
implicit def makeFileVisitor(f: (Path) => Unit) = new SimpleFileVisitor[Path] {
    override def visitFile(p: Path, attrs: attribute.BasicFileAttributes) = {
        f(p)
        FileVisitResult.CONTINUE
    }
}

Por lo tanto se pueden imprimir todos los directorios de esta manera:

Files.walkFileTree(dir.toPath, (f: Path) => println(f))

Si se quiere realizar otra acción, se pasara otro método y no println

Serialización. 

Esto no tiene ninguna relación con los cereales (jeje). En Java, la serialización es utilizada para cuando queremos transmitir un objeto a otra maquina virtual o para guardar el objeto por un tiempo mínimo.

En scala se puede utilizar el mismo mecanismo pero su declaración es un tanto diferente:

Java:
public class Person implements java.io.Serializable {
    private static final long serialVersionUID = 42L;
    …
}

Scala:
@SerialVersionUID(42L) class Person extends Serializable

El Trait Serializable esta definido en el paquete scala por lo que no es necesario ningún import.
Si nos olvidamos de @SerialVersionUID este tomara un valor por defecto.

Serializar y desserializar un objeto es similar a Java:

val fred = new Person(...)
import java.io._
val out = new ObjectOutputStream(new FileOutputStream("/tmp/test.obj"))
out.writeObject(fred)
out.close()
val in = new ObjectInputStream(new FileInputStream("/tmp/test.obj"))
val savedFred = in.readObject().asInstanceOf[Person]

En scala las colecciones son seriabilizables por lo que se pueden utilizar como atributos de objetos seriabilizables:

class Person extends Serializable {
    private val friends = new ArrayBuffer[Person] // OK—ArrayBuffer is serializable
    …
}

Procesos de control

Tradicionalmente los administradores de sistema utilizan un script de shell para hacer tareas de mantenimiento como mover archivos o combinarlos. Los lenguajes script hacen fáciles estas tareas pero como lenguajes de programación dejan mucho que desear.

Scala fue diseñado para poder hacer desde humildes script a grandes aplicaciones. El paquete scala.sys.process provee utilidades para interactuar con programas shell. Es decir que podemos escribir programas tipo shell con scala.

Veamos un ejemplo simple:

import sys.process._
"ls -al .." !

Como resultado obtendremos el listado de todos los archivos del directorio. sys.process tiene un convertidor implícito de string al objeto ProcessBuilder. Y con el operador ! ejecutamos el comando.

El resultado del operador ! es un numero entero que si es 0 el comando tuvo éxito y si es distinto de 0, no.

Si utilizamos el operador !! esto retornara un string :

val result = "ls -al .." !!

Se puede utilizar este resultado como entrada de otros programas con el operador |# que es similar al operador | de linux:

"ls -al .." #| "grep sec" !

Se puede redireccionar la salida con el operador #>

"ls -al .." #> new File("output.txt") !

Si se desea que se agregar en el archivo se puede utilizar #>>

"ls -al .." #>> new File("output.txt") !

Y si se desea introducir una estrada se debe utilizar #<

"grep sec" #< new File("output.txt") !

Se puede introducir un input desde una URL :

"grep Scala" #< new URL("http://horstmann.com/index.html") !
 
Notemos que los operadores de scala se diferencian de los operadores bash solo porque comienzan con el carácter #.

martes, 1 de agosto de 2017

Un resumen de Scala for the Impatient, parte 26



Leer una linea de archivo

Leer todas lineas desde un archivo, puede llamar al método getLine del Objeto scala.io.Source :

import scala.io.Source
val source = Source.fromFile("myfile.txt", "UTF-8")
// El primer argumento va ser un string or java.io.File
val lineIterator = source.getLines

El resultado es un iteraror que permite procesar las lineas:

for (l <- lineIterator) process l

o se puede volcar las lineas en un arreglo o en un buffer :

val lines = source.getLines.toArray

Algunas veces deseamos volcar el contenido de un archivo en un string, lo podemos hacer con :

val contents = source.mkString

Ojo! Cuando terminamos de usar source debemos cerrarlo.

Para leer caracteres podemos utilizar directamente source e iterar por le mismo:

for (c <- source) process c

Si deseamos leer un archivo (como istream :: peek en C ++ o PushbackInputStreamReader en Java), llama al método buffered en el origen

val source = Source.fromFile("myfile.txt", "UTF-8")
val iter = source.buffered
while (iter.hasNext) {
    if (iter.head is nice)
        process iter.next
    else
        …
}
source.close()

Veamos como podemos leer un todos lo espacios en nuestro código:

val tokens = source.mkString.split("\\s+")

Para convertir un String a entero o flotante se puede utilizar los métodos toInt o toDouble

Si necesitamos leer un entero desde la consola, podemos hacer lo siguiente:

print("Cuntos años tienes? ")
val age = readInt()
// O podemos usar readDouble o readLong

Este comando asume que el usuario ingresará un numero y si no lo hace se lanzara NumberFormatException

Leer url o otros recursos. 

Para leer desde una Url o algun otro recurso podemos utilizar Source de la siguiente manera:

val source1 = Source.fromURL("http://horstmann.com", "UTF-8")
val source2 = Source.fromString("Hello, World!")
// lee desde un string, puede ser util para cuando se esta debugeando
val source3 = Source.stdin
// lee desde la entrada estandar

Leer archivos Binarios. 

Para leer archivos binarios debemos utilizar la librería estándar de java. Veamos como podemos leer un archivo binario a un array de bytes:

val file = new File(filename)
val in = new FileInputStream(file)
val bytes = new Array[Byte](file.length.toInt)
in.read(bytes)
in.close()

Escribir un archivo de texto. 

Scala no tiene un objeto o función especial para escribir archivos, debemos utilizar la librería de java. Por ejemplo:

val out = new PrintWriter("numbers.txt")
for (i <- 1 to 100) out.println(i)
out.close()

Todo funciona como se esperaba, excepto para el método printf. Cuando pasa un número a printf, el compilador se quejará de que necesita convertirlo a un AnyRef:

out.printf("%6d %10.2f",
 quantity.asInstanceOf[AnyRef], price.asInstanceOf[AnyRef])

pero podemos usar el método de formato de la clase String:

out.print("%6d %10.2f".format(quantity, price))

lunes, 24 de julio de 2017

Un resumen de Scala for the Impatient, parte 25

La herencia de jerarquías en Scala.

El siguiente gráfico muestra la jerarquía de clases en Scala. Los tipos primitivos de java extienden de AnyVal.

Las referencias a clases o subclases extienden de AnyRef, que es sinónimo de Object de java o .net.
AnyVal y AnyRef extienden de Any, el inicio de la jerarquía.

La clase Any define los métodos isInstanceOf, asInstanceOf y métodos generales como equals o hashcode.

AnyVal no agrega ningún método, solo representa el tipo de los valores.

AnyRef agrega métodos de monitoreo como wait, notify o notifyAll (que son métodos de la clase Object) , también tiene un método synchronized, el cual puede recibir como parámetro una clausura, la cual va ser ejecutada como threed safe.

Todas las clases de scala implementan la interface ScalaObject, que no tiene métodos.

En el otro extremo de la jerarquía están los tipos Nothing y Null. Null es un tipo que tiene una sola instancia null, el desarrollador puede asignar null a cualquier referencia pero no a cualquier valor. Por ejemplo no se le puede asignar null a un valor entero.

El tipo Nothing no tiene instancia, ocasionalmente es útil para constructores genéricos o por ejemplo para definir Nil (que en una lista vacía) de tipo List[Nothing]

Ojo Nothing no es igual que void de Java o C, void en scala se representa con el tipo Unit, que es un tipo que tiene un solo valor ().  Tenga en cuenta que Unit no es un supertipo de ningún otro tipo. Sin embargo, el compilador permite que Any sea reemplazado por Unit.

Igualdad de objetos

En scala, el método eq esta definido en la clase AnyRef y comprueba si dos referencias referencian al mismo objeto. El método equals en AnyRef llama eq. Cuando implemente una clase, debe considerar la posibilidad sobrescribir el método equals para proporcionar una noción natural de igualdad para los objetos.
 
Por ejemplo, si definimos class Item(val description: String, val price: Double) se debe sobrescribir el método equals como en Java:

final override def equals(other: Any) = {
val that = other.asInstanceOf[Item]
if (that == null) false
else description == that.description && price == that.price
}

Cuando defina iguales, recuerde definir también hashCode como en Java. El código de hash se debe calcular sólo a partir de los campos que se utilizan en la comprobación de igualdad. Por ejemplo:

final override def hashCode = 13 * description.hashCode + 17 * price.hashCode

Generalmente no se llama el método eq o equals. Simplemente utilice el operador ==. Para tipos de referencia, llama a equals después de hacer la comprobación adecuada para los operandos nulos.



martes, 4 de julio de 2017

Un resumen de Scala for the Impatient, parte 24

Campos Abstractos

En Scala puede haber campos abstractos, y este campo abstracto es un campo sin valor inicial.

abstract class Person {
    val id: Int //no esta inicializado, por lo que es abstracto igual que su getter y setter
    var name: String //también abstracto
}

Esta clase si la vemos desde java no va tener estos atributos y los métodos getters y setters van a ser abstractos.

Dada la siguiente clase si queremos extenderla debemos definir estos atributos, por ejemplo:

class Employee(val id: Int) extends Person { // una subclase concreta
     var name = "" // propiedad concreta
}

Como se puede ver no es necesario la palabra clave “override” para sobrescribir campos.

A la vez se puede utilizar esta clase abstracta por medio de la instanciación de una clase anónima, la cual debe establecer los valores :

val fred = new Person {
     val id = 1729
     var name = "Fred"
}

Construcción ordenada y definición temprana. 

Cuando se desea sobrescribir un campo y se usa este campo en el constructor de la superclase, el resultado es un comportamiento poco intuitivo.

Veamos un ejemplo, supongamos que queremos modelar una criatura puede ver una dimensión y solo 10 unidades de medida:
   
class Creature {
    val range: Int = 10
    val env: Array[Int] = new Array[Int](range)
}

Pero una hormiga puede ver solo 2 :

class Ant extends Creature {
    override val range = 2
}

Desafortunadamente ahora tenemos un problema, el rango es un valor que es utilizado en el constructor de la superclase y el constructor de la superclase se ejecuta antes del constructor de la subclase. Vamos que sucede en mayor detalle:

  • Se llama al constructor de Ant
  • El constructor de Ant llama al constructor de criatura 
  • El constructor de criatura inicializa range en 10 y luego inicializa env con el método getter de range que fue sobrescrito, este no utiliza el de criatura sino el de hormiga que aun no fue inicializado. Por lo tanto env es un vector de 0, porque 0 es el entero por defecto. 
  • Hormiga establece a range en 2. 


Como se puede ver env se inicializo como un vector de 0 y debía ser de 2.

Existen diferentes soluciones a este problema pero podemos nombrar 3:

  • Declarar val como final. Esto es seguro pero no flexible. 
  • Declarar val como lazy, esto es seguro, flexible pero un poco ineficiente. 
  • Declarar este campo como “early definition” o  definición temprana en la subclase. 

En esta sección vamos a explorar la ultima opción. Para definir un campo con definición temprana lo podemos hacer de la siguiente manera:

class Bug extends {
     override val range = 3
} with Creature

Note que with es una palabra reservada, que se encuentra antes de la superclase.


domingo, 25 de junio de 2017

Un resumen de Scala for the Impatient, parte 23


Sobrescribir campos

Se puede sobrescribir val (o un método sin parámetros) con una declaración val con el mismo nombre. La clase tiene privados los atributos y públicos los getters y setters por lo tanto se sobrescribirán estos métodos.

Por ejemplo:

class Person(val name: String) {
     override def toString = getClass.getName + "[name=" + name + "]"
}

class SecretAgent(codename: String) extends Person(codename) {
     override val name = "secret" // Don’t want to reveal name . . .
     override val toString = "secret" // . . . or class name
}

Este ejemplo muestra el mecanismo, pero es bastante artificial. Un caso más común es sobrescribir un def de una clase abstracta, veamos un ejemplo :

abstract class Person { // See Section 8.8 for abstract classes
     def id: Int // Each person has an ID that is computed in some way
     ...
}

class Student(override val id: Int) extends Person
// A student ID is simply provided in the constructor

Note las siguientes restricciones:
  • Un def solo puede ser sobrescrito por un def
  • Un val solo puede ser sobrescrito por otro val o un def sin parámetros
  • Un var solo puede ser sobrescrito por un var abstracto


Veamos un cuadro que describe la sobrecarga:



Con val
Con def
Con var
Sobrescribir val
La subclase tiene un campo privado (con el mismo nombre que el campo de la superclase: está bien).
Getter reemplaza al getter de superclase.
Error
Error
Sobrescribir def
La subclase tiene un campo privado.
Getter anula el método de superclase.
Como java.
Una var puede anular un par getter / setter.
Anular solo el getter produce un error.
Sobrescribir var
Error
Error
Sólo si la superclase var es abstracta

Subclases anónimas.

Como en java, se pueden implementar interfaces o extender clases abstractas implementando los métodos a continuación del llamado de dicha clase o interfaz, es más fácil explicar esto con un ejemplo:

val alien = new Person("Fred") {
     def greeting = "Greetings, Earthling! My name is Fred."
}

Técnicamente esto creo una persona con el método “greeting” implementado con el texto: "Greetings, Earthling! My name is Fred." El tipo es Person{def greeting: String}. Se puede utilizar este tipo como parámetro de la siguiente forma:

def meet(p: Person{def greeting: String}) {
     println(p.name + " says: " + p.greeting)
}

Clases Abstractas.

Como en java, podemos definir una clase abstracta con la palabra “abstract” y esta clase no se podrá instanciar. Normalmente esto es usado de la siguiente manera porque hay uno o más métodos que no podemos definir, es decir que son abstractos.

abstract class Person(val name: String) {
     def id: Int // No method body—this is an abstract method
}

Una subclase de Person va a estar obligada a implementar el método id o ser abstracta. A diferencia de java no es necesario utilizar la palabra abstract para los métodos, simplemente debemos omitir el cuerpo de la función.

Una subclase concreta esta obligada a sobre escribir este método:

class Employee(name: String) extends Person(name) {
     def id = name.hashCode // override keyword not required
}