Seven deadly sins of lambda expressions

A few weeks ago, we talked about lambda expressions. We learnt how to use them and some of the advantages they offer.

But, as the old saying goes, all that glitters is not gold. Last April, Jamie Allen came to our Scala meetup in Madrid and spoke about the dark sideof lambda expressions: “What you need to know about lambdas”. In this post we’ll give you an overview of that talk and support the fact that lambda expressions are not to be taken lightly.

First problem: Not reusable.

As a name is not being given to the function, we won’t be able to use it anywhere else in the code.

Second problem: Cannot be tested in isolation.

Provided that it is an ad-hoc function, implemented and embedded at some specific point, it cannot be tested in a separate way.

Third problem: Maintainance.

In general, it is not good practice to use excessive complicated and nested lambda expressions. With them, our code becomes somewhat obscured and thus, difficult to follow and maintain.

List(1, 2, 0, 3, 4).flatMap(
  x => (1 to x).filter(
    _ % 2 == 0
  ).map( _ * 2 )
)

//List(4, 4, 4, 8)

Fourth problem: error messages.

This problem varies depending on the programming language used. In some cases, error messages will be manageable and in many others, you may feel the desire to slice your wrists off. In our case, in Scala, when there’s an error in an anonymous function we’ll get an error like this:

List(1, 2, 0, 3, 4).map(1 / _)

java.lang.ArithmeticException: / by zero
at $anonfun$1.apply$mcII$sp(<console>:8)
at $anonfun$1.apply(<console>:8)
at $anonfun$1.apply(<console>:8)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.List.foreach(List.scala:318)
...

Given that no name was given to the function, the system will throw the error in a $$anonfun. A bit of a WTF, isn’t it?

Fifth problem: Hard time debugging.

Actually, this is not a particular problem of lambda expressions but of the way the code is expressed when using them. If several lambda expressions are written in the same line and there’s an error in any of them, we won’t be able to know which one is causing trouble. That’s why it is highly convenient to separate them in several lines.

The expression

List(1, 2, 0, 3, 4).map(_ + 1).filter(_ % 2 == 0).map(_ * 2)

would be better written this way:

List(1, 2, 0, 3, 4).map(_ + 1)
  .filter(_ % 2 == 0)
  .map(_ * 2)

Sixth problem: they can encapsulate variables with state

Lambda expressions can access the whole state of their scope. Therefore, they might have access to some mutable value and lead to different results in different execution contexts. Something that we definitely don’t want in functional programming.

Seventh problem … 

Their problems can be enclosed into just six statements and lead to inconsistent post titles…

desk_flip

Conclusion

As we have seen, the use of lambda expressions may give rise to a few headaches. That’s why it is recommended to only use those ones which are less complex. In case we want to do something more sophisticated or intricate, the best thing to do is to define a method in order to test and debug it without any trouble. Besides, special precautions shall be taken not to use scope variables that may change its value. We should not deviate from our functional perspective.

I heartily recommend you to take a look at the talk . Many more details were treated in it (this post is just a brief overview) and it might help us to work with lambda expressions in a better way.

Anuncios

Los 7 pecados capitales de las expresiones lambda

Hace unas semanas estuvimos hablando de expresiones lambda. Vimos como utilizarlas y algunas de las ventajas que ofrecen.

Sin embargo, no es oro todo lo que parece. El pasado mes de Abril, Jamie Allen vino al meetup de Scala en Madrid y nos habló del lado oscuro de las expresiones lambda: “What you need to know about lambdas”. En este post vamos a hacer un overview de la charla, y corroborar que no podemos usar expresiones lambda de cualquier manera.

Primer problema: No es reutilizable

Debido a que no estamos poniendo nombre a la función, no podremos reutilizarla en otros lugares.

Segundo problema: No se puede testear de forma aislada

Como es una función implementada de forma ad-hoc, e incrustada en algún lugar, no es posible probarla de forma aislada.

Tercer problema: Mantenimiento

No es bueno abusar de expresiones lambda complicadas y anidadas. Provoca que nuestro código se vuelva dificil de seguir, y por tanto, poco mantenible.

List(1, 2, 0, 3, 4).flatMap(
  x => (1 to x).filter(
    _ % 2 == 0
  ).map( _ * 2 )
)

//List(4, 4, 4, 8)

Cuarto problema: trazas de error

Este problema varía en función del lenguaje de programación que se utilice. En algunos casos las trazas de error serán mejores y en otros casos te darán ganas de cortarte las venas. En nuestro caso, en Scala, cuando aparece un error en una función anónima obtendremos un mensaje de error con esta pinta:

List(1, 2, 0, 3, 4).map(1 / _)

java.lang.ArithmeticException: / by zero
at $anonfun$1.apply$mcII$sp(<console>:8)
at $anonfun$1.apply(<console>:8)
at $anonfun$1.apply(<console>:8)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
at scala.collection.immutable.List.foreach(List.scala:318)
...

Debido a que no se ha dado nombre a la función, el sistema nos devolverá un error en un $$anonfun. Un poco WTF.

Quinto problema: Dificultad para depurar

Realmente no es un problema propio de las expresiones lambda, sino de la forma de expresar el código.
Si encadenamos varias expresiones lambda en la misma línea y existe algún error en alguna de ellas, no podremos saber cual de ellas ha fallado. Por ello es conveniente separarlas en varias líneas.

La expresión

List(1, 2, 0, 3, 4).map(_ + 1).filter(_ % 2 == 0).map(_ * 2)

será mejor expresarla de esta forma:

List(1, 2, 0, 3, 4).map(_ + 1)
  .filter(_ % 2 == 0)
  .map(_ * 2)

Sexto problema: Puede encapsular variables con estado

Una función lambda puede tener acceso a todo el estado de su scope. Por tanto, puede tener acceso a algún valor mutable y hacer que tengamos diferentes resultados en cada ejecución. Algo que no queremos en la programación funcional.

Séptimo problema …

Sus problemas se pueden encapsular en solo seis y provocan que algunos títulos de post no encajen del todo.

desk_flip

Conclusión

Como hemos podido ver, usar expresiones lambda puede originar algunos quebraderos de cabeza. Por ello se recomienda usar solo aquellas que tengan una baja complejidad. En caso de que queramos hacer algo más complejo o enrrevesado será mejor definir un método para poder testearla y depurarla sin ningún tipo de problema. Además, hay que tener especial cuidado de no utilizar variables del scope que puedan cambiar de valor. Debemos seguir un enfoque funcional.

Os recomiendo que le echéis un ojo a la charla . En ella se profundiza mucho más (este post solo ha sido un pequeño resumen) y puede ayudarnos a trabajar con expresiones lambda de una mejor forma.

Lambda Expressions Everywhere

From the makers of ‘Anonymous classes’ comes… anonymous functions. Or as friends call them: lambda expressions. Anonymous functions, as we already saw in a previous post, are functions that don’t need to be declared previously. Let’s see an example: A function which returns the length of a String removing any blank space in it could be defined as:

  def length(s: String): Int = s.replaceAll(" ", "").length

Its equivalent anonymous function would be:

  (s: String) => s.replaceAll(" ", "").length

The type of this expression is: String => Int

Where can we use anonymous functions?

The most common way to use them is in function that accept other functions as parameter. This type of functions are called Higher Order Functions. Functions that return a function as result are also known as higher order functions. Fantastic, extraordinary… an example, please? Thanks to our unlimited imagination, we’ll create a simple integer calculator. Let’s define our calculator in the following way:

  object Calculator {
    
    def sum(n1: Int, n2: Int): Int = n1 + n2

    def subtract(n1: Int, n2: Int): Int = n1 - n2

    def multiplicate(n1: Int, n2: Int): Int = n1 * n2

    def divide(n1: Int, n2: Int): Int = n1 / n2
  }

Hmmmm, cool. A class with some methods in it. It works but… what if we try and take a more generic turn?
meme-functions

What we really want is to apply a binary operation. That operation, given two integers, will return a new integer. We could say that we need a method like this:

  def calculate(n1: Int, n2: Int)(operation:(Int, Int) => Int) =
    operation(n1, n2)

As can be appreciated, we are actually passing a function as parameter. We are using an anonymous function. In order to make it more readable, we can create a new type that represents the integer binary operation: (Int, Int) => Int.

  type Operation = (Int, Int) => Int

And if we apply this to our calculate method:

  def calculate(n1: Int, n2: Int)(operation: Operation) =
    operation(n1, n2)

This method can be used in several ways:

1) The easiest one: we insert a previously defined function.

  def addition(n1: Int, n2: Int) = n1 + n2

calculate(1, 2)(addition) //returns 3

2) There is no function defined. Besides, the function is pretty simple and it won’t be used elsewhere in the code. All right then, we can use a lambda expression:

  calculate(1, 2)((n1: Int, n2: Int) => n1 + n2) //returns 3

As can be seen, in this case, an anonymous function is used to define the operation we want to apply to the two integers. It is a nimble and quick way to define functions. But that’s not all. Thanks to the type inference, we can avoid writing the type of the input parameters:

  calculate(1, 2)((n1, n2) => n1 + n2) //returns 3

And with a spoonful of syntactic sugar…

  calculate(1, 2)(_ + _) //returns 3

What advantages do we have when compared to the object oriented implementation?

  • Our code is significantly reduced:
  object Calculator {

    type Operation = (Int, Int) => Int
    
    def calculate(n1: Int, n2: Int)(operation: Operation) =
      operation(n1, n2)
  }
  • We are not bound to use only the operations that we have hardcoded in our implementation. We can create more complex operations on the fly:
  calculate(1, 2)((n1, n2) => (n1 + n2) * (n1 - n2))

As it always happens in Scala, its misuse might lead to unpleasant consequences. In future posts, we’ll take a look at the dark side of lambda expressions. In the meantime, we shall remain naively happy.

Seeking anonymity. Anonymous classes

What are anonymous classes?

As their name already suggests, anonymous classes are unnamed classes, classes that don’t have a name. They are the classes that corrupt people like the most. They are not declared… BA DUM, TSS!

Now seriously, how can I create an instance from an anonymous class? Easy, simply by using the reserved word new and defining the body with braces.

val myPoint = new{ val x = 1; val y = 2 }

This way, we create an instance that has two integer values: x and y. However, as can be appreciated, we have given no name to this class.

tGWoYYo

Syntactic sugar

A few weeks ago, we talked about traits and how we could create an instance from a trait by means of an anonymous class.
Perhaps at that time, a WTF the size of Sebastopol crossed your mind. If we take a look at that post, the syntax for creating an instance from a trait was something like this:

  trait AnonymousHero {
    def superpower: String
  }

  val myHero = new AnonymousHero {
    def superpower = "I can compile Scala with my brain"
  }

We are creating an instance from a trait! They are no classes anywhere! Is this black magic? Well, no, it’s sintactic sugar.

Actually, what is really going on underneath is something like this:

  class AnonymousHeroClass extends AnonymousHero {
    def superpower: String = "I can compile Scala with my brain"
  }

  val myHero = new AnonymousHeroClass

As can be seen, when instantiating a trait, what is really happening is that a class that extends that trait is created. After that, an instance of that class is created.

This way, instances can be created from traits without any boilerplate code.

Bonus-track: Anonymous functions

Lambda expressions can be used in Scala, that is, anonymous functions. Further on, in other posts we’ll get to the importance of the use of anonymous functions in, for instance, methods that accept functions as parameter.
Anonymous functions, just like anonymous classes, are functions that don’t need to be declared.
This is an example of anonymous function:

  (x: Int) => x + 1

In this case, the anonymous function expects an integer and returns that same integer plus one unit.

We must not forget that, in Scala, functions are objects in reality. By taking this last consideration into account, we can relate anonymous functions (or lambda expressions) to anonymous classes.

So, when we use the anonymous function (x: Int) => x + 1, what is really happening is the creation of an instance of an anonymous class from the trait Function1:

new Function1[Int, Int] {
  def apply(x: Int): Int = x + 1
}

Everything fits!

tGWoYYo

Expresiones Lambda a cholón

De los creadores de las clases Anónimas llega…..funciones anónimas. O como les llaman los amigos: expresiones lambda. Las funciones anónimas, como ya vimos en un post anterior, son funciones que no es necesario declarar previamente. Vamos a ver un ejemplo: Una función que devuelva la longitud de un String eliminando los espacios en blanco la podemos definir de la siguiente forma:

  def length(s: String): Int = s.replaceAll(" ", "").length

Su equivalente en función anónima será la siguiente:

  (s: String) => s.replaceAll(" ", "").length

El tipo de esta expresión será: String => Int

¿Dónde podemos usar funciones anónimas?

Lo más usual es utilizarlo en funciones que, como parámetro, aceptan otras funciones. A este tipo de funciones se les llama Funciones de Orden Superior (o Higher Order Function). También son funciones de orden superior aquellas que como resultado devuelven una función. ¿Pues muy bien. Mu rico….¿un ejemplo? Gracias a una imaginación desbordante, vamos a crear una calculadora de números enteros muy simple. Podemos definir una calculadora de la siguiente forma:

  object Calculator {
    
    def sum(n1: Int, n2: Int): Int = n1 + n2

    def subtract(n1: Int, n2: Int): Int = n1 - n2

    def multiplicate(n1: Int, n2: Int): Int = n1 * n2

    def divide(n1: Int, n2: Int): Int = n1 / n2
  }

Mmmmm, guay. Una clase con varios métodos. Funciona. Pero…¿y si probamos a darle un giro más genérico? meme-functions Realmente lo que queremos es aplicar una operación binaria. Dicha operación, dado dos enteros, devolverá un nuevo entero. Podemos decir que necesitamos un método como este:

  def calculate(n1: Int, n2: Int)(operation:(Int, Int) => Int) =
    operation(n1, n2)

Como se puede ver, estamos pasando realmente una función como parámetro. Estamos utilizando una función anónima. Para hacerlo más legible, podemos crear un nuevo tipo que represente la operación binaria de enteros: (Int, Int) => Int.

  type Operation = (Int, Int) => Int

Y lo aplicamos a nuestro método calculate:

  def calculate(n1: Int, n2: Int)(operation: Operation) =
    operation(n1, n2)

Este método puede ser llamado de varias formas:
1) La facilita: le inserto una función definida anteriormente:

  def addition(n1: Int, n2: Int) = n1 + n2

calculate(1, 2)(addition) //returns 3

2) No tengo una función definida. Además, la función es simple y no se volverá a usar en ningún otro punto del código. Pues nada, meto una expresión lambda

  calculate(1, 2)((n1: Int, n2: Int) => n1 + n2) //returns 3

Como se puede observar, en este caso se utiliza una función anónima para definir la operación que queremos aplicar a los dos enteros. Es una forma ágil y rápida de definir funciones. Pero no se queda aquí. Gracias a la inferencia de tipos, nos podemos saltar marcar el tipo de los parámetros de entrada:

  calculate(1, 2)((n1, n2) => n1 + n2) //returns 3

Y si aplicamos un poco de sintatix sugar….

  calculate(1, 2)(_ + _) //returns 3

¿Qué ventajas tenemos respecto a la implementación orientada a objetos?

  • Nuestro código se reduce de manera significativa:
  object Calculator {

    type Operation = (Int, Int) => Int
    
    def calculate(n1: Int, n2: Int)(operation: Operation) =
      operation(n1, n2)
  }
  • No estamos atados a utilizar solo las operaciones que hemos definido a fuego en nuestra implementación. Podemos hacer otras operaciones más complejas al vuelo:
  calculate(1, 2)((n1, n2) => (n1 + n2) * (n1 - n2))

Como siempre pasa en Scala, su mal uso puede provocar problemas algo desagradables. En futuros post veremos el lado oscuro de las expresiones lambda. Mientras tanto viviremos felices.