viernes, 29 de diciembre de 2017

Comparando F# con C#


Vamos a comparar C# con F# a traves de un ejemplo, una suma simple. El problema es simple: "sumar los cuadrados de 1 a N".

Primero, veamos el código F# :

// define the square function
let square x = x * x

// define the sumOfSquares function
let sumOfSquares n =
   [1..n] |> List.map square |> List.sum

// try it
sumOfSquares 100

Y listo, como podemos ver podemos utilizar el operador |> para que la salida de una función, sea entrada de otra. Es similar a el operador |> de elixir.

Si vemos la linea: let sumOfSquares n = [1..n] |> List.map square |> List.sum

  1. Crea una lista de enteros de 1 a n. 
  2. Luego toma esa lista y aplica una función square usando la biblioteca llamada List.map.
  3. Luego acumula todos los elementos de la lista resultante. 

Ahora veamos una implementación en C# utilizando el estilo clásico (no funcional) :

public static class SumOfSquaresHelper
{
   public static int Square(int i)
   {
      return i * i;
   }

   public static int SumOfSquares(int n)
   {
      int sum = 0;
      for (int i = 1; i <= n; i++)
      {
         sum += Square(i);
      }
      return sum;
   }
}

¿Cuáles son las diferencias?

  • El código F# es más compacto
  • El código F# no tenía ninguna declaración de tipo
  • Con F# podemos desarrollar interactivamente

La diferencia más obvia es que hay mucho más código C#. 13 líneas en C# en comparación con 3 líneas en F# (ignorando los comentarios). El código C# tiene mucho "ruido", cosas como llaves, puntos y comas, etc. Y en C# las funciones no pueden estar solas, sino que deben agregarse a alguna clase ("SumOfSquaresHelper"). F# usa espacios en blanco en lugar de paréntesis, no necesita terminador de línea y las funciones pueden ser independientes.

En F#, es común que las funciones completas se escriban en una línea, como la función "cuadrada". La función sumOfSquares también podría haber sido escrita en una línea. En C# esto es mal visto y es considerado una mala práctica.

Cuando una función tiene múltiples líneas, F# usa indentación para indicar un bloque de código, lo que elimina la necesidad de llaves. (Si alguna vez ha usado Python, esta es la misma idea). Entonces la función sumOfSquares también podría haber sido escrita de esta manera:

let sumOfSquares n = 
   [1..n] 
   |> List.map square 
   |> List.sum

La siguiente diferencia es que el código C# tiene que declarar explícitamente todos los tipos utilizados. Por ejemplo, el parámetro n es de tipo int y el tipo de retorno SumOfSquares es int. Sí bien, C# permite usar la palabra clave "var" en muchos lugares, pero no para los parámetros y tampoco para tipos de funciones.

En el código F#, no declaramos ningún tipo. Este es un punto importante: F# parece un lenguaje sin tipo o de tipado dinámico, pero en realidad es igual de seguro que C#, de hecho, ¡aún más! F# usa una técnica llamada "inferencia de tipo" para inferir los tipos que está utilizando desde su contexto. Funciona increíblemente bien la mayor parte del tiempo, y reduce la complejidad del código inmensamente.

En este caso, el algoritmo de inferencia de tipo observa que comenzamos con una lista de enteros. Esto a su vez implica que la función cuadrada y la función suma también deben tomarse, y que el valor final debe ser un int. Puede ver cuáles son los tipos inferidos mirando el resultado de la compilación en la ventana interactiva. Verás algo como:

val square : int -> int

lo que significa que la función "cuadrado" toma un int y devuelve un int. Esta notación me recuerda mucho a Haskell o Scala.

Si la lista original hubiera utilizado flotantes en su lugar, el sistema de inferencia tipo habría deducido que la función cuadrada utilizada flotantes en su lugar:

// define the square function
let squareF x = x * x

// define the sumOfSquares function
let sumOfSquaresF n = 
   [1.0 .. n] |> List.map squareF |> List.sum  // "1.0" is a float

sumOfSquaresF 100.0

¡La verificación de tipos es muy estricta! Si intenta usar una lista de flotantes ([1.0..n]) en el ejemplo original sumOfSquares, o una lista de ints ([1 ..n]) en el ejemplo sumOfSquaresF, obtendrá un error de tipo del compilador.

Finalmente, F# tiene una ventana interactiva donde puedes probar el código inmediatamente y jugar con él. En C# no hay una manera fácil de hacer esto.

Por ejemplo, puedo escribir mi función cuadrada e inmediatamente probarla:

// define the square function
let square x = x * x

// test
let s2 = square 2
let s3 = square 3
let s4 = square 4

Muchas personas afirman que el diseño de códigos impone de forma interactiva buenas prácticas de diseño, como el desacoplamiento y las dependencias explícitas, y por lo tanto, el código que es adecuado para la evaluación interactiva también será un código que es fácil de probar. Por el contrario, el código que no se puede probar de forma interactiva probablemente también sea difícil de probar.

El ejemplo anterior fue escrito usando C# viejo. C# ha incorporado muchas características funcionales, y es posible reescribir el ejemplo de una manera más compacta utilizando las extensiones LINQ.

Así que aquí hay otra versión C#: 

public static class FunctionalSumOfSquaresHelper
{
   public static int SumOfSquares(int n)
   {
      return Enumerable.Range(1, n)
         .Select(i => i * i)
         .Sum();
   }
}

Sin embargo, además del ruido de las llaves, los puntos y los puntos y comas, la versión C# necesita declarar el parámetro y los tipos de retorno, a diferencia de la versión F#.

Si bien el ejemplo fue trivial, nos permitio revisar las deferencias entre F# y C#.

Dejo link: https://fsharpforfunandprofit.com/posts/fvsc-sum-of-squares//