miércoles, 30 de noviembre de 2022

Semigroupal, Parallel y Applicative parte 3

Cats proporciona una sintaxis de aplicación simplificada. Importamos cats.syntax.apply.


import cats.instances.option._ // for Semigroupal

import cats.syntax.apply._ // for tupled and mapN


El método tuplado se agrega implícitamente a la tupla de Opciones. Utiliza el Semigroupal para Option para comprimir los valores dentro de las Opciones, creando una sola Opción de una tupla:


(Option(123), Option("abc")).tupled

// res8: Option[(Int, String)] = Some((123, "abc"))


Podemos usar el mismo truco en tuplas de hasta 22 valores. Cats define un método tuplado separado para cada aridad:


(Option(123), Option("abc"), Option(true)).tupled

// res9: Option[(Int, String, Boolean)] = Some((123, "abc", true))


Además de tuplado, la sintaxis de aplicación de Cats proporciona un método llamado mapN que acepta un Funtor implícito y una función de la aridad correcta para combinar los valores.


final case class Cat(name: String, born: Int, color: String)

(

Option("Garfield"),

Option(1978),

Option("Orange & black")

).mapN(Cat.apply)

// res10: Option[Cat] = Some(Cat("Garfield", 1978, "Orange & black"))


De todos los métodos mencionados aquí, el más común es usar mapN. Internamente, mapN usa Semigroupal para extraer los valores de Option y Functor para aplicar los valores a la función.

Es bueno ver que esta sintaxis está marcada. Si proporcionamos una función que acepta el número o tipo de parámetros incorrectos, obtenemos un error de compilación:


val add: (Int, Int) => Int = (a, b) => a + b

// add: (Int, Int) => Int = <function2>

(Option(1), Option(2), Option(3)).mapN(add)

// error: ':' expected but '(' found.

// Option("Garfield"),

// ^

(Option("cats"), Option(true)).mapN(add)

/ error: ':' expected but '(' found.

// Option("Garfield"),

// ^


Apply  también tiene métodos contramapN e imapN que aceptan funtores contravariantes e invariantes. Por ejemplo, podemos combinar Monoids usando Invariant. Aquí hay un ejemplo:

import cats.Monoid

import cats.instances.int._// for Monoid

import cats.instances.invariant._// for Semigroupal

import cats.instances.list._// for Monoid

import cats.instances.string._// for Monoid

import cats.syntax.apply._// for imapN


final case class Cat(

   name: String,

   yearOfBirth: Int,

   favoriteFoods: List[String]

)


val tupleToCat: (String, Int, List[String]) => Cat = Cat.apply _

val catToTuple: Cat => (String, Int, List[String]) = cat => (cat.name, cat.yearOfBirth, cat.favoriteFoods)

implicit val catMonoid: Monoid[Cat] = (

    Monoid[String],

    Monoid[Int],

    Monoid[List[String]]

).imapN(tupleToCat)(catToTuple)


Nuestro Monoid nos permite crear Cats "vacíos" y agregar Cats usando la sintaxis :


import cats.syntax.semigroup._ // for |+|

val garfield= Cat("Garfield", 1978, List("Lasagne"))

val heathcliff = Cat("Heathcliff", 1988, List("Junk Food"))

garfield |+| heathcliff

// res14: Cat = Cat("GarfieldHeathcliff", 3966, List("Lasagne", "Junk

Food"))


domingo, 27 de noviembre de 2022

Semigroupal, Parallel y Applicative parte 2

cats.Semigroupal es una clase de tipos que nos permite combinar contextos. Si tenemos dos objetos de tipo F[A] y F[B], un Semigroupal[F] nos permite combinarlos para formar un F[(A, B)]. Su definición en Cats es:


trait Semigroupal[F[_]] {

    def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]

}


Como comentamos en un post anterior, los parámetros fa y fb son independientes entre sí: podemos calcularlos en cualquier orden antes de pasarlos al producto. Esto contrasta con flatMap, que impone un orden estricto en sus parámetros. Esto nos da más libertad cuando definimos instancias de Semigroupal que cuando definimos Monads. 

Mientras que Semigroup nos permite unir valores, Semigroupal nos permite unir contextos. Juntemos algunas Opciones como ejemplo:

import cats.Semigroupal

import cats.instances.option._ // for Semigroupal

Semigroupal[Option].product(Some(123), Some("abc"))

// res1: Option[(Int, String)] = Some((123, "abc"))


Si ambos parámetros son instancias de Some, terminamos con una tupla de los valores dentro. Si alguno de los parámetros se evalúa como None, el resultado completo es None:


Semigroupal[Option].product(None, Some("abc"))

// res2: Option[Tuple2[Nothing, String]] = None

Semigroupal[Option].product(Some(123), None)

// res3: Option[Tuple2[Int, Nothing]] = None


El objeto complementario de Semigroupal define un conjunto de métodos además del producto. Por ejemplo, los métodos tuple2 a tuple22 generalizan el producto a diferentes aridades:


import cats.instances.option._ // for Semigroupal

Semigroupal.tuple3(Option(1), Option(2), Option(3))

// res4: Option[(Int, Int, Int)] = Some((1, 2, 3))

Semigroupal.tuple3(Option(1), Option(2), Option.empty[Int])

// res5: Option[(Int, Int, Int)] = None


Los métodos map2 a map22 aplican una función especificada por el usuario a los valores dentro de 2 a 22 contextos:


Semigroupal.map3(Option(1), Option(2), Option(3))(_ + _ + _)

// res6: Option[Int] = Some(6)

Semigroupal.map2(Option(1), Option.empty[Int])(_ + _)

// res7: Option[Int] = None


También existen métodos contramap2 a contramap22 e imap2 a imap22, que requieren instancias de Contravariant e Invariant respectivamente.

Solo hay una ley para Semigroupal: el método del producto debe ser asociativo.


product(a, product(b, c)) == product(product(a, b), c)



miércoles, 23 de noviembre de 2022

Libros de Java Code Geeks

 

Download FREE IT Guides!

 

JVM Troubleshooting Guide

The Java bytecode produced when application are compiled, is eventually executed by the Java Virtual Machine (JVM). The JVM has grown to be a sophisticated tool, but it essentially...

 
 

Java 8 Features

With no doubts, Java 8 release is the greatest thing in the Java world since Java 5 (released quite a while ago, back in 2004). It brings tons of new features to the Java as a language,...

 
 

JPA Mini Book

One of the problems of Object Orientation is how to map the objects as the database requires. JPA allows us to work with Java classes as it provides a transparent layer to each database...

 
 

Spring Integration for EAI

It is a lightweight framework that builds upon the core Spring framework. It is designed to enable the development of integration solutions typical of event-driven architectures and...

 

Semigroupal, Parallel y Applicative


Los funtores y las mónadas nos permiten secuenciar operaciones usando map y flatMap. Si bien los funtores y las mónadas son abstracciones inmensamente útiles, hay ciertos tipos de flujo de programa que no pueden representar.

Un ejemplo de ello es la validación de formularios. Cuando validamos un formulario, queremos devolver todos los errores al usuario, no detenernos en el primer error que encontremos. Si modelamos esto con una mónada como Either, fallamos rápidamente y perdemos errores. Por ejemplo, el siguiente código falla en la primera llamada a parseInt y no avanza más:


import cats.syntax.either._ // for catchOnly


def parseInt(str: String): Either[String, Int] = Either.catchOnly[NumberFormatException](str.toInt).

leftMap(_ => s"Couldn't read $str")

for {

    a <- parseInt("a")

    b <- parseInt("b")

    c <- parseInt("c")

} yield (a + b + c)

// res0: Either[String, Int] = Left("Couldn't read a")


Otro ejemplo es la evaluación concurrente de Futures. Si tenemos varias tareas independientes de larga duración, tiene sentido ejecutarlas simultáneamente. Sin embargo, la comprensión monádica solo nos permite ejecutarlos en secuencia. map y flatMap no son del todo capaces de capturar lo que queremos porque suponen que cada cálculo depende del anterior:


// context2 is dependent on value1:

context1.flatMap(value1 => context2)


Las llamadas a parseInt y Future.apply anteriores son independientes entre sí, pero map y flatMap no pueden aprovechar esto. Necesitamos una construcción más débil, una que no garantice la secuenciación, para lograr el resultado que queremos. Veremos tres clases de tipos que soportan este patrón:

  • Semigroupal abarca la noción de componer pares de contextos. Cats proporciona un módulo cats.syntax.apply que utiliza Semigroupal y Functor para permitir a los usuarios secuenciar funciones con múltiples argumentos.
  • Parallel convierte tipos con una instancia de Monad en un tipo relacionado con una instancia de Semigroupal.
  • Applicative extiende Semigroupal y Functor. Proporciona una forma de aplicar funciones a parámetros dentro de un contexto.E

Applicatives a menudo se formulan en términos de aplicación de funciones, en lugar de la formulación semigrupal que se enfatiza en Cats. Esta formulación alternativa proporciona un enlace a otras bibliotecas y lenguajes como Scalaz y Haskell. Veremos las diferentes formulaciones de Applicative, así como las relaciones entre Semigroupal, Functor, Applicative y Monad.

sábado, 19 de noviembre de 2022

C# 11 y .NET 7 traen análisis genérico


Uno de los patrones de diseño básicos en .NET es la función Parse estática. Prácticamente todas las clases que se pueden instanciar a partir de una cadena tienen una. Sin embargo, antes de C# 11 y .NET 7, no había forma de crear una interfaz que se adaptara a esto.

El problema fundamental es que antes de ahora, las interfaces abstractas (es decir, la palabra clave de la interfaz) no admitían funciones estáticas. Esto no era solo una limitación de C#, el CLR en sí no lo admitía antes de la introducción de la característica de métodos abstractos estáticos en las interfaces. Al igual que con los métodos de interfaz, los métodos abstractos estáticos pueden tener una implementación predeterminada, pero existen limitaciones.

Veamos interfaz IParsable<TSelf> ofrece dos métodos, Parse y TryParse. Ambos aceptan un String y un IFormatProvider. Para acceder a ellos, necesita un método de ayuda genérico como:


static T Parse<T>(string s, IFormatProvider? provider)

    where T : IParsable<T>

{

    return T.Parse(s, provider);

}


Se puede invocarlo usando un parámetro de tipo.


var a = Parse<int>("4", null);



martes, 15 de noviembre de 2022

Roslyn, compiladores de C# y Visual Basic con unas APIs para crear herramientas de análisis de código.

Roslyn es la implementación de código abierto de los compiladores de C# y Visual Basic con unas APIs para crear herramientas de análisis de código.

La idea es que el compilador exponga información del proceso de compilación para crear herramientas que permitan mejorar ese código. 

Los compiladores procesan el código siguiendo reglas estructuradas que a menudo difieren de la forma en que los humanos leen y entienden el código. Una comprensión básica del modelo utilizado por los compiladores es esencial para comprender las API que utiliza al crear herramientas basadas en Roslyn.

El SDK de .NET Compiler Platform expone el análisis de código de los compiladores de C# y Visual Basic, al proporcionar una capa de API que refleja una canalización de compilador tradicional.

Cada fase de este proceso es un componente separado. Primero, la fase de análisis tokeniza y analiza el texto fuente en sintaxis que sigue la gramática del lenguaje. En segundo lugar, la fase de declaración analiza la fuente y los metadatos importados para formar símbolos con nombre. A continuación, la fase de enlace hace coincidir los identificadores en el código con los símbolos. Finalmente, la fase de emisión emite un ensamblado con toda la información construida por el compilador.

En correspondencia con cada una de esas fases, el SDK de .NET Compiler Platform expone un modelo de objeto que permite el acceso a la información en esa fase. La fase de análisis expone un árbol de sintaxis, la fase de declaración expone una tabla de símbolos jerárquicos, la fase de vinculación expone el resultado del análisis semántico del compilador y la fase de emisión es una API que produce códigos de bytes IL.


Cada compilador combina estos componentes como un único todo de extremo a extremo.

Estas API son las mismas que usa Visual Studio. Por ejemplo, las funciones de esquematización y formato del código usan los árboles de sintaxis, el Examinador de objetos y las funciones de navegación usan la tabla de símbolos, las refactorizaciones e Ir a definición usan el modelo semántico, y Edit and Continue usa todo esto, incluida la API Emit.

La capa del compilador contiene los modelos de objetos que corresponden a la información expuesta en cada fase de la canalización del compilador, tanto sintáctica como semántica. La capa del compilador también contiene una instantánea inmutable de una única invocación de un compilador, incluidas las referencias de ensamblado, las opciones del compilador y los archivos de código fuente. Hay dos API distintas que representan el lenguaje C# y el lenguaje Visual Basic. Las dos API tienen una forma similar pero están diseñadas para ofrecer alta fidelidad a cada lenguaje individual. Esta capa no tiene dependencias en los componentes de Visual Studio.

Como parte de su análisis, el compilador puede producir un conjunto de diagnósticos que cubren todo, desde errores de sintaxis, semánticos y de asignación definitiva hasta varias advertencias y diagnósticos informativos. La capa de API del compilador expone diagnósticos a través de una API extensible que permite que los analizadores se conecten al proceso de compilación. Permite que los diagnósticos definidos por el usuario, como los producidos por herramientas como StyleCop, se produzcan junto con los diagnósticos definidos por el compilador. Producir diagnósticos de esta manera tiene la ventaja de integrarse naturalmente con herramientas como MSBuild y Visual Studio, que dependen de los diagnósticos para experiencias como detener una compilación basada en una política y mostrar IL en vivo en el editor y sugerir correcciones de código.

Las API de alojamiento y secuencias de comandos forman parte de la capa del compilador. Puede usarlos para ejecutar fragmentos de código y acumular un contexto de ejecución en tiempo de ejecución. El REPL interactivo de C# (Read-Evaluate-Print Loop) utiliza estas API. El REPL le permite utilizar C# como lenguaje de secuencias de comandos, ejecutando el código de forma interactiva a medida que lo escribe.

La capa Workspaces contiene la API Workspace, que es el punto de partida para realizar análisis de código y refactorizar soluciones completas. Lo ayuda a organizar toda la información sobre los proyectos en una solución en un solo modelo de objeto, ofreciéndole acceso directo a los modelos de objetos de la capa del compilador sin necesidad de analizar archivos, configurar opciones o administrar dependencias de proyecto a proyecto.

Además, la capa Workspaces muestra un conjunto de API que se utilizan al implementar herramientas de análisis y refactorización de código que funcionan dentro de un entorno de host como el IDE de Visual Studio. Los ejemplos incluyen las API Buscar todas las referencias, Formato y Generación de código. Esta capa no tiene dependencias en los componentes de Visual Studio.

viernes, 11 de noviembre de 2022

Microsoft lanza .NET 7



Y porque es tan importante esto?  .NET 7 es una plataforma de desarrollo multiplataforma unificada. 

La idea era unificar .NET Core (reescrito desde cero, open source y multiplataforma) con la tecnología de .NET Framework (más versátil, pero limitada a sistemas Windows), así como con productos como Xamarin y Mono, para crear un producto que permitiera programar sobre una única base de código con runtimes y experiencias de desarrollo uniformes.

Sin embargo, en agosto de 2020, Microsoft anunció que estos pasos se pospondrían hasta el lanzamiento de .NET 6, previsto para noviembre de 2021. Tras eso, la compañía anunció "cambios en el cronograma" que obligaron a postergar de nuevo la integración de componentes como MAUI (Multi-platform App UI). Y así hasta hoy.

Una de las grandes novedades de .NET 7 es la inclusión de la versión 11 de la sintaxis de C#, el lenguaje de programación estrella de Microsoft (F#, también ha sido actualizado).

Además, el anuncio de Microsoft incluye referencias a mejoras de rendimiento, especialmente en ARM64 (con un rendimiento hasta un 45% superior) y acuerdos de colaboración para desarrollar con .NET en Ubuntu Linux, y en los servidores Power System de IBM.

Dejo link : https://news.microsoft.com/es-xl/net-7-esta-disponible-hoy/

miércoles, 9 de noviembre de 2022

Free eBook: Declarative Cloud Infrastructure Management with Terraform (New Edition)

 

Download Now

 

Free eBook: Declarative Cloud Infrastructure Management with Terraform (New Edition)


Understand the underlying concepts of Terraform, declarative cloud infrastructure management, and key basics of the Hashicorp Configuration Language.

Also covered: why and when you should use Linode Kubernetes Engine (LKE) and Terraform together, Packer explained, and links to more educational content.

Get the PDF instantly (2.6 MB) — no registration required
Download Now
Your Suggestions
Any ideas or suggestions? Shoot us an email at newsletter@javacodegeeks.com

Scalafmt


Cuando trabajas con mucha gente tenes que definir que formato de código van utilizar. Es decir, no puede cada uno dar formato de código como le venga en ganas porque cuando tengan que interactuar por medio de git van a tener que subir más código y más que mergear. 

Por ende existen formateadores de código que te permiten formatear el código de forma estandar y listo!! Unos de esos formateadores es Scalafmt que es para scala. Por lo tanto podemos dedicar más tiempo a discutir cuestiones importantes en la revisión del código y menos tiempo al estilo del código. Scalafmt formatea el código para que se vea consistente entre las personas.

Se puede ejecutar scalafmt desde el editor, herramienta de compilación o terminal. Scalafmt tiene integraciones con IntelliJ, sbt, Maven, Gradle y Mill.

Dejo link: 

https://scalameta.org/scalafmt/

sábado, 5 de noviembre de 2022

Un ejemplo de uso de Go routines


Un ejemplo sencillo de Go routines, tenemos 2 Slices con diferentes números y queremos que se impriman en go routines diferentes y bueno, tengo que poner un time.Sleep(time.Second) para esperar que termine. 

Debería haber usado un sync.WaitGroup pero no lo hice, igual hay un ejemplo acá.


package main

import (

"fmt"

"time"

"math/rand"

)


func main(){

s1, s2 := []int{1,2,3}, []int{4,5,6}

fmt.Println("Let's Race !!!!!!!!!!!!\n")

go printElemOfIntSlice(s1, 100)

go printElemOfIntSlice(s2, 100)

time.Sleep(time.Second)

}


func printElemOfIntSlice(slice []int, velo int){

for _, val  := range slice{

fmt.Println(val)

duration := rand.Intn(velo)

time.Sleep(time.Duration(duration) * time.Millisecond)

}

}

viernes, 4 de noviembre de 2022

Ejemplo de uso de sync.WaitGroup en Go

 


En Go cuando queremos ejecutar una corutina luego que se ejecuten un conjunto de corutinas, podemos utilizar waitgroup. 

En el ejemplo se ordena un slice dividido en 4 partes en 4 corutinas diferentes y se espera a que terminen. Espero que les sirva. 

package main


import (

 "fmt"

 "strconv"

 "sort"

 "math/rand"

 "math"

 "sync"

)


func main() {

fmt.Println("enter quantity of integers")

var nroStr string

fmt.Scan(&nroStr)

nro, err := strconv.Atoi(nroStr)

if (err != nil) {

return;

}

wg := sync.WaitGroup{}

nros := make([]int, 0, nro)

for i := 0; i < nro; i++ {

nros = append(nros,rand.Intn(10*nro))

}

if nro > 4 {

quart := int(math.RoundToEven(float64(nro) / 4.0))

wg.Add(4)

for i:=0; i < 3; i++ {

start := (i * quart)

end := (((i + 1) * quart))

go sortNros(nros[start:end], &wg)

}

go sortNros(nros[(3 * quart) : nro], &wg)

} else {

wg.Add(1)

go sortNros(nros, &wg)

}

wg.Wait()

fmt.Println(nros)

}


func sortNros(nros []int, wg *sync.WaitGroup) {

sort.Ints(nros)

wg.Done()

}

    


jueves, 3 de noviembre de 2022

Interfaces vs. generics en Go




Habiamos visto en post anterior la función de los generics. Por lo tanto sigamos profundizando su relación con las interfaces. 

A diferencia de los genéricos en C++, los genéricos de Go solo pueden realizar operaciones específicas enumeradas en una interfaz, esta interfaz se conoce como restricción.

El compilador utiliza una restricción para asegurarse de que el tipo proporcionado para la función admita todas las operaciones realizadas por los valores instanciados mediante el parámetro de tipo.

Por ejemplo, en el siguiente fragmento, cualquier valor del parámetro de tipo T solo es compatible con el método String; puede usar len() o cualquier otra operación sobre él.


// Stringer is a constraint

type Stringer interface {

 String() string

}


// Here T has to implement Stringer, T can only perform operations defined by Stringer

func stringer[T Stringer](s T) string {

 return s.String()

}


Go permite tipos predefinidos como int y string para implementar interfaces que se usan en restricciones. Estas interfaces con tipos predefinidos solo se pueden usar como una restricción.


type Number {

  int

}

No se puede usar una restricción con un tipo y método predefinidos, ya que los tipos predefinidos no tienen métodos en estos tipos definidos; por lo tanto, es imposible implementar estas restricciones.


type Number {

  int

  Name() string // int no tiene el metodo Name

}


El operador | permite una unión de tipos (es decir, múltiples tipos concretos pueden implementar la interfaz única y la interfaz resultante permite operaciones comunes en todos los tipos de unión).


type Number interface {

        int | int8 | int16 | int32 | int64 | float32 | float64

}


En el ejemplo anterior, la interfaz Number ahora admite todas las operaciones que son comunes en el tipo proporcionado, como <,> y +; todas las operaciones algorítmicas son compatibles con la interfaz Number.


func Min[T Number](x, y T) T {

        if x < y {

                return x

        }

        return y

}


El uso de una unión de varios tipos permite realizar operaciones comunes admitidas por estos tipos y escribir código que funcione para todos los tipos en unión.


Go permite crear tipos definidos por el usuario a partir de tipos predefinidos como int, string, etc. Los operadores ~ nos permiten especificar que la interfaz también admite tipos con los mismos tipos subyacentes.


Por ejemplo, si desea agregar soporte para el tipo Punto con el tipo subrayado int a la función Min; esto es posible usando ~.


// Any Type with given underlying type will be supported by this interface

type Number interface {

        ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64

}


// Type with underlying int

type Point int


func Min[T Number](x, y T) T {

        if x < y {

                return x

        }

        return y

}


func main() {

        // creating Point type

        x, y := Point(5), Point(2)


        fmt.Println(Min(x, y))


}


Todos los tipos predefinidos admiten este tipo aproximado: el operador ~ solo funciona con restricciones.


// Union operator and type approximation both use together without interface

func Min[T ~int | ~float32 | ~float64](x, y T) T {

        if x < y {

                return x

        }

        return y

}


Las restricciones también admiten la anidación; la restricción de número se puede construir a partir de la restricción de entero y la restricción de flotación.


// Integer is made up of all the int types

type Integer interface {

        ~int | ~int8 | ~int16 | ~int32 | ~int64

}


// Float is made up of all the float type

type Float interface {

        ~float32 | ~float64

}


// Number is build from Integer and Float

type Number interface {

        Integer | Float

}


// Using Number

func Min[T Number](x, y T) T {

        if x < y {

                return x

        }

        return y

}

El equipo de Go ha proporcionado un nuevo paquete con una colección de restricciones útiles; este paquete contiene restricciones para Integer, Float, etc.

Este paquete exporta restricciones para tipos predefinidos. Dado que se pueden agregar nuevos tipos predefinidos al lenguaje, es mejor usar las restricciones definidas en el paquete de restricciones. La más importante de ellas es la restricción Ordered https://pkg.go.dev/golang.org/x/exp/constraints#Ordered). Define todos los tipos que admiten los operadores >,<,== y !=.

func min[T constraints.Ordered](x, y T) T {
    if x > y {
        return x
    } else {
        return y
    }
}

Los genéricos no reemplazan a las interfaces. Los genéricos están diseñados para funcionar con interfaces y hacer que Go sea más seguro, y también se pueden usar para eliminar la repetición de código.

La interfaz representa un conjunto del tipo que implementa la interfaz, mientras que los genéricos son un marcador de posición para los tipos reales. Durante la compilación, el código genérico puede convertirse en una implementación basada en interfaz.



martes, 1 de noviembre de 2022

Interface y polimorfismo en Go

 


En Go no existen las clases pero si existe la programación orientada a objetos, veamos un ejemplo


package main


import (

 "fmt"

 "bufio"

 "os"

 "strings"

)


type Animal interface {

Eat()

Move()

Speak()

GetName() string

}


type Cow struct { name string }


func (c Cow) Eat() {

fmt.Println("grass")

}


func (c Cow) Move() {

fmt.Println("walk")

}


func (c Cow) Speak() {

fmt.Println("moo")

}


func (c Cow) GetName() string {

return c.name

}



type Bird struct { name string }


func (c Bird) Eat() {

fmt.Println("worms")

}


func (c Bird) Move() {

fmt.Println("fly")

}


func (c Bird) Speak() {

fmt.Println("peep")

}


func (c Bird) GetName() string {

return c.name

}


type Snake struct { name string }


func (c Snake) Eat() {

fmt.Println("mice")

}


func (c Snake) Move() {

fmt.Println("slither")

}


func (c Snake) Speak() {

fmt.Println("hsss")

}


func (c Snake) GetName() string {

return c.name

}


func main() {


animals := make([]Animal, 0, 0)

var animal *Animal

for {

    fmt.Print("< ")

inputStr := get_input()

input := strings.Split(inputStr, " ")

if (len(input) == 1 && input[0] == "quit") {

break

if len(input) == 3 && input[0] == "newanimal" {

aniStr := input[2]

switch aniStr {

case "cow":

animals = append(animals,Cow{ input[1] })

fmt.Println(" Created it! ")

case "bird":

       animals = append(animals,Bird{ input[1] })

fmt.Println(" Created it! ")

case "snake":

animals = append(animals,Snake{ input[1] })

fmt.Println(" Created it! ")

default:

fmt.Println("incorrect input animal")

}

}

if len(input) == 3 && input[0] == "query" {

    animal = find(animals, input[1])

if animal == nil {

fmt.Println("incorrect animal name")

} else {

actionStr := input[2]

switch actionStr {

case "eat":

(*animal).Eat()

case "move":

(*animal).Move()

case "speak":

(*animal).Speak()

default:

fmt.Println("incorrect input action")

break

}

}

}

}


func find(animals []Animal, name string) *Animal {

for i := 0; i < len(animals); i++ {

if animals[i].GetName() == name {

return &animals[i]

}

}

return nil

}


func get_input() string {

    

  var input_string string

    var input []byte

    var err error

    

    for {

        

        reader := bufio.NewReader(os.Stdin)


        input, _ , err = reader.ReadLine()

        

        if err != nil{

            fmt.Println("Something going wrong! Try again")

        } else { 

    input_string = string(input)

            break

        } 

    }

    return input_string

}

    


Generics en Go


Go 1.18 soporta generics. Espero que sepan que es generics, pero para hacerla corta, es la forma en la que en lenguajes de tipado estático podemos hacer funciones o clases (si el lenguaje lo soporta) que hacen cosas con un tipo genérico. Por ejemplo una lista, se comporta como lista sin importar si es una lista de personas, números, letras o choripanes. Siempre se comporta como una lista y punto. 

Entonces, Go 1.18.0 presenta una nueva sintaxis para proporcionar metadatos adicionales sobre tipos y definir restricciones en estos tipos. Veamos un ejemplo : 


package main

import "fmt"


func main() {

        fmt.Println(reverse([]int{1, 2, 3, 4, 5}))

}


// T es un parámetro de tipo que se usa como tipo normal dentro de la función

// any es una restricción en el tipo, es decir, T tiene que implementar la interfaz "any"

func reverse[T any](s []T) []T {

        l := len(s)

        r := make([]T, l)

        for i, ele := range s {

                r[l-i-1] = ele

        }

        return r

}


Los corchetes [] se usan para especificar parámetros de tipo, que son una lista de identificadores y una interfaz de restricción.  T es un parámetro de tipo que se usa para definir el tipo del argumento y el tipo de retorno de la función.

El parámetro también es accesible dentro de la función. any es una interfaz; T tiene que implementar esta interfaz. Go 1.18 presenta any como un alias para la interfaz{}.

El parámetro de tipo es como una variable de tipo: todas las operaciones admitidas por tipos normales son compatibles con variables de tipo (por ejemplo, hacer función). La variable inicializada usando estos parámetros de tipo soportará la operación de la restricción; en el ejemplo anterior, la restricción es la interfaz any.

La función tiene un tipo de retorno []T y un tipo de entrada de []T. Aquí, el parámetro de tipo T se usa para definir más tipos que se usan dentro de la función. Estas funciones genéricas se instancian pasando el valor de tipo al parámetro de tipo.

reverseInt:= reverse[int]

El compilador de Go infiere el parámetro de tipo comprobando los argumentos pasados a las funciones. En el ejemplo, infiere automáticamente que el parámetro de tipo es int y, a menudo, puede omitirlo.


// without passing type

fmt.Println(reverse([]int{1, 2, 3, 4, 5}))

// passing type

fmt.Println(reverse[int]([]int{1, 2, 3, 4, 5}))


El uso de parámetros de tipo en funciones permite a los programadores escribir códigos genéricos sobre tipos.

El compilador creará una definición separada para cada combinación de tipos pasados en la creación de instancias o creará una definición basada en interfaz derivada de patrones de uso y algunas otras condiciones.

Al crear un Slice, solo se requiere un tipo, por lo que solo se necesita un parámetro de tipo. Veamos un ejemplo: 


func ForEach[T any](s []T, f func(ele T, i int , s []T)){

    for i,ele := range s {

        f(ele,i,s)

    }

}

El mapa requiere dos tipos, un tipo para la clave y un tipo el valor. El tipo de valor no tiene restricciones, pero el tipo de clave siempre debe satisfacer la restricción comparable.

func keys[K comparable, V any](m map[K]V) []K {

    key := make([]K, len(m))

    i := 0

    for k, _ := range m {

        key[i] = k

        i++

    }

    return key

}

Del mismo modo, los canales también son compatibles con los genéricos.

Go permite definir estructuras con un parámetro de tipo. La sintaxis es similar a la función genérica. El parámetro de tipo se puede usar en el método y los miembros de datos en la estructura.


// T is type parameter here, with any constraint

type MyStruct[T any] struct {

    inner T

}


// No new type parameter is allowed in struct methods

func (m *MyStruct[T]) Get() T {

    return m.inner

}

func (m *MyStruct[T]) Set(v T) {

    m.inner = v

}


No se permite definir nuevos parámetros de tipo en los métodos de estructura, pero los parámetros de tipo definidos en las definiciones de estructura se pueden usar en los métodos.

Los tipos genéricos se pueden anidar dentro de otros tipos. El parámetro de tipo definido en una función o estructura se puede pasar a cualquier otro tipo con parámetros de tipo.


type Enteries[K, V any] struct {

    Key   K

    Value V

}

func enteries[K comparable, V any](m map[K]V) []*Enteries[K, V] {

    // define a slice with Enteries type passing K, V type parameters

    e := make([]*Enteries[K, V], len(m))

    i := 0

    for k, v := range m {

        // creating value using new keyword

        newEntery := new(Enteries[K, V])

        newEntery.Key = k

        newEntery.Value = v

        e[i] = newEntery

        i++

    }

    return e

}


func enteries[K comparable, V any](m map[K]V) []*Enteries[K, V]


A diferencia de los genéricos en C++, los genéricos de Go solo pueden realizar operaciones específicas enumeradas en una interfaz, esta interfaz se conoce como restricción.

El compilador utiliza una restricción para asegurarse de que el tipo proporcionado para la función admita todas las operaciones realizadas por los valores instanciados mediante el parámetro de tipo.

Por ejemplo, en el siguiente fragmento, cualquier valor del parámetro de tipo T solo es compatible con el método String.

// Stringer is a constraint

type Stringer interface {

 String() string

}


// Here T has to implement Stringer, T can only perform operations defined by Stringer

func stringer[T Stringer](s T) string {

 return s.String()

}

Puff que me quedo relargo el post, y todavia hay algunos detalles que se me escapan que seguire explorando en otros posts. 


Dejo link: 

https://go.dev/doc/tutorial/generics

https://gobyexample.com/generics