Scalera tip: Keep your actor’s state with no VAR at all!

It’s pretty well known that using VARs is, apart from unethical, the evil itself, some kind of hell, it make kitties die and many other stuff you might probably heard before and that could eventually be the cause of a painfull slowly dead.

The essence of functional programming is therefore the immutability: every time I mutate an element, I actually generate a new one.

What about Akka actors?

When we talk about actors, we can define them as stateful computation units that sequentially process a message queue by reacting(or not) to each of these messages.

It’s always been said that, in order to keep state within some actor’s logic, it was ok to use VARs:

It’s impossible that concurrency problems happen: it’s the actor itself and nobody else who access that var and will only process one message at a time.

But maybe, we could renounce to this premise if we look for some way to redefine the actor’s behavior based on a new state.

Mortal approach

If we follow the previously described philosophy, the very first (and more straight forward) approach for keeping some actor’s state would be pretty similar to the following:

class Foo extends Actor{
  var state: Int = 0
  override def receive = {
    case Increase => state += 1
  }
}

Every time an Increase arrives, we modify the state value by adding 1.
So easy so far, right?

Immutable approach

Nevertheless, we could define a receive function parameterized by certain state, so when a message arrives, this parameter is the state to take into account.

If the circumstances to mutate the state took place, we would just invoke the become method that would modify the actor’s behavior. In our case, that behavior mutation would consist on changing the state value.

If we use the previously defined example:

class Foo extends Actor{
  def receive(state: Int): Receive = {
    case Increase =>
      context.become(
        receive(state + 1),
        discardOld = true)
  }
  override def receive = receive(0)
}

we can notice that the function defined by receive is parameterized by some state argument. When some Increase message arrives, what we perform is an invocation to become for modifying the actor’s behavior, passing as an argument the new state to handle.

If we wanted some extra legibility, we could even get abstract from every updatabe-state actor:

trait SActor[State] extends Actor {
  val initialState: State
  def receive(state: State): Receive
  def update(state: State): Receive =
    context.become(
      receive(state),
      discardold = true)
  override def receive =
    receive(initialState)
}

this way, we just have to define the initial state of some actor, a new parameterized receive function and a new update function that takes care of performing the proper become call as explained before.
With all these in mind, we now have some cuter brand new Foo actor:

class Foo extends SActor[Int] {
  val initialState = 0
  def receive(state: Int): Receive = {
    case Increase => update(state +1)
  }
}

Potential hazardous issues

Please, do notice that in the featuring example, we’ve used a second argument for become: discardOld = true. This argument settles whether the new behavior should be stashed on the top of the older one, or ‘au contraire’ it should completely substitute the previous behavior.

Let’s suppose we used discardOld = false. If every single time a new Increase message arrived we had to stash a new behavior, we could obtain a wonderful overflow issue.

See you in the next tip.

Peace out 🙂

Scalera tip: Mantén estado en tu actor sin usar un solo VAR

Es de todos sabido que usar VARs es algo que, aparte de mal visto, está mal, es el infierno en vida, hace que mueran gatitos y muchas otras perlitas que probablemente ya habréis oído antes y que por poco os ha causado una muerte lenta y dolorosa en el cadalso.

La esencia en programación funcional es, por tanto, la inmutabilidad: cada vez que muto un elemento, genero uno nuevo.

What about Akka actors?

Cuando hablamos de actores, podemos definirlos como unidades con estado que procesan de manera secuencial una cola de mensajes, asociando (o no) a cada uno de estos mensajes una cierta lógica computacional.

Siempre se ha dicho que para mantener dicho estado dentro de la lógica de un actor, no pasaba nada si usabas un var:

Es imposible que hayan problemas de concurrencia: solo el propio actor tiene acceso a dicho VAR y procesará un solo mensaje al mismo tiempo.

Pero quizás podamos renunciar a esta premisa si buscamos una manera de redefinir el comportamiento del actor en base a un nuevo estado.

Mortal approach

Siguiendo la filosofía antes descrita, la primera (y más directa) aproximación para mantener el estado en un actor se parecería bastante a lo siguiente:

class Foo extends Actor{
  var state: Int = 0
  override def receive = {
    case Increase => state += 1
  }
}

Cada vez que llega un mensaje Increase, modificamos el valor de state, sumando 1.
Hasta aquí nada complicado, ¿no?

Immutable approach

Sin embargo, podríamos definir una función receive que estuviera parametrizada por un cierto estado, de manera que cuando llegue un mensaje, el estado a tener en cuenta sea este parámetro.

Si se diera la circunstancia de tener que actualizar el valor de dicho estado, bastaría con invocar al método become que modifica el comportamiento del actor. En nuestro caso, dicha modificación del comportamiento consistiría en cambiar el valor del estado.

Si usamos el mismo ejemplo que antes:

class Foo extends Actor{
  def receive(state: Int): Receive = {
    case Increase =>
      context.become(
        receive(state + 1),
        discardOld = true)
  }
  override def receive = receive(0)
}

vemos que la función que define el receive en base al estado recibe un parámetro denominado state. Cuando llega un mensaje de tipo Increase, lo que hacemos es invocar a become para modificar el comportamiento del actor, pasando como argumento el nuevo estado a tener en cuenta.

Si queremos mejorar un poco la legibilidad, podríamos incluso abstraer todo actor con estado actualizable:

trait SActor[State] extends Actor {
  val initialState: State
  def receive(state: State): Receive
  def update(state: State): Receive =
    context.become(
      receive(state),
      discardold = true)
  override def receive =
    receive(initialState)
}

de manera que se especifique el estado inicial del actor, una nueva función de receive que queda parametrizada por el nuevo estado a gestionar, y una nueva función de update que se encarga de realizar la llamada a become como antes explicábamos.
Con todo ello nos queda un nuevo actor Foo mucho más curioso:

class Foo extends SActor[Int] {
  val initialState = 0
  def receive(state: Int): Receive = {
    case Increase => update(state +1)
  }
}

Potential hazardous issues

Nótese que en el ejemplo de antes hemos pasado un segundo argumento: discardOld = true. Este argumento indica si el comportamiento nuevo debe apilarse sobre el anterior o si por el contrario debe sustituirlo por completo.

Supongamos que usáramos un discardOld = false. Si cada vez que llegase un mensaje de incremento, apilásemos un nuevo comportamiento, podríamos llegar a tener un problema de desbordamiento.

Hasta el próximo briconsejo.

Agur de limón 🙂

More lazy values, the State monad and other stateful stuff

In the previous post, we talked about lazy evaluation in Scala. At the end of that post, we asked an interesting question: Does a Lazy value hold an state?

24195622

In order to answer that question, we’ll try to define a type that could represent the Lazy values:

trait Lazy[T] {

  val evalF : () => T

  val value: Option[T] = None

}
object Lazy{
  def apply[T](f: => T): Lazy[T] =
    new Lazy[T]{ val evalF = () => f }
}

As you can see, our Lazy type is parameterized by some T type that represents the actual value type(Lazy[Int] would be the representation for a lazy integer).
Besides that, we can see that it’s composed of the two main Lazy type features:

  • evalF : Zero-parameter function that, when its ‘apply’ method is invoked, it evaluates the contained T expression.
  • value : The result value of the interpretation of the evalF function. This concrete part denotes the state in the Lazy type, and it only admit two possible values: None (not evaluated) or Some(t) (if it has been already evaluated and the result itself).

We’ve also added a companion object that defines the Lazy instance constructor that receives a by-name parameter that is returned as result of the evalF function.

e9a2295b3db9b45c8f5484a09033c1c71cf88e3375bb7ff60456bc81c29a4e04

Now the question is, how do we join both the evaluation function and the value that it returns so we can make Lazy an stateful type? We define the ‘eval’ function this way:

trait Lazy[T] { lzy =>

  val evalF : () => T

  val value: Option[T] = None

  def eval: (T, Lazy[T]) = {
    val evaluated = evalF.apply()
    evaluated -> new Lazy[T]{ mutated =>
      val evalF = lzy.evalF
      override val value = Some(evaluated)
      override def eval: (T, Lazy[T]) = 
        evaluated -> mutated
    }
  } 

}

The ‘eval’ function returns a two-element tuple:

  • The value result of evaluating the expression that stands for the lazy value.
  • a new Lazy value version that contains the new state: the T evaluation result.

If you take a closer look, what ‘eval’ method does in first place is to invoke the evalF function so it can retrieved the T value that remained until that point not-evaluated.
Once done, we return it as well as the new Lazy value version. This new version (let’s call it mutated version) will have in its ‘value’ attribute the result of having invoked the evalF function. In the same way, we change its eval method, so in future invocations the Lazy instance itself is returned instead of creating new instances (because it actually won’t change its state, like Scala’s lazy definitions work).

The interesting question that comes next is: is this an isolated case? Could anything else be defined as stateful? Let’s perform an abstraction exercise.

Looking for generics: stateful stuff

Let’s think about a simple stack:

sealed trait Stack[+T]
case object Empty extends Stack[Nothing]
case class NonEmpty[T](head: T, tail: Stack[T]) extends Stack

The implementation is really simple. But let’s focus in the Stack trait and in a hypothetical pop method that pops an element from the stack so it is returned as well as the rest of the stack:

sealed trait Stack[+T]{
  def pop(): (Option[T], Stack[T])
}

Does it sound familiar to you? It is mysteriously similar to

trait Lazy[T]{
  def eval: (T, Lazy[T])
}

isn’t it?

If we try to re-factor for getting a common trait between Lazy and Stack, we could define a much more abstract type called State:

trait State[S,T] {
  def apply(s: S): (T, S)
}

Simple but pretty: the State trait is parameterized by two types: S (state type) and T (info or additional element that is returned in the specified state mutation). Though it’s simple, it’s also a ver common pattern when designing Scala systems. There’s always something that holds certain state. And everything that has an state, it mutates. And if something mutates in a fancy and smart way…oh man.

That already exists…

24314442

All this story that seems to be created from a post-modern essay, has already been subject of study for people…that study stuff. Without going into greater detail, in ScalaZ library you can find the State monad that, apart from what was previously pointed, is fully-equipped with composability and everything that being a monad means (semigroup, monoid, …).

If we define our Lazy type with the State monad, we’ll get something similar to:

import scalaz.State

type Lazy[T] = (() => T, Option[T])

def Lazy[T](f: => T) = (() => f, None)

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

When decrypting the egyptian hieroglyph, given the State[S,T] monad, we have that our S state will be a tuple composed of what exactly represents a lazy expression (that we also previously described):

type Lazy[T] = (() => T, Option[T])
  • A Function0 that represents the lazy evaluation of T
  • The T value that might have been evaluated or not

For building a Lazy value, we generate a tuple with a function that stands for the expression pointed with the by-name parameter of the Lazy method; and the None value (because the Lazy guy hasn’t been evaluated yet):

def Lazy[T](f: => T) = (() => f, None)

Last, but not least (it’s actually the most important part), we define the only state transition that is possible in this type: the evaluation. This is the key when designing any State type builder: how to model what out S type stands for and the possible state transitions that we might consider.

In the case of the Lazy type, we have two possible situations: the expression hasn’t been evaluated yet (in that case, we’ll evaluate it and we’ll return the same function and the result) or the expression has been already evaluated (in that case we won’t change the state at all and we’ll return the evaluation result):

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

iZcUNxH

In order to check that we can still count on the initial features we described for the Lazy type (it can only be evaluated once, only when necessary, …) we check the following assertions:

var sideEffectDetector: Int = 0

val two = Lazy {
  sideEffectDetector += 1
  2
}

require(sideEffectDetector==0)

val (_, (evaluated, evaluated2)) = (for {
  evaluated <- eval[Int]
  evaluated2 <- eval[Int]
} yield (evaluated, evaluated2)).apply(two)

require(sideEffectDetector == 1)
require(evaluated == 2)
require(evaluated2 == 2)

Please, do notice that, as we mentioned before, what is defined inside the for-comprehension are the same transitions or steps that the state we decide will face. That means that we define the mutations that any S state will suffer. Once the recipe is defined, we apply it to the initial state we want.
In this particular case, we define as initial state a lazy integer that will hold the 2 value. For checking the amount of times that our Lazy guy is evaluated, we just add a very dummy var that will be used as a counter. After that, we define inside our recipe that the state must mutate twice by ussing the eval operation. Afterwards we’ll check that the expression of the Lazy block has only been evaluated once and that the returning value is the expected one.

I wish you the best tea for digesting all this crazy story 🙂
Please, feel free to add comments/menaces at the end of this post or even at our gitter channel.

See you on next post.
Peace out!

Más lazy’s, la mónada State y otras cosas con estado

En el anterior post hablábamos sobre la evaluación perezosa en Scala. Al final de dicho post, planteábamos una pregunta: ¿Un Lazy tiene estado?

24195622

Para responder a dicha pregunta, vamos a intentar definir un tipo que represente un valor Lazy como sigue:

trait Lazy[T] {

  val evalF : () => T

  val value: Option[T] = None

}
object Lazy{
  def apply[T](f: => T): Lazy[T] =
    new Lazy[T]{ val evalF = () => f }
}

Como se puede observar, nuestro tipo Lazy está parametrizado por un tipo T que representa el tipo del valor en cuestión(Lazy[Int] sería la representación de un entero perezoso).
Además, podemos ver que se compone de dos elementos principales que caracterizan a un Lazy:

  • evalF : Función de cero argumentos que, al invocar su método apply, evalúa la expresión de T contenida.
  • value : El valor resultante de la interpretación de la función evalF. Esta parte es la que denota el estado en el tipo Lazy, y solo admite dos posibles valores: None (no evaluado) o Some(t) (si ya ha sido evaluado y el resultado obtenido).

También hemos añadido un objeto companion que define el constructor de instancias Lazy que recibe un argumento by-name que se devuelve como resultado de la función evalF.

e9a2295b3db9b45c8f5484a09033c1c71cf88e3375bb7ff60456bc81c29a4e04

La cuestión ahora es: ¿Cómo unimos la función de evaluación con el valor que devuelve para hacer que Lazy mantenga un estado? Definiendo la función eval:

trait Lazy[T] { lzy =>

  val evalF : () => T

  val value: Option[T] = None

  def eval: (T, Lazy[T]) = {
    val evaluated = evalF.apply()
    evaluated -> new Lazy[T]{ mutated =>
      val evalF = lzy.evalF
      override val value = Some(evaluated)
      override def eval: (T, Lazy[T]) = 
        evaluated -> mutated
    }
  } 

}

La función eval devuelve una tupla de dos elementos:

  • el valor resultante de la evaluación de la expresión que representa el valor perezoso.
  • una nueva versión del valor Lazy que contiene el nuevo estado: el resultado de la evaluación.

Si os fijáis, lo que hace el método en primer lugar, es invocar a la función evalF para obtener el valor de tipo T que aún estaba sin evaluar.
Una vez hecho esto, lo devolvemos así como la nueva versión del elemento Lazy. Esta nueva versión (llamémosla mutated) tendrá en su atributo value el resultado de haber invocado a evalF. Del mismo modo, modificamos su método eval, para que en sucesivas invocaciones se devuelva a sí mismo y no genere nueva instancias que en realidad no varían su estado.

La cuestión interesante viene ahora: ¿es este un caso único? ¿Existen más ‘cosas’ que mantienen un estado? Hagamos un ejercicio de abstracción.

Buscando la genericidad: cosas-con-estado

Pensemos en el caso de una pila:

sealed trait Stack[+T]
case object Empty extends Stack[Nothing]
case class NonEmpty[T](head: T, tail: Stack[T]) extends Stack

La implementación sale casi sola. Pero centrémonos en el trait Stack y en un hipotético método pop que desapila un elemento que se devuelve junto al resto de la pila:

sealed trait Stack[+T]{
  def pop(): (Option[T], Stack[T])
}

¿Os suena de algo? ¿No se parece misteriosamente a

trait Lazy[T]{
  def eval: (T, Lazy[T])
}

…?

Si intentamos sacar factor común entre Lazy y Stack podríamos definir un tipo mucho más abstracto llamado State:

trait State[S,T] {
  def apply(s: S): (T, S)
}

Simple pero bello: el trait State está parametrizado por dos tipos: S (tipo de estado) y T (información o elemento adicional que devuelve cada vez que mutamos el estado). Aquí donde lo veis, se trata de un patrón muy recurrente al diseñar sistemas en Scala. Siempre hay algo que mantiene un estado. Y todo lo que tiene estado muta. Y si ese algo muta de manera segura y elegante…oh man.

Esto ya existe …

21495586

Toda esta historia que parece sacada de un ensayo post-moderno, resulta que ya ha sido objeto de estudio de personas que estudian cosas. Sin entrar en mucho detalle, en la librería ScalaZ podéis encontrar la mónada State que, además de lo descrito anteriormente, trae de serie un full-equipped de componibilidad y todo lo que conlleva ser Mónada (semigrupo, monoide, etc).

Si definimos nuestro tipo Lazy con la mónada State tenemos algo como:

import scalaz.State

type Lazy[T] = (() => T, Option[T])

def Lazy[T](f: => T) = (() => f, None)

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

Al descomponer el jeroglífico egipcio arriba expuesto, dada la mónada State[S,T], nuestro estado S va a ser una tupla de lo que representa en el fondo a una evaluación perezosa:

type Lazy[T] = (() => T, Option[T])

y que más arriba hemos descrito:

  • Una Function0 que representa la evaluación demorada de T
  • El valor T que puede haberse evaluado o no

Para construir un valor Lazy, generamos una tupla con una función que recoge la expresión indicada por un argumento by-name del método Lazy y el valor None (porque aún no ha sido evaluado el Lazy):

def Lazy[T](f: => T) = (() => f, None)

Por último (y esta es la parte importante) definimos la única transición posible de estado que podemos concebir cuando hablamos de valores perezosos: la evaluación. Esta es la clave cuando diseñamos cualquier constructor de tipos que extiende de State: lo importante es modelar qué es nuestro tipo S y las transiciones de estado posibles.

Para el tipo Lazy, tenemos dos posibles casos: que la expresión aún no haya sido evaluada (en cuyo caso la evaluamos y devolvemos la misma función y el resultado) ó que la expresión ya haya sido evaluada (en cuyo caso dejamos el estado como está y devolvemos además el resultado de la evaluación):

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

iZcUNxH

Para comprobar que seguimos contando con las mismas características iniciales para las que definimos el tipo Lazy (solo se evalúa una vez, solo se evalúa cuando es necesario, …) lanzamos las siguiente aserciones:

var sideEffectDetector: Int = 0

val two = Lazy {
  sideEffectDetector += 1
  2
}

require(sideEffectDetector==0)

val (_, (evaluated, evaluated2)) = (for {
  evaluated <- eval[Int]
  evaluated2 <- eval[Int]
} yield (evaluated, evaluated2)).apply(two)

require(sideEffectDetector == 1)
require(evaluated == 2)
require(evaluated2 == 2)

Si os fijáis, como antes comentábamos, lo que se define en la for-comprehension son las transiciones o pasos que va a enfrentar el estado que nosotros queramos. Es decir, definimos las mutaciones que sufrirá un estado S cualquiera. Una vez definida la ‘receta’, la aplicamos al estado inicial que nosotros queramos.
En este caso, definimos como estado inicial un perezoso número entero dos. Para comprobar el número de veces que se evalúa nuestro Lazy, añadimos un var muy dummy que funcionará a modo de contador. Luego definimos en nuestra ‘receta’ que el estado debe mutar dos veces mediante la operación eval. Posteriormente comprobamos que solo se ha ejecutado una vez la expresión del bloque Lazy y que el valor resultante de la expresión es el esperado.

Os deseo la mejor de las sales de frutas para digerir todo esto 🙂
Sentíos libres de añadir comentarios/amenazas en el post o en nuestro canal de gitter.

Hasta el próximo post.
¡Agur de limón!

Lazy values

Just in case you lived in a hole for the last ten years and you didn’t know: Scala allows managing lazy values.

image

In Scala, we can define a value that won’t be evaluated until it is explicitly invoked. For example:

lazy val myLazyInt: Int = { println("hi"); 2 }

As you can see, using lazy notation, we’ve defined lazily an integer that stands for the literal 2 and also prints a ‘hi’ when it’s evaluated.
Apart from violating the biggest functional programming law (referential transparency) due to the insidious println, side effects, dead, destruction, blah blah …

anigif_enhanced-1822-1407333641-6

notice that, if we execute the code block, the previously mentioned ‘println’ is not executed. The block is not evaluated until any other expression makes use of our lazy integer value:

val result = myLazyInt + 3
//woa! somebody printed 'hi' and I have a brand new 5 inside 'result'

Once myLazyInt is evaluated, its value won’t be calculated again, no matter how many times it’s invoked. Therefore, the mysterious impression won’t salute us anymore:

lazy val myLazyInt: Int = { println("hi"); 2 }
myLazyInt
//"hi"
myLazyInt //nothing special happened now ...
myLazyInt //no matter how many times you invoke it...
myLazyInt //seriously, let it go...

Curious. The question that could come up is, if I define a lazy value and I pass it as a method parameter, what happens? Is it evaluated at the very same moment that the method is invoked? Maybe inside the method? That’ll depend on the way you define your method’s parameters.

Call by name vs. call by value

When defining a method, people usually define its parameter ‘by-value’, that means, that we expect the parameter to be already evaluated when it is passed to the method:

def myMethod(someInteger: Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

If we invoke our method with any integer:

val n = 3
val result = myMethod(n)
//"begin"
//"end"
require(result == 5)

We just print both traces and it’s not big deal. Nothing new so far.
What happens if we now pass to the method our lazy value? In which exact moment will it print the salutation? Before or after the method traces?
Let’s try:

myMethod(myLazyInt)
//"hi"
//"begin"
//"end"

It printed it out before the method traces, which means that our lazy value was evaluated just before the method was invoked. Why does this happen? Because the way that Scala usually works needs the exact value of someInteger in order to be able to execute myMethod
It’s a pity if we want to keep myLazyInt lazy until the very last moment. How do we fix that? We’ll pass the argument ‘by-name’, that is, indicating the way the value has to be resolved instead of explicitly passing the value:

def myMethod(someInteger: => Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

This way (someInteger: => Int) we indicate that our method requires as parameter an expression that, in the end, returns an integer and not an integer itself. If we now execute the method passing our non-yet evaluated lazy value:

myMethod(myLazyInt)
//"begin"
//"hi"
//"end"

Voilà! We made it. The ‘hi’ trace is not printed until the exact value of our lazy guy is required inside the method.

Some other ways to express laziness

Another way to express a lazy evaluation, which could be extremely useful, is the Function0 type:

trait Function0[+R]{
  def apply(): R
}

It’s just a function that requires zero parameters and return an only output type. It’s expressed as follows:

val f: () => Int =
  () => 2
f.apply() //2

And that’s pretty much everything…Once understood in rough outlines how laziness works in Scala, let’s move on to more interesting questions. A Lazy value, does it represent something stateful?
The answer (or more extra questions) will be available in the following post.

Peace out!

Valores perezosos

Por si hubieras estado en un agujero durante los últimos 10 años y no lo supieras, Scala permite gestionar valores de evaluación perezosa.

image

En Scala, podemos definir un valor que no será evaluado hasta que se le llame de manera explícita. Por ejemplo:

lazy val myLazyInt: Int = { println("hi"); 2 }

Como podéis ver, usando la notación lazy hemos definido de manera perezosa un entero que vale 2 y que imprime un ‘hola’ cuando se evalúa.
Aparte de haber violado la gran ley de la programación funcional (transparencia referencial) debido al infame println, side effects, muerte, destrucción, blah blah …

anigif_enhanced-1822-1407333641-6

fijaros que si ejecutamos el fragmento de código, dicho println no se ejecuta.
No es sino hasta que otra expresión hace uso de nuestro entero perezoso, que no se ejecuta el bloque:

val result = myLazyInt + 3
//woa! somebody printed 'hi' and I have a brand new 5 inside 'result'

Una vez calculado myLazyInt, su valor no volverá a calcularse independientemente de cuantas veces se invoque. Es decir, ya no volverá a aparecer una misteriosa impresión que nos saluda:

lazy val myLazyInt: Int = { println("hi"); 2 }
myLazyInt
//"hi"
myLazyInt //nothing special happened now ...
myLazyInt //no matter how many times you invoke it...
myLazyInt //seriously, let it go...

Curioso. La cuestión es, si yo defino un valor perezoso y lo paso a un método como argumento, ¿qué ocurre? ¿Se evalúa en el momento en que se invoca la función?¿Quizás dentro del cuerpo de la función? Eso dependerá de cómo definas los argumentos de tu método.

Call by name vs. call by value

Al definir un método, por lo general, definimos sus argumentos ‘by-value’, es decir, esperamos que el argumento ya se encuentre evaluado al pasarse al método:

def myMethod(someInteger: Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

Si invocamos nuestro método con un número entero cualquiera:

val n = 3
val result = myMethod(n)
//"begin"
//"end"
require(result == 5)

Imprimimos nuestras dos trazas y ya está. Hasta aquí nada nuevo.
¿Qué ocurre ahora si le pasamos nuestro valor perezoso?¿En qué momento imprimirá “hi”?¿Antes o después de las trazas del método?
Probemos:

myMethod(myLazyInt)
//"hi"
//"begin"
//"end"

Lo imprimió antes, es decir, nuestro valor perezoso se evaluó antes de invocarse el método. ¿Esto por qué ocurre? Porque Scala, para poder ejecutar myMethod, necesita saber el valor de someInteger.
Es un fastidio si queremos mantener la evaluación de myLazyInt perezosa hasta el final. ¿Cómo lo solucionamos? Pasando el argumento ‘by-name’, es decir, indicando cómo se resolverá en el futuro el valor, pero sin pasar el valor de manera explícita:

def myMethod(someInteger: => Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

De esta forma (someInteger: => Int) indicamos que le vamos a nuestro método como argumento una expresión que devolverá un entero (que no un entero). Si ahora ejecutamos el método pasándole nuestro valor perezoso no-evaluado:

myMethod(myLazyInt)
//"begin"
//"hi"
//"end"

Voilà! No es hasta el último momento en que se requiere el valor dentro del método, que no se evalúa nuestro entero perezoso.

Otras formas de expresar laziness

Otra forma que nos puede resultar muy útil para denotar que una expresión se evalúa de manera perezosa, es el tipo Function0:

trait Function0[+R]{
  def apply(): R
}

Se trata de una función que recibe 0 argumentos y devuelve un tipo de salida. Normalmente se suele notar como sigue:

val f: () => Int =
  () => 2
f.apply() //2

No hay mucho más misterio…Una vez comprendido a grandes rasgos el funcionamiento de la evaluación perezosa en Scala, pasemos a cuestiones más interesantes…¿Un Lazy es algo con estado?
La respuesta (o más preguntas) en el próximo post.

¡Agur de limón!

Spark Streaming: stateful streams (Android vs IOS?)

If you remember the post where we talk about how to connect Spark Streaming and Twitter, we said that the limit for performing analytics was up to your imagination.

In this post we’re going to propose some example that works for illustrating the idea of keeping a state based on the defined stream feed. Speaking plainly, we’ll see how popular is some Twitter topic compared to another one: Android vs IOS.

appleandroid

Stateful

The main idea is to have some S entity which state is fed by V-typed elements that are received and processed for each batch in the stream.

stateful stream

The way that the state is updated for a concrete instant Tn is by applying a user defined function that takes as parameters both the state that was at Tn-1 instant and the value set that provides the batch received at Tn instant:

updateState function (2)

A more casual notation for a common ‘scalero’ would be

type StateUpdate[S] = 
  (Option[S],Seq[V]) => Option[S]

Why Option[S]? For a good main reason: initially, when we first start listening on the stram, we don’t have any S state, so a (S,Seq[V]) => S function wouldn’t make sense.

And in practice?

Spark’s API for pair DStreams (PairDStreamFunctions) provides the following method for doing so:

def updateStateByKey[S]
  (updateFunc: (Seq[V], Option[S]) ⇒ Option[S])
  (implicit arg0: ClassTag[S]): DStream[(K, S)]

So for a DStream which is able to classify the elements by using a function that provides a key (we’ll see an example later on), we’ll be able to apply this method and get an S state (most of the cases, this state will refer to some aggregation over the values) for each key.

The method is overloaded, so you can specify a different partition apart from HashPartitioner, indicate a custom partition number or set an initial state (RDD[(K,S)]. Remember that, otherwise, initially our state for all K keys will be None).

The example

Let’s suppose we want to measure how strong is the rivalry between Android and IOS and we want to know which is the top trending topic at Twitter (in this example we won’t distinguish between against and in favour mentions).

Using the same project we proposed for the previous Spark + Twitter example, we’ll change the Boot.scala file so it looks like more to the following gist contents.

At first place, we have to enable the checkpointing dir and tweet filters that we are interested in:

  //  Set checkpoint dir

  ssc.checkpoint("/tmp")

  //  Add filters ...

  val Android = "android"
  val IOS = "ios"

  filter(
    Android,
    IOS
  )

We’ll next group our tweets based on whether these tweets contain the ‘Android’ or ‘IOS’ filter (if the tweet contains both, it will be counted in both sides). The result we get is another DStream but that contains key-value pairs (filter-tweet):

val groupedTweets = stream.flatMap{content =>
  List(Android, IOS).flatMap(key =>
    if (content.getText.contains(key)) 
      Option(key -> content)
    else None)
  }

Once we have grouped the tweets, we create a new DStream from the previous one by using the function we defined at the beginning of this post updateStateByKey, so the S state that we were talking about would be the sum of tweets for each key word:

val aggregatedTweets: DStream[(String,Long)] =
  groupedTweets.updateStateByKey{
    (newTweets, previousState) =>
      val newTweetsAmount = newTweets.size.toLong
      previousState.fold(
        Some(newTweetsAmount))(previousSize =>
        Some(previousSize + newTweetsAmount))
  }

The only ‘tricky’ part to understand could be the fold code part, but it’s simple. It actually indicates that, in case of having a previous amount (previous state) we just add the new amount to it. Otherwise, we use the new amount.

Apart from this, we make our snippet work by printing these figures and we start listening at the stream:

//  And add actions to perform (like printing the aggregatedTweets) ...

aggregatedTweets.foreachRDD{ results =>
  results.foreach{
    case (team, amount) => 
      logger.info(s">>> $team : $amount")
  }
}

// ... and begin listening

listen()

Can you think about another way to make the stream more interesting? Playing with tweets geo-location on a heat map, for example? 😉

Easy peasy!

Spark Streaming: streams con estado (¿Android vs IOS?)

Si os acordais del post en el que hablabamos sobre como conectar Spark Streaming con Twitter, dijimos que el límite para hacer analíticas lo ponía vuestra imaginación.

En este post vamos a proponer algún ejemplo que nos sirva a su vez para ilustrar la idea de mantener un estado en base a la alimentación del stream que definimos. En cristiano, vamos a ver cómo de popular es un topic de twitter frente a otro: Android vs IOS.

appleandroid

Stateful

La idea básica es tener una entidad S cuyo estado se alimenta en base a los batches con elementos de tipo V que procesa el stream.

stateful stream

La manera en que dicho estado se actualiza para un instante discreto Tn es aplicando una función definida por el usuario que toma como argumentos el estado que había en el instante Tn-1 y el conjunto de valores que trae el batch recibido por el stream en Tn:

updateState function (2)

Que en una notación más familiar para el scalero de a pie sería

type StateUpdate[S] = 
  (Option[S],Seq[V]) => Option[S]

¿Por qué Option[S]? Por una razón principal: inicialmente, cuando arrancamos el stream, no disponemos de ningún estado S, de manera que la función no podía ser (S,Seq[V]) => S.

¿Y todo esto en la práctica?

La API de Spark para funciones de DStreams sobre pares (PairDStreamFunctions) provee el siguiente método para ello:

def updateStateByKey[S]
  (updateFunc: (Seq[V], Option[S]) ⇒ Option[S])
  (implicit arg0: ClassTag[S]): DStream[(K, S)]

De manera que para un DStream en el que podamos discriminar los elementos por una función que nos provea una clave (veremos un ejemplo más adelante), podremos aplicar este método, obteniendo un estado S (en la mayoría de los casos se podrá corresponder con una agregación) por cada clave.

El método se encuentra sobrecargado, de manera que se puede especificar un particionador disinto del HashPartitioner, indicar un número de particiones particular o bien pasar un estado inicial RDD[(K,S)] (recordad que, de otra manera, inicialmente nuestro estado para todas las claves K tiene que ser None).

El ejemplo

Supongamos que tenemos intención de medir la rivalidad entre Android e IOS y queremos saber de quién se está hablando más en Twitter (en este ejemplo no distinguiremos entre si las menciones son positivas o negativas).

Utilizando el mismo proyecto que propusimos para el anterior ejemplo de Spark + Twitter, modificaremos el fichero Boot.scala para que tenga el contenido del siguiente gist.

En primer lugar, tenemos que habilitar el directorio de checkpointing y los filtros de los tweets que nos interesan:

  //  Set checkpoint dir

  ssc.checkpoint("/tmp")

  //  Add filters ...

  val Android = "android"
  val IOS = "ios"

  filter(
    Android,
    IOS
  )

A continuación agruparemos nuestros tweets en base a si estos contienen el filtro Android o IOS (en caso de que el tweet contenga ambos, se contabilizará en ambas claves). El resultado obtenido es otro DStream pero que contiene pares de clave-valor (filtro-tweet):

val groupedTweets = stream.flatMap{content =>
  List(Android, IOS).flatMap(key =>
    if (content.getText.contains(key)) 
      Option(key -> content)
    else None)
  }

Una vez agrupados los tweets, creamos un nuevo DStream a partir de este, usando la función que definimos al principio del post updateStateByKey, de manera que el estado S del que hablábamos sería la suma de tweets para cada palabra clave:

val aggregatedTweets: DStream[(String,Long)] =
  groupedTweets.updateStateByKey{
    (newTweets, previousState) =>
      val newTweetsAmount = newTweets.size.toLong
      previousState.fold(
        Some(newTweetsAmount))(previousSize =>
        Some(previousSize + newTweetsAmount))
  }

La única parte ‘complicada’ de entender del fragmento de código podría ser el fold, pero es sencillo ya que en realidad indica que, en caso de tener una cantidad anterior (estado previo) sumamos la nueva cantidad a la anterior. En caso contrario, dejamos la nueva.

Aparte de esto, terminamos de hacer funcionar el snippet imprimiendo estas cantidades por pantalla y comenzando la escucha del stream:

//  And add actions to perform (like printing the aggregatedTweets) ...

aggregatedTweets.foreachRDD{ results =>
  results.foreach{
    case (team, amount) => 
      logger.info(s">>> $team : $amount")
  }
}

// ... and begin listening

listen()

¿Se te ocurre otra forma de hacer el stream más interesante? ¿jugar con la geolocalización de los tweets en un mapa de calor, por ejemplo? 😉

¡Agur de limón!

Scala: One language to rule them all (I)

In the beginning, there were not programming languages, just machine code. Automatic programming came to save all us by casting power rings to rule the machines: Programming languages. They diversified, inspired new ones, constituted a rich ecosystem. Among them appeared a highly useful kind of computer languages: DSLs.

DSLs

Domain Specific (computer) Languages have been around for quite a long time. The academic description of this kind of languages states that they are centred and used for an specific application domain. DSLs are small and concise which implies that they guide their users in the process of describing actions, entities or relations within their domain of application. What’s more important, they are made to fit their purpose.

If I am doing statistics research, why should worry about memory management?!!

It isn’t surprising that a whole kingdom of these mini-languages has evolved and took over machine learning tools, scientific programming, hardware design and management, modeling…

Wasn’t this a blog on Scala? Leave the history for academics!

Yeah this might sound as the stories from the past. However, in the context of the current times, when we all seem obsessed by data acquisition, storage and analysis and taking into account that this data is usually complex to manage because of its variability. We are forced to deal with dozens of different ways of managing data and many of them pass through the use of DSLs: SQL, HiveQL, CQL, bash, grep, awk, R, C’mon! How can I even finish the list! Let’s forget of what is to come.

What if a tool would give as the power to create simple, guided and short languages to perform specific domain tasks within.  What if Scala was a kind of DSL where the D stands for the Domain of Creating new DSLs?!

When its creators named Scala they were not just thinking on its capabilities in code reuse and  potential use in horizontal concurrent environments; they also kept in mind the extensibility of the language. Some of the features in that direction are:

  • Infix notation: objectA.method(objectB) can be written as objectA method objectB
  • Lack of operators: There are no operators as separated entities, just methods. Precedence order and associativity rules are provided by the last character of each method name.This way, any method name NOT ending with `:` gets associated from left to right:
    obj1 + obj2 is the same as writing obj1.+(obj2) whereas obj1 +: obj2 is as writing obj2.+:(obj1).
    Similarly, operator precedence is provided by a priority list of method name ending characters. e.g: `*` gets a higher priority than `+`, obj1 + obj2 * obj3  is always interpreted as obj1 + (obj2 * obj3).The mentioned precedence priority list is as follows:

    • Any letter, no matter case.
    • |
    • ^
    • &
    • Symbols = and !
    • Symbols < and >
    • :
    • Arithmetic operations + and –
    • Arithmetic operations  *, / and %
    • Any other special character.
  • Advanced object oriented features: object, trait, …

These features can be combined to model and build internal DSLs within the Scala programming language.

Scala DSLs 101

Infix notation is the main feature to create our own embedded languages.

Consider the following trait:

trait MovingRobot {
  def moveForward(): MovingRobot = {
    println("Robot moved one position forward")
    this
  }
  def moveBackward(): MovingRobot = {
    println("Robot moved one position backward")
    this
  }
}

Which can be mixed in an object declaration as:

object robot extends MovingRobot

Its methods can be called using traditional dot notation:

robot.moveForward.moveBackward

But infix notation give us a more natural way to talk with this simple bot:

robot moveForward() moveBackward
robot moveForward
robot moveBackward

This is the simplest  of all possible DSLs.

State transitions

Yes, simple but rather imperative and useless.  Commands are not changing the system state besides the lateral effect behind println:

simpliest_automaton

At this point, there are two options to model the effects of the DSL instructions:

  • The mutable approach: Somehow easiest to Scala new comers from imperative languages but it is way more bug prone. This one is rather similar to the approach followed by so many builders in Java. Check Java’s StringBuilder:

    The builder state is the string that is being composed. Methods, such as append(double d), return a reference to the instance of the builder whose state has been altered by the very same method.  Hence, the same reference is always returned since is the same StringBuilder instance which is mutating call after call, sounds familiar?!mutable_state
  • The immutable one (or the path of wisdom): Do not change anything, return a new state with the attributes derived from the previous state your action. From now, this post will only cover this approach.

The beauty of the second solution is that each action returns a new state object having a 1 to 1 relation with the system state. That is, the code entities are a perfect reflection of all the changes. Moreover the state is immutable by definition.

state /steɪt/  n., adj., v., stat•ed, stat•ing. 
n.

  1. the condition of a person or thing with respect to circumstances or experiences;
    the way something is[countable; usually singular]the state of one’s health.
  2. the condition of substances with respect to structure, form, etc.[countable]Water in a gaseous state is steam.

(www.wordreference.com)

Discussing why immutability drives to way less buggy systems is out of the scope of this post, hundreds of explanations can be found by googling “immutability reduces bugs”. Even Java creators decided it was better, at least for their strings.

state_transition

Each transition returning a whole new state reduces its responsibility  to just one: To generate a new state hence simplifying the DSL design. No changes in the state are to be expected beyond explicit transition calls.

The nitty-gritties of immutable state transitions

Following the utterly complex example of our uni-dimensional robot API (at this point you must have realized that the previous Scalera Challenge included a beautiful DSL), it can be altered to make it follow the above-described functional approach:

// All states extend `RobotState`
trait RobotState {
  def position: Int
}

// Transitions which can be mixed with any state for which they
// make sense.

trait MovementTransitions {
  self: RobotState =&amp;gt;

  def moveForward(nSteps: Int = 1): RobotState with MovementTransitions

  def moveBackward(nSteps: Int = 1): RobotState with MovementTransitions

}

// States
// In this example, states only differ in the robot position so they all
// are represented by the same case class.
case class Robot(position: Int) extends RobotState with MovementTransitions {

  def moveForward(nSteps: Int = 1) =
    Robot(position + nSteps)

  def moveBackward(nSteps: Int = 1) =
    Robot(position - nSteps)

}

// Initial state
val robot = new Robot(0)

And its use:

robot moveForward(10) moveBackward() position

The code above is an oversimplification but shows the basic tricks behind Scala DSLs, namely: The use of infix notation, families of states and transitions only usable within state definitions.

A bit of theory: Really? State Machines?

Is the state machine model actually needed to implement DSLs? Yes, if you like avoiding shooting yourself in the foot.

shootfoot

Immutable state machines are easy to understand, maintain and expand.

On the other hand, DSLs are languages, formal languages with grammars with a place in Noam Chomsky’s classification, commonly, Regular Grammars and Context-Free grammar.

  • Which theoretical machine is able to recognize/process languages with regular grammars? A finite state automaton
  • In the case of Context-Free grammar languages, they can be processed by push-down automatons which (ALERT! Oversimplification ahead) can be regarded as a finite automaton enjoying the perk of making use of its own stack to place and read symbols.

The transition model described afore seems to be just made for implementing this kind of machines. A self-answered question arises as to whether DSLs’ developers should dedicate their efforts to find buggy and flimsy solutions when such a solid model is available.

– Well, I suggest you gentlemen invent a way to put a square peg in a round hole.

gene_krantz

– Sir, we have plenty of round pegs!

engineer

Coming soon…

In the next episode: A practical DSLs development step by step, no more history, theory or that silly things nobody cares about! Just…

letsdoit

Scala: Un lenguaje para gobernarlos a todos (I)

Al principio, no teníamos lenguajes de programación, sólo código máquina. Entonces, la programación automática vino para salvarnos a todos. Proporcionando anillos de poder para controlar a las máquinas: Estos fueron llamados “Lenguajes de programación”. Se diversificaron, sirvieron de inspiración para otros nuevos y acabaron constituyendo un rico ecosistema. De entre ellos surgió un nuevo y útil género de lenguajes para ordenadores: Los DSLs.

DSLs

Los lenguajes de propósito específico, Domain Specific (computer) Languages, existen desde hace bastante tiempo. La definición académica para este sub-grupo establece que han de estar enfocados en el marco de un dominio concreto de aplicación. Los DSLs son pequeños y concisos, esto implica que son de gran ayuda ya que guían a sus usuarios a través del proceso de describir acciones, entidades y relaciones dentro de su dominio de aplicación. Aún más importante es el hecho de que están hechos y diseñados para servir a un único propósito.

Si soy un matemático y estoy haciendo un estudio estadístico con mi ordenador ¿Por qué debería preocuparme acerca de la gestión de memoria?

No es de sorprender que haya surgido un gran reino de mini-lenguajes que han tomado el poder sobre actividades teles como el aprendizaje automático, la programación científica, el diseño y gestión de hardware, modelado de datos, …

¡Y a mi qué! ¿No era esto un blog sobre Scala?

¡Cierto! todo esto puede sonar a antiguas batallitas. Sin embargo, en una actualidad en la que todos parecemos estar obsesionados por la adquisición, almacenamiento y análisis de grandes volúmenes de datos que son, a su vez, de naturaleza muy variable (o lo que es lo mismo, mezclando peras con manzanas); Estamos obligados a enfrentarnos a docenas de formas de tratar estos datos y muchas de estas formas no son más que casos concretos de DSLs: SQL, HiveQL, CQL, bash, grep, awk, R… !Por favor! !Cuando va a acabar esta lista! Probablemente: Nunca. Y eso que estamos obviando lo que está por venir.

Que maravilla si hubiese una herramienta que nos ayudase, pobres humanos, a construir DSLs, un meta-DSL que, de alguna manera, nos guiase a la hora de desarrollar nuevos DSLs. Esta herramienta existe, y se llama Scala.

Cuando los creadores de este gran (no sólo por sus bondades sino por su extensión) lenguaje de programación le pusieron nombre, no sólo lo hicieron pensando en su capacidad para la re-utilización de código y potencial uso en entornos concurrentes con facilidad para escalar horizontalmente, sino que también tenían en mente su potencial para ser fácilmente expandido. Quizás sea esa la razón de su basta extensión en sintaxis y herramientas. Algunas de las características de Scala en la dirección de la expansibilidad son:

  • Notación infija: Cualquier llamada a un método que recibe un objeto como parámetro puede ser escrita de forma infija. Esto es,
    objA.metodo(objB) puede escribirse como objA metodo objB.
  • No existencia de operadores: A diferencia de otros lenguajes, carece de operadores como elementos con entidad propia. Todo son métodos cuyas reglas de precedencia y asociatividad están determinadas por el último carácter del nombre del método.Así, cualquier método cuyo nombre no acabe en el caracter `:` asocia de izquierda a derecha: obj1 + obj2 es lo mismo que obj1.+(obj2) en tanto que obj1 +: obj2 es lo mismo que obj2.+:(obj1).
    Algo parecido ocurre con la precedencia ya que existe un orden de importancia de caracteres finales, un ejemplo es la mayor importancia de `*` frente a `+` :
    obj1 + obj2 * obj3  
    es siempre igual a obj1 + (obj2 * obj3).Esta es la lista de importancia (de menor a mayor prioridad):

    • Todas las letras del alfabeto, en mayúsculas o minúsculas.
    • |
    • ^
    • &
    • Los símbolos = y !
    • Los símbolos < y >
    • :
    • Las operaciones aritméticas + y –
    • Las operaciones aritméticas *, / y %
    • Cualquier otro caracter especial.
  • Características avanzadas de programación orientada a objetos: object, trait, …

 

Estas tres características se combinan para proporcionar un entorno completo para el desarrollo de DSLs internos en el seno de Scala.

Primeros pasos

La notación infija sienta las base sobre la que construir nuestros lenguajes embebidos en Scala.

Partiendo, por ejemplo, del siguiente trait:

trait MovingRobot {
  def moveForward(): MovingRobot = {
    println("Robot moved one position forward")
    this
  }
  def moveBackward(): MovingRobot = {
    println("Robot moved one position backward")
    this
  }
}

Que puede mezclarse en la declaración de un objeto:

object robot extends MovingRobot

Podemos modelar los movimientos del un robot virtual llamando a los métodos de MovigRobot de una forma tradicional:

robot.moveForward.moveBackward

Pero el uso de la notación infija proporciona da lugar a un código mucho más cercano al lenguaje natural:

robot moveForward() moveBackward
robot moveForward
robot moveBackward

Este es el tipo más simple de los DSLs embebidos en Scala y sirve como punta de partida para enfoques más avanzados.

Transiciones de estado

Sí, simple y también imperativo además de ser de poca utilidad. Los comandos no están cambiando el estado del sistema más allá del efecto lateral que implica la impresión de caracteres por la salida estándar realizada por medio de println:

simpliest_automaton

Con DSLs de este nivel, existen dos opciones para modelar los efectos de las instrucciones del DSL:

  • El enfoque mutable: Es el más sencillo para aquellos llegados a Scala desde lenguajes imperativos pero, definitivamente, es mucho más arriesgado en lo que a la introducción de bugs respecta. La idea es muy similar a a que hay detrás de tantos builders o acumuladores de Java, por ejemplo, StringBuilder:El estado del acumulador es la cadena que se está componiendo. Métodos, como append(double d), devuelven una referencia a la instancia del acumulador cuyo estado se está modificando como consecuencia de la llamada. De esta manera, la misma referencia se devuelve llamada tras llamada ya que es la misma instancia de StringBuilder la que muta. ¿Suena familiar?

    mutable_state

  • El enfoque inmutable (o el camino a la sabiduría):  ¡No se debe cambiar nada! Hay que devolver un nuevo estado con los atributos derivados del estado anterior y la acción realizada. De ahora en adelante, sólo se tratará esta técnica.

La belleza de la segunda solución radica en que cada acción devuelve un único estado que mantiene una relación 1 a 1 con los estados reales del sistema modelado. Esto significa que las entidades del código que implementa al DSL son un reflejo exacto de los cambios y estados que se desean representar. Además, el estado es inmutable por definición.

estado
Del lat. status.Escr. con may. inicial en aceps. 6 y 7.
1. m. Situación en que se encuentra alguien o algo, y en especial cada uno de sus sucesivos modos de ser o estar.

(www.rae.es)

Explicar el por qué del hecho de que la programación basada en la inmutabilidad es más segura (en lo que la introducción de bugs respecta) que la basada en la mutabilidad de objetos está fuera del alcance de este artículo, cientos de explicaciones están al alcance de buscador. Algunas razones están muy bien resumidas en este artículo de IBM.

Incluso, los creadores de Java, decidieron que la inmutabilidad era mejor, al menos para sus cadenas de texto.

state_transition

Devolviendo en cada transición un, totalmente nuevo, estado se reduce la lista de responsabilidades del código que implementa dicha transición a sólo una: Generar un nuevo estado. Esto genera diseños mucho más sencillos para el conjunto del DSL. No pueden darse cambios inesperados fuera de llamadas explícitas a los métodos de transición.

Al grano de las  transiciones inmutables

Siguiendo el extremadamente complejo ejemplo de nuestra API para robots unidimensionales (llegados a este punto, un ávido lector de Scalera probablemente se de cuenta de que el anterior reto Scalera incluía un bonito DSL). Este API puede cambiarse para seguir el enfoque funcional arriba descrito:

// All states extend `RobotState`
trait RobotState {
  def position: Int
}

// Transitions which can be mixed with any state for which they
// make sense.

trait MovementTransitions {
  self: RobotState =>

  def moveForward(nSteps: Int = 1): RobotState with MovementTransitions

  def moveBackward(nSteps: Int = 1): RobotState with MovementTransitions

}

// States
// In this example, states only differ in the robot position so they all
// are represented by the same case class.
case class Robot(position: Int) extends RobotState with MovementTransitions {

  def moveForward(nSteps: Int = 1) =
    Robot(position + nSteps)

  def moveBackward(nSteps: Int = 1) =
    Robot(position - nSteps)

}

// Initial state
val robot = new Robot(0)

A continuación, un sencillo ejemplo de uso:

robot moveForward(10) moveBackward() position

Esto es una simplificación extrema que muestra las técnicas básicas detrás de los DSLs embebidos en Scala que puede resumirse en:

  • El uso de la notación infija.
  • De familias de estados y transiciones entre estos.
  • Limitación de las transiciones de forma que sólo pueden producirse desde un estado y resultar en otro completamente nuevo que, podría compartir, la mayoría de los atributos con su predecesor.

Un poco de teoría: Máquinas de estado ¿En serio?

¿El realmente necesario un modelo de diseño y programación basado en máquinas de estado?  La respuesta es sí, siempre que no quieras acabar dándote un tiro en tu propio pie.

shootfootLas máquinas de estados inmutables son sencillas de mantener, entender y expandir.

Por otro lado, hay que tener en cuenta que los DSLs no son otra cosa que lenguajes formales con gramáticas que tienen su lugar en la clasificación de Chomsky: Habitualmente Gramáticas regulares y gramáticas independientes de contexto.

  • ¿Qué tipo de máquina es capaz de procesar/reconocer lenguajes basados en gramáticas regulares? Los autómatas finitos.
  • En el caso de las gramáticas independientes de contexto, son los autómatas de pila los que pueden reconocer/procesar sus lenguajes. Estos (¡Ojo! Simplificación) no son más que autómatas finitos que pueden hacer uso de una pila auxiliar dónde colocar y de dónde leer símbolos que pueden determinar el resultado de una transición, junto con el estado anterior y la entrada.

El modelo de transiciones expuesto en las secciones anteriores parece encajar a la perfección con las máquinas teóricas de estado. Surge una pregunta que es, as su vez, su propia respuesta: ¿Debería un desarrollador invertir su tiempo en buscar nuevas soluciones, poco probadas y que pueden ser inestables o estar cargadas de bugs cuando tiene un modelo tan firme a su alcance?

– Bien, les sugiero caballeros que inventen una forma de encajar una clavija cuadrada en un agujero redondo.

gene_krantz

– Pero, señor, ¡Si nos sobran clavijas redondas!

engineer

En el siguiente episodio…

Desarrollo práctico de un DSL útil, paso a paso. Sin historias de guerra, teoría y sin ninguna de esas cosas “tan aburridas”. Sólo…

letsdoit