Translate

domingo, 31 de mayo de 2020

Crear un árbol binario con Haskell


En informática, los árboles crecen al revés. La raíz única está en la parte superior, y las hojas están en la parte inferior. En realidad depende como se lo vea. 

El árbol binario se usa a menudo para almacenar datos ordenados, para permitir una búsqueda eficiente, por ejemplo, un directorio telefónico.

Vamos a definir un tipo de datos Haskell para árboles, almacenando valores enteros.

data Tree = Leaf | Node Int Tree Tree deriving Show

Un valor de árbol puede ser una hoja o un nodo. Tenga en cuenta que este es un tipo de datos recursivo, ya que un Nodo almacena un árbol de enteros y tiene ramificaciones a dos subárboles (a veces llamados hijos). Como se ve definimos que un árbol es una hoja o un nodo con el dato y dos arboles. 

Aquí está el árbol más simple: es solo una hoja.

Leaf

Aquí hay un árbol con un Nodo que contiene el valor 3 y dos hojas.

Node 3 Leaf Leaf

Si escribe esto en ghci, verá los valores devueltos cuando construya estos árboles, siempre que su tipo de datos Tree derive la clase Show type.

El tipo de este valor es:

let l = Node 3 Leaf Leaf
:t l

El tipo del nodo constructor:

:t Node

Esta es una función: el nodo constructor toma tres argumentos y devuelve un resultado de árbol.

Ahora escribamos una función para calcular la profundidad de un árbol: esta es la cantidad máxima de ramas desde la raíz hasta cualquier hoja. Para escribir esta función, haremos coincidir patrones en los diferentes tipos de árbol, es decir, valores de hoja y nodo. Cada hoja es un caso base, pero para cada nodo, necesitamos procesar recursivamente los dos árboles secundarios.

treeDepth :: Tree -> Int
treeDepth Leaf = 0
treeDepth (Node _ leftSubtree rightSubtree) = 
  1 + max (treeDepth leftSubtree) (treeDepth rightSubtree)

Observe el _ en la línea 3, que es un valor de "don’t care" o "no me importa", ya que descartamos el entero en cada nodo.

¿Qué tal una función para verificar si un árbol está ordenado correctamente? La estructura de datos invariable que queremos es que, para cualquier valor de almacenamiento de Nodo x, todos los valores en su subárbol izquierdo sean <x, y todos los valores en su subárbol derecho sean> = x.

Entonces, esta función tomará un Árbol, un valor mínimo, un valor máximo y devolverá un Bool. isSortedTree :: Árbol -> Int -> Int -> Bool

Una hoja se ordena automáticamente, ya que no contiene un valor. Para cada nodo, tenemos que verificar que el valor entre los valores mínimo y máximo, que comienzan lo más lejos posible, luego se dividen en rangos más pequeños según el valor en el nodo.

isSortedTree :: Tree -> Int -> Int -> Bool
isSortedTree Leaf _ _ = True
isSortedTree (Node x leftSubtree rightSubtree) minVal maxVal =
    let leftSorted   = isSortedTree leftSubtree minVal x
        rightSorted = isSortedTree rightSubtree x maxVal
    in x >= minVal && x< maxVal && leftSorted && rightSorted

Hasta ahora, hemos estudiado las funciones de recorrido del árbol, donde revisamos la estructura de datos del árbol y hacemos algunos cálculos incrementales en cada nodo. Ahora queremos hacer una función de modificación del árbol. Esto genera un nuevo árbol que es una versión modificada del árbol de entrada.

La función particular que vamos a definir inserta un nuevo valor máximo. Pasamos por el árbol de entrada hasta encontrar el nodo de la derecha con una hoja a la derecha, luego reemplazamos esta hoja de la derecha con un nuevo nodo que contiene un nuevo valor máximo (uno más grande que el valor máximo anterior).

addNewMax :: Tree -> Tree
-- add a new max element to tree
addNewMax Leaf = Node 0 Leaf Leaf  -- input tree with no nodes
addNewMax (Node x t1 Leaf) = Node x t1 (Node (x+1) Leaf Leaf)  -- this is the rightmost Node
addNewMax (Node x t1 t2) = Node x t1 (addNewMax t2) -- intermediate node, go down right subtree

Esta función addNewMax toma un valor de entrada de árbol y devuelve un valor de salida de árbol. 

sábado, 30 de mayo de 2020

Inscripción gratuita a MongoDB.live

Me llego el siguiente mail de la gente de MongoDB para anotarme a Mongo.live un evento gratuito donde podes aprender sobre esta gran base de datos NoSql :

Únanse el 9 y 10 de junio* a MongoDB.live para profundizar en el contenido educativo y conocer las nuevas tecnologías. Aprenderá de los expertos en MongoDB para resolver desafíos y adelantarse a la competencia.

En MongoDB.live podrás:
  • Personalizar tu experiencia con sesiones centradas en temas como arquitectura de aplicaciones, rendimiento de consultas, modelo de datos, diseño de esquemas, análisis, seguridad, microservicios, migración a la nube y más.
  • Aprender de los clientes de MongoDB en todos los sectores sobre las herramientas y características que están utilizando para aumentar la agilidad de su organización y la productividad de sus equipos de desarrollo.
  • ¡Participar durante la conferencia para ganar puntos de actividad y competir con otros asistentes para ganar premios!
Después de asistir, estará equipado con los conocimientos esenciales para crear y administrar aplicaciones basadas en datos. ¡Esperamos verte ahí!


* MongoDB.live comenzará a las 10:00 a.m. ET el 9 de junio y se extenderá hasta el 10 de junio a las 4:00 p.m. ET.

jueves, 21 de mayo de 2020

Libros Gratuitos de Java Code Geek

Download IT Guides!

 

Vaadin Programming Cookbook

Vaadin is an open source web framework for rich Internet applications. In contrast to JavaScript libraries and browser-plugin based solutions, it features a server-side architecture,...

 
 

GWT Programming Cookbook

Google Web Toolkit, or GWT Web Toolkit, is an open source set of tools that allows web developers to create and maintain complex JavaScript front-end applications in Java. Other than a...

 
 

Amazon Elastic Beanstalk Tutorial

AWS Elastic Beanstalk is an orchestration service offered from Amazon Web Services for deploying infrastructure which orchestrates various AWS services, including EC2, S3, Simple...

 
 

Hibernate Tutorial

Hibernate ORM (Hibernate in short) is an object-relational mapping framework, facilitating the conversion of an object-oriented domain model to a traditional relational database....

 

miércoles, 20 de mayo de 2020

Revolution.OS.2001

Quiero recomendarles un buen documental para pasar la pandemia : 



y de yapa:

martes, 19 de mayo de 2020

Design patterns



Quiero recomendar una pagina de patrones de diseño que esta buenisima, trae una descripción de casi todos los patrones con ejemplos y código. El codigo esta en deferentes lenguajes. 


lunes, 18 de mayo de 2020

Juego de Serie en C# con .net core


Cuando quiero aprender un nuevo lenguaje desarrollo un juego de series, es decir aparece una serie con un valor faltante y el jugador debe completarlo.

Uno de los requerimientos no funcionales es que se pueda agregar una serie nueva fácilmente, para cumplirlo vamos a utilizar las ventajas de herencia y polimorfismo.

Vamos a desarrollar este juego en C# para ello creamos el proyecto con : 
$ dotnet new console -lang C#

Luego de esto abrimos nuestro editor favorito, podemos utilizar visual code.  

Empecemos desarrollo de la serie, la serie tiene como responsabilidad generarse y si lo hacemos de esta manera podemos utilizar la potencia del polimorfismo, para que no quede acoplado la generación de la serie con el desarrollo del juego:

using System;
namespace dotNetSecuencia.model
{
    public abstract class Secuencia
    {
        protected int[] nros = new int[4];

        public Secuencia() {
            this.generar();
        }

        public int[] Nro {
            get {
                return nros;
            }
        }
        
        public abstract void generar();
    }
}

Ahora vamos a ver las implementaciones de secuencia:

using System;

namespace dotNetSecuencia.model
{
    public class SecuenciaPar : Secuencia
    {        
        public override void generar() {
            int semilla = new Random ().Next (99) * 2;
            for (int i = 0; i<4; i++) {
                this.nros[i] = (semilla + i * 2); 
            }
        }
    }
}

Y ahora la secuencia Impar: 

using System;

namespace dotNetSecuencia.model
{
    public class SecuenciaImpar : Secuencia
    {
        public override void generar() {
            int semilla = new Random ().Next (99) * 2;
            for (int i = 0; i<4; i++) {
                this.nros[i] = (semilla + i * 2) + 1; 
            }
        }
    }
}

Y ahora la secuencia Fibonacci :

using System;

namespace dotNetSecuencia.model
{
    public class SecuenciaFibonacci : Secuencia
    {
        public override void generar() {
            int until = new Random ().Next (15);
            this.nros[0] = 0;
            this.nros[1] = 1;
            this.nros[2] = 1;
            this.nros[3] = 2;
            for (int i = 0; i<until; i++) {
                 this.nros[0] = this.nros[1];
                 this.nros[1] = this.nros[2];
                 this.nros[2] = this.nros[3];
                 this.nros[3] = this.nros[1] + this.nros[2];
            }
        }
    }
}

Ahora vamos a ver el juego, este tiene la responsabilidad de verificar si el usuario acertó y tambien debe llevar los puntos: 

 using System;

namespace dotNetSecuencia.model
{
    public class Juego
    {
        private int puntaje = 0;
        private Secuencia secuencia = null;

        public Juego() 
        {
            this.generarSecuencia();
        }

        public int Nro1 {
            get {
                return this.secuencia.Nro[0];
            }
        }

        public int Nro2 {
            get {
                return this.secuencia.Nro[1];
            }
        }

        public int Nro4 {
            get {
                return this.secuencia.Nro[3];
            }
        }

        public int Puntaje {
            get {
                return this.puntaje;
            }
        }

        public void generarSecuencia() 
        {
            int semilla = new Random ().Next (3);
            switch (semilla)
            {
                case 0 :
                    this.secuencia = new SecuenciaPar(); 
                break;
                case 1 :
                    this.secuencia = new SecuenciaImpar(); 
                break;
                default:
                    this.secuencia = new SecuenciaFibonacci();
                break;
            }
        }

        public bool isOk(int valor) {
            bool result = false;
            if (this.secuencia.Nro[2] == valor) {
                this.puntaje++;
                result = true;
            } else {
                this.puntaje--;
            }
            this.generarSecuencia();
            return result;
        }
    }

}

Ahora programemos la interfaz por consola por supuesto : 

using System;
using dotNetSecuencia.model;
namespace dotNetSecuencia
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Bienvenido !!");
            Juego juego = new Juego();
            String respuestaStr;
            do {
                Console.WriteLine("La secuencia es : ");
                Console.WriteLine(" " + juego.Nro1 + " " + juego.Nro2 + "  __  " + juego.Nro4 );
                respuestaStr = Console.ReadLine();
                int respuesta = Convert.ToInt32(respuestaStr);
                if (juego.isOk(respuesta)) {
                    Console.WriteLine("Ganaste!! Tu puntaje es " + juego.Puntaje);
                } else {
                    Console.WriteLine("Perdiste!! Tu puntaje es " + juego.Puntaje);
                }
                Console.WriteLine("Quieres seguir Jugando [n/s] ? ");
                respuestaStr = Console.ReadLine();
            } while (respuestaStr != "n");
        }
    }
}

Y eso es todo!!! a jugar se a dicho!!

Dejo el repositorio git: 

sábado, 16 de mayo de 2020

Coconut, programación funcional simple, elegante en python



Coconut es un lenguaje de programación funcional que compila a Python. Como todo codigo válido en Python es válido en Coconut , el uso de Coconut solo extenderá y mejorará lo que ya es capaz de hacer en Python.

¿Por qué usar Coconut? Coconut está hecho para ser útil. Coconut mejora el repertorio de los programadores de Python para incluir las herramientas de la programación funcional moderna, de tal manera que esas herramientas sean fáciles de usar e inmensamente poderosas; es decir, Coconut hace a la programación funcional lo que Python hizo a la programación imperativa. Y el código Coconut se ejecuta igual en cualquier versión de Python.

Instalar Coconut es tan fácil como

instalando Python,
abriendo una línea de comandos,
y escribir:

pip install coconut

Veamos un poco de código: 

Un "hola mundo" : 

"hello, world!" |> print

lambdas:

x -> x ** 2

pattern-matching:

match [head] + tail in [0, 1, 2, 3]:
    print(head, tail)

composición de funciones: 

(f..g..h)(x, y, z)

Data types: 

data Empty()
data Leaf(n)
data Node(l, r)

def size(Empty()) = 0
addpattern def size(Leaf(n)) = 1
addpattern def size(Node(l, r)) = size(l) + size(r)

Sin, más dejo link: 
http://coconut-lang.org/

Funciones recursivas en listas parte 2


Si deseamos realizar una operación en cada elemento de una lista map es la función indicada. map aplica una función a cada elemento de una lista

map f [x0,x1,x2] -- > [f x0, f x1, f x2]

map es una de las herramientas más utilizadas en programación funcional. Un estilo común es definir un conjunto de cálculos simples usando el map y componerlos.

map f (map g xs) = map (f . g) xs

Map se puede definir de esta manera: 

map :: (a -> b) -> [a] -> [b]
map _ []     = []
map f (x:xs) = f x : map f xs

Una iteración sobre una lista para producir un valor se llama pliegue y existen varias variaciones: plegado desde la izquierda, plegado desde la derecha y varias variaciones que tienen que ver con la "inicialización" y algunas variaciones más avanzadas.

Los pliegues pueden parecer difíciles al principio, pero son extremadamente poderosos y se usan mucho. Y en realidad no son muy complicados.

foldl se pliega desde la izquierda y ed como una iteración en una lista, de izquierda a derecha.

Una aplicación típica es por ejemplo, foldl f z xs
Donde z es un valor inicial
El argumento xs :: [a] es una lista de valores que combinamos sistemáticamente utilizando la función proporcionada f
Una intuición útil: piense en el argumento z :: b como un "acumulador".
La función f toma el valor actual del acumulador y un elemento de lista, y da el nuevo valor del acumulador.

foldl :: (b-> a-> b) -> b -> [a] -> b

Similar a foldl, pero funciona de derecha a izquierda

foldr :: (a -> b -> b) -> b -> [a] -> b


Hemos visto que una lista [x0, x1, x2] también se puede escribir como
    x0: x1: x2: []

cons (:) sobre una lista usando la lista vacía [] como el acumulador da:

foldr (:) [] [x0, x1, x2]
  ->
  x0: x1: x2: []
¡Esto es idéntico a construir la lista usando (:) y []! Podemos formalizar esta relación de la siguiente manera:

foldr cons [] xs = xs
 
Veamos algunos ejemplos: 

suma xs = foldr (+) 0 xs
producto xs = foldr (*) 1 xs

De hecho, podemos "factorizar" las x que aparecen a la derecha de cada lado de la ecuación, y escribir:

sum = foldr (+) 0
producto = foldr (*) 1

(Esto a veces se denomina estilo "point free" porque está programando únicamente con las funciones; los datos no se mencionan directamente).


viernes, 15 de mayo de 2020

Programación explicada con música



Funciones recursivas en listas

Hay dos enfoques para trabajar con listas:
  • Escribir funciones utilizando definiciones recursivas que atraviesan la estructura de la lista.
  • Utilizar funciones estandar. 
Se prefiere el segundo enfoque, pero las funciones estándar de procesamiento de listas deben definirse, y esas definiciones usan el primer enfoque (definiciones recursivas).

Se crea una lista a partir de la lista vacía [] y la función cons :: a → [a] → [a] la cual se puede escribir con ":"

Una lista se puede definir como : 
  • [] o
  • (x: xs) para algunas x (el encabezado de la lista) y xs (la cola) donde (x: xs) 
La lista esta definida de forma recursiva, el caso base de la recursión es [], el caso de recursión (o inducción) es (x: xs).

La longitud de una lista se puede calcular de forma recursiva de la siguiente manera:

length :: [a] -> Int           -- function type
length [] = 0                  -- base case
length (x:xs) = 1 + length xs  -- recursion case

filter recibe un predicado (una función que da un resultado booleano) y una lista, y devuelve una lista de los elementos que satisfacen el predicado.
filter :: (a->Bool) -> [a] -> [a]

filter (<5) [3,9,2,12,6,4] -- > [3,2,4]

La podemos definir así:

filter :: (a -> Bool) -> [a] -> [a]
filter pred []    = []
filter pred (x:xs)
  | pred x         = x : filter pred xs
  | otherwise      = filter pred xs

Muchos cálculos que serían calculados con bucles for / while en un lenguaje imperativo se expresan naturalmente como cálculos de lista en un lenguaje funcional.

En Haskell tenemos: 
  • Map : Realizar un cálculo en cada elemento de una lista
  • Foldl: Iterar sobre una lista, de izquierda a derecha 
  • Foldr: Iterar sobre una lista, de derecha a izquierda

Es una buena práctica usar estas tres funciones cuando corresponda

Podemos expresar un gran cálculo "encadenando" una secuencia de funciones que realizan cálculos más pequeños. Por ejemplo tenemos un argumento de tipo a, aplicamos una función g :: a−> b, obteniendo un resultado intermedio de tipo b luego aplicamos una función f :: b−> c al resultado intermedio, obteniendo el resultado final de tipo c. El cómputo completo (primero g, luego f) se escribe como f∘g. Esta es la notación matemática tradicional; solo recuerda que en f∘g, las funciones se usan en orden de derecha a izquierda.

Haskell utiliza el . como operador de composición de funciones

(.) :: (b->c) -> (a->b) -> a -> c
(f . g) x = f (g x)


lunes, 11 de mayo de 2020

Tutorial de Python, Guía básica de Python en español


Quiero compartir una página que nos permite aprender python de forma fácil y práctica. 

Reza la página: 

"Este tutorial de Python es una introducción a los principales aspectos y características del lenguaje. El tutorial sirve de guía de inicio para dar los primeros pasos con Python, familiarizarse con su sintaxis, conocer los principales tipos de datos y entender algunos conceptos clave.

¿Para quién es este tutorial Python?
En principio, el tutorial es apto para todos los públicos. Si nunca has programado en Python, te servirá para aprender lo básico del lenguaje. Por el contrario, si ya lo conoces y/o has desarrollado alguna aplicación con él, te será útil para reforzar conceptos y descubrir cosas nuevas que quizá desconozcas."

domingo, 10 de mayo de 2020

HumanOS


HumanOS es un blog de software libre. Tiene post sobre temas de variados de diferentes temas y el propósito de este blog es mostrarle a las personas cuales son los provechos que se les puede sacar al software libre

Dejo link:
http://humanos.uci.cu/

Juego de adivinanzas en Haskell

Vamos a ser un juego de adivinanzas. Va a haber una palabra que el jugador debe adivinar. Para cada turno del juego, el jugador adivina una sola letra. Si esa letra es correcta, las letras adivinadas se muestran en los lugares correctos de la palabra. Si esa letra es incorrecta, el usuario pierde una estrella. Una vez que el usuario no tiene estrellas, ha perdido el juego. Sin embargo, si el usuario adivina todas las letras de la palabra, ha ganado el juego, como el ahorcado.

Comience creando un archivo de texto vacío llamado ahorcado.hs: la extensión hs es para indicar que este archivo contiene el código fuente de Haskell.


El corazón del juego consiste en verificar las conjeturas del jugador. Queremos saber si la suposición fue correcta. Este resultado es un valor Bool, ya sea verdadero o falso. Necesitamos actualizar la palabra mostrada, si la suposición fue correcta, reemplazando los guiones apropiados en la palabra mostrada con el carácter correctamente adivinado. Por lo tanto, el tipo de resultado de la función es un par (Bool, String). El primer elemento del par es el resultado de la suposición. El segundo elemento es la Cadena que se mostrará al usuario para la próxima ronda.

Ahora, la función de verificación necesita saber:
  • La palabra secreta, una cadena
  • La palabra que se muestra en pantalla o real, también una cadena
  • El carácter elegido por el jugador.
Estas son las entradas a la función de verificación. Entonces ahora podemos establecer el tipo de la función:

check :: String -> String -> Char -> (Bool, String)

Siempre es útil determinar primero el tipo de función. Esto enfoca su atención en lo que se supone que debe calcular la función, y qué datos necesita para hacerlo. Los buenos ingenieros de software hacen las especificaciones antes de la implementación.

¿Cómo será el cuerpo de la función de verificación? La suposición del jugador es correcta si y solo si el carácter elegido c está en la palabra secreta. Entonces la suposición es correcta si :

c palabra `elem`

La nueva palabra que se mostrará será:

[(if x==c then c else y) | (x,y) <- zip word display]

Esta es una lista de comprensión, donde seleccionamos cada letra de la palabra real o de la pantalla anterior. La palabra es texto sin formato, mientras que la pantalla comienza con todos los caracteres discontinuos.

check :: String -> String -> Char -> (Bool, String)
check word display c
  = (c `elem` word, [if x==c
          then c
          else y | (x,y) <- zip word display])

La siguiente función que definiremos es la función de giro. Esta es la función que se llamará cada vez que sea el turno del jugador para ingresar un caracter. Primero debemos comprobar cuántas conjeturas le quedan al jugador:

if n == 0

Si quedan algunas conjeturas, entonces necesitamos ver si el jugador completo la palabra o no:

if word == display

Entonces tendremos dos verificaciones if, cada una seguida de mensajes de estado putStrLn y el final de la secuencia de llamada a la función (ya que es el final del juego). Sin embargo, si ninguna de las condiciones es verdadera, entonces el jugador puede tomar un turno, por lo que llamamos a otra función para obtener otro personaje de la entrada del usuario.

turn :: String -> String -> Int -> IO ()
turn word display n =
  do if n==0
       then putStrLn "You lose"
       else if word==display
              then putStrLn "You win!"
              else mkguess word display n

Tenga en cuenta que hay una forma más ordenada de escribir la función de giro, utilizando Haskell guards, pero no vamos a utilizarlas para simplificar el programa.

mkguess word display n =
  do putStrLn (display ++ "  " ++ take n (repeat '*'))
     putStr "  Enter your guess: "
     q <- getLine
     let (correct, display') = check word display (q!!0)
     let n' = if correct then n else n-1
     turn word display' n'

¿Cuál es el tipo de mkguess? ¿Puedes resolverlo y agregarlo antes de la definición de la función? Tomamos una línea de entrada del usuario, pero solo usamos el primer carácter para la suposición. Esto fallará si el usuario simplemente presiona ENTER sin escribir ningún carácter, ya que q será una cadena vacía.

Bien, ahora solo necesitamos una función de nivel superior:

starman :: String -> Int -> IO ()
starman word n = turn word ['-' | x <- word] n

Esta función toma dos argumentos, el primero es la palabra a adivinar y el segundo es el número de conjeturas incorrectas que el jugador tiene permitido.

Pongamos todas estas cuatro funciones en un archivo de texto, llamado starman.hs

Guarde el archivo, luego inicie ghci quizás escribiendo ghci en un símbolo del sistema de DOS, ejecutando WinGHCi o escribiendo ghci en una ventana de terminal (macOS o Linux).

Si está en el directorio correcto, es decir, en el que guardó starman.hs, debería poder escribir

:l starman.hs

y el programa debería cargarse. Dirá algo como:

Prelude> :l ahorcado.hs
[1 of 1] Compiling Main             ( ahorcado.hs, interpreted )
Ok, one module loaded.

Starman "funcionalmente" 5

y comienza a jugar el juego! Volverá a la solicitud de GHCi cuando se complete la función starman.

Y Listo!!

miércoles, 6 de mayo de 2020

martes, 5 de mayo de 2020

Swift es como Kotlin


Quiero compartir una pagina que compara Swift con Kotlin.

Lo comparto porque hasta esta pagina, no me parecían similares pero son igualitos :D

Dejo link: http://nilhcem.com/swift-is-like-kotlin/

lunes, 4 de mayo de 2020

Libro gratuito de Scala




Quiero compartir el siguiente libro de Alvin Alexander que es gratuito y esta bueno. El libro tiene aproximadamente 225 páginas y proporciona una breve pero completa introducción al lenguaje Scala. Según el autor.

Sin más dejo el link: https://alvinalexander.com/scala/scala-book-free/

Ejemplo de pattern matching en Scala

La idea es que tenemos que saber si un string (en realidad lo hice con lista de caracteres) tiene sus paréntesis balanceados. Onda si abre uno lo tiene que cerrar y por supuesto, no puede comenzar cerrando.

Veamos como podemos solucionar esto en Scala:

def balance(palabra : List[Char]) : Boolean = {

  def balanceAux(palabra : List[Char], n : Int) : Int = palabra match {
    case List() => n
    case '('::tail => balanceAux(tail, n + 1)
    case ')'::tail => if (n == 0) -1 else balanceAux(tail, n - 1)
    case _::tail => balanceAux(tail, n)
  }

  balanceAux(palabra, 0) == 0
}

Se puede ver que el ultimo case tiene el valor que no es un paréntesis, cualquier otra letra y esto lo decimos con "_"

Es un ejemplo bastante pavo, pero podemos ver la potencia que tiene Pattern matching

domingo, 3 de mayo de 2020

Primer acercamiento a I/O en Haskell



Las funciones puras son el tipo de funciones toman valores como argumentos, procesan algunos de esos valores y luego devuelven un valor de resultado. Una función pura no depende del "estado del mundo". El cálculo es totalmente autónomo e independiente. Dados los mismos argumentos, una función pura siempre devolverá el mismo resultado.

I/O es impuro. Las operaciones de entrada y salida son impuras. Influyen e interactúan con el "mundo exterior". Esencialmente, esta es la única forma de hacer que las computadoras hagan cosas interesantes.

La función getLine lee la entrada del usuario y la devuelve como un tipo especial de valor de cadena: una cadena de I/O. La función putStrLn toma una entrada de cadena y la imprime en el terminal, devolviendo un valor de IO vacío, es decir, IO ().

Los tipos IO es lo que nos permite no mezclar funciones puras e impuras: el sistema de tipos nos mantiene honestos. Sabemos por el tipo de función si está involucrado con I/O.

Veamos esta simple función :

let greet() = do
    planet <- getLine
    home <- getLine
    putStrLn ("greetings " ++ planet ++ "ling.")
    putStrLn ("I am from " ++ home ++ ".")
    putStrLn "Take me to your leader."

Podemos escribirlo como una (¡muy larga!) Línea única:

do { planet <- getLine; home <- getLine; putStrLn ("greetings " ++ planet ++ "ling."); putStrLn ("I am from " ++ home ++ "."); putStrLn "Take me to your leader."}

Si ejecutamos esto, escribiendo "Earth" y "Mars". Entonces deberías ver:

greetings Earthling.
I am from Mars.
Take me to your leader.

Tenga en cuenta que el orden es importante aquí:

queremos que la primera llamada getLine obtenga el nombre del planeta en el que hemos aterrizado
queremos que la segunda llamada getLine obtenga el nombre de dónde somos.

El orden de evaluación de funciones no importa en código puro, por Ej.

let a = reverse "winston"
    b = reverse "churchill"
in "sir " ++ a ++ " " ++ b

No importa si hacemos el primer reverso antes del segundo; el resultado de la expresión sigue siendo el mismo. Sin embargo, este no es el caso de I/O. La secuencia es vital para las acciones de I/O.

La notación do nos permite secuenciar acciones. Esto se parece a una secuencia de comandos en un lenguaje de programación imperativo. Sin embargo, hacer es solo azúcar sintáctico. Debajo, se reescribe como una cadena de llamadas de función donde la salida de la primera función se convierte en la entrada de la segunda función. El operador de enlace realiza esta secuencia de funciones. Es una parte clave de la mónada IO. Estamos comenzando a arañar la superficie de Haskell IO y descubrimos que es muy complejo. Reservaremos la discusión de Mónadas y operadores de enlace para más adelante en el curso.

Por ahora, todo lo que necesitamos entender es:
  • Las operaciones de I/O son impuras
  • Puede usar do para especificar una secuencia de acciones
  • use <- dentro de un do para asociar valores de entrada con nombres
  • cualquier valor o función que implique I/O tiene IO en su tipo
  • una secuencia de acciones de  I/O se describe como estar en la Mónada de IO

sábado, 2 de mayo de 2020

Tail recursion


Cuando me tocaba enseñar recursibidad, hago el típico ejemplo de la Sucesión de Fibonacci la cual podríamos definir de este modo :

f(0) = 1
f(1)= 1
f(n) = f(n - 1) + f(n - 2)

Y listo! esto es muy fácil de programar en scala, sería:

def fibo(n: Int) : Long = if (n==0 || n==1) 1 else fibo(n-1) + fibo(n-2)

esto funciona si no pretendemos calcular el fibonacci en la posición  n = 55

El problema es que este algoritmo tiene una complejidad de dos a la n.

Podemos resolver este problema empezando desde el comienzo. Si lo pensamos de forma imperativo, sería algo así :

  def fibonacci(n: Int): Long = {
     var fa = 1l
     var faa = 1l
     var i = 1
    while (i < n) {
      var aux = fa
      fa = fa + faa
      faa = aux
      i = i + 1
    }
    faa
  }

Es decir si empezamos desde el principio ya tenemos los f(n-1) y f(n-2), pero no solo es solucionable de este modo de forma imperativa, tambien podemos utilizar recursividad de este modo :

  def fibonacci(n: Int): Long = {
     def fibonacciAux(n: Int, fa: Long, faa: Long) : Long =
       if (n == 3) fa + faa
       else fibonacciAux(n-1, fa + faa, fa)

    if (n<=2) 1
    else fibonacciAux(n,1,1)
  }


Tail recursion es un tipo específico de recursión. En particular, se dice que una función recursiva usa tail recursion si la llamada a sí misma ocurre como la última acción de la función.

Cuando una función recursiva se llama a sí misma varias veces, las llamadas van acumulándose en la pila de llamadas, que de por sí, no es muy eficiente porque consume memoria. Pero aun peor, si se acumulan demasiadas llamadas en esta pila, eventualmente se desborda la pila de llamadas y te lanza la excepción java.lang.StackOverflowError.

Esta ineficiencia es un problema serio para un lenguaje funcional como Scala que promueve el uso de funciones recursivas.

Sin embargo, cuando Scala detecta que la función recursiva usa tail recursion, entonces el compilador es capaz de efectuar una optimización al código de forma automática, efectivamente eliminando la recursividad enteramente y reemplazándolo con un simple bucle. Esta optimización es comúnmente conocida como tail call optimization.

Y listo! La función de fibonacci funciona rápido porque utilizamos tail recursion y porque bajamo su complejidad.

Dejo link :
https://es.stackoverflow.com/questions/121965/que-es-tail-recursion

Libros gratuitos de Haskell

Me gustaría recomendar estos libros gratuitos :


Real World Haskell
Real World Haskell es un típico libro de lenguaje de programación O’Reilly. El contenido está disponible en línea de forma gratuita : http://book.realworldhaskell.org/read/

Learn You a Haskell
Learn You a Haskell es una introducción muy informal, pero hace un gran trabajo al explicar conceptos complejos. Puedes leerlo : http://learnyouahaskell.com/chapters


Listas en Haskell


Una estructura de datos clave es la lista

Sintaxis: los elementos se escriben entre corchetes, separados por comas.

['3', 'a']
[2.718, 50.0, -1.0]

Una función solo puede devolver un resultado pero las listas le permiten agrupar varios valores en un objeto, que puede ser devuelto por una función.

Aquí hay una función (minmax) que devuelve el menor y el mayor de dos números:

minmax = \x y -> [min x y, max x y]
minmax 3 8  -- > [3,8]
minmax 8 3  -- > [3,8]

Ojo! se puede hacer pero no esta del todo bien...

Se puede escribir una lista constante, de la siguiente manera:

mylist = [2,4,6,8]

A la vez los elementos pueden ser expresiones y se evalúan solo cuando se usan. Supongamos que se define:

answer = 42
yourlist = [7, answer+1, 7*8]

Entonces

yourlist -- > [7, 43, 56]

Pero mientras no acceda a la expresión, no se evalúa.

El operador (++) toma dos listas existentes y retorna una nueva que contiene todos los elementos de las listas pasadas por parámetro.

[23, 29] ++ [48, 41, 44] -- > [23, 29, 48, 41, 44]

Si xs es una lista, entonces [] ++ xs = xs = xs ++ [].

A veces es útil tener una secuencia de números.

En notación matemática estándar, puedes escribir 0,1, ..., n.

Haskell tiene una notación de secuencia para listas.

La secuencia se escribe entre corchetes, con el valor inicial, el operador ... y el valor final.

[0 .. 5] -> [0,1,2,3,4,5]
[100 .. 103] -> [100,101,102,103]

Las secuencias no están limitadas a números, hay muchos tipos enumerables donde hay una forma natural de incrementar un valor y se puede usar secuencias en cualquier de estos tipos.

Por ejemplo:

[’A’ .. ’z’] -> ['a', 'b', 'c', 'd', 'e', ​​'f', 'g', 'h', 'i', 'j', 'k', 'l', ' m ',' n ',' o ',' p ',' q ',' r ',' s ',' t ',' u ',' v ',' w ',' x ',' y ' , 'z']

['0' ... '9'] -> ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ']

es una lista de caracteres (que resultan ser los caracteres de dígitos);

[0 .. 9] -> [0,1,2,3,4,5,6,7,8,9]
Es una lista de números.

Una comprensión de lista es una notación de alto nivel para especificar el cálculo de una lista

El compilador transforma automáticamente las comprensiones de una lista en una expresión utilizando una familia de funciones básicas que operan en listas

Las comprensiones de listas se inspiraron en la comprensión del conjunto de notación matemática.

Ejemplos de comprensiones de conjuntos:

  • Un conjunto obtenido multiplicando los elementos de otro conjunto por 3 es {3 × x | x ← {1, ..., 10}}.
  • El conjunto de números pares es {2 × x | x ← N}.
  • El conjunto de números impares es {2 × x + 1 | x ← N}.
  • El producto cruzado de dos conjuntos A y B es {(a, b) | a ← A, b ← B}.


Ejemplos de listas por comprensión
[3 * x | x <- [1..10]]
->
[3,6,9,12,15,18,21,24,27,30]

[2 * x | x <- [0..10]]
->
[0,2,4,6,8,10,12,14,16,18,20]

[2 * x + 1 | x <- [0..10]]
->
[1,3,5,7,9,11,13,15,17,19,21]

[[a, b] | a <- [10,11,12], b <- [20,21]]
->
[[10,20], [10,21], [11,20], [11,21], [12,20], [12,21]]

Podemos indexar una lista numerando los elementos, comenzando por 0. Así, una forma canónica de una lista con n elementos es [x0, x1, .. xn − 1]. El operador !! toma una lista y un índice, y devuelve el elemento correspondiente.

[5,3,8,7] !! 2 -> 8
[0 .. 100] !! 81 -> 81
['a' .. 'z'] !! 13 -> 'n'

Si el índice es negativo o demasiado grande, se devuelve indefinido.

Hay funciones de la biblioteca estándar para acceder encabezado de una lista (su primer elemento) o la cola (todo el resto de la lista)

El resultado de aplicar head o tail a la lista vacía no está definido.

head :: [a] -> a
head [4,5,6] -- > 4
tail :: [a] -> [a]
tail [4,5,6] -- > [5,6]


Hemos mencionado antes que Haskell es "lazy", lo que significa que solo evalúa expresiones cuando son necesarias para la evaluación de otra expresión. Este comportamiento se extiende a las listas, por lo que podemos definir listas infinitas usando secuencias, por ejemplo [1 .. ] es la lista de todos los enteros positivos. Otro ejemplo es la función primos (del paquete Data.Numbers.Primes) que devuelve una lista infinita de números primos. Una consecuencia de lazy en las listas es que puede definir listas que contengan expresiones muy complejas y que requieren mucho tiempo, y no se evaluarán, si no son requeridas. Lo mismo es cierto para una expresión incorrecta, por ejemplo, definir xs = [1,2, xs !! 5,4] no generará un error siempre que no acceda al tercer elemento.

Otra cosa muy importante es que las listas también son inmutables. Como resultado, si define xs2 = xs ++ xs e intenta acceder al tercer elemento xs2 !! 2 aún generará un error porque xs no se ha modificado:

xs2 !! 2 -- > *** Exception: Prelude.(!!): index too large

Curiosamente, si cambiamos la definición de xs a xs = [1,2, xs2 !! 5,4], entonces ambas xs !! 2 y xs2 !! 2 devolverá 2:

xs = [1,2,xs2 !! 5,4]
xs2 = xs ++ xs
xs2 !! 2 -- > 2
xs !! 2 -- > 2

Esta es una consecuencia de la evaluación de expresiones de Haskell mediante la reducción: el orden de las expresiones no importa.

PD: la imagen es de http://learnyouahaskell.com/ con licencia Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  porque no pudo encontrar una licencia con un nombre más largo.

viernes, 1 de mayo de 2020

DevOps: tecnología, herramientas y plan de carrera



DevOps se ha convertido en el nuevo paradigma de desarrollo. Su agilidad, una metodología de entrega continua y un sistema prueba-error que permite obtener feedback instantáneo, ha disparado la demanda de este tipo de profesionales en las empresas. ¿Quieres saber más? ¡Descárgate nuestro eBook gratuito "DevOps: "tecnología, herramientas y plan de carrera"!

Sito textual, la pagina que nos esta regalando un libro sobre esta tecnología.

Dejo link: https://landing.muypro.com/ebook-devops/