Antes de empezar lee la parte 1. No te adelantes.
- console: Aplicación de consola.
- classlib: Biblioteca de clases.
- web: Aplicación web ASP.NET Core.
- mvc: Aplicación ASP.NET Core MVC.
- blazorserver: Aplicación Blazor Server.
import gleam/int
import gleam/io
import gleam/result
pub fn main() {
io.println("=== map ===")
let _ = io.debug(result.map(Ok(1), fn(x) { x * 2 }))
let _ = io.debug(result.map(Error(1), fn(x) { x * 2 }))
io.println("=== try ===")
let _ = io.debug(result.try(Ok("1"), int.parse))
let _ = io.debug(result.try(Ok("no"), int.parse))
let _ = io.debug(result.try(Error(Nil), int.parse))
io.println("=== unwrap ===")
io.debug(result.unwrap(Ok("1234"), "default"))
io.debug(result.unwrap(Error(Nil), "default"))
io.println("=== pipeline ===")
int.parse("-1234")
|> result.map(int.absolute_value)
|> result.try(int.remainder(_, 42))
|> io.debug
}
El módulo de la biblioteca estándar gleam/result contiene funciones para trabajar con resultados. Los programas Gleam harán un uso intensivo de este módulo para evitar expresiones de caso anidadas excesivas al llamar a múltiples funciones que pueden fallar.
Las funciones de resultado se utilizan a menudo con canalizaciones para encadenar varias llamadas a funciones que devuelven resultados.
Primary Constructors allow a constructor to be defined directly in the class declaration, which reduces the need for repetitive code and simplifies the definition of immutable classes.
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
In this example, the `Person` class has a primary constructor that takes two parameters: name and age. And the Name and Age properties are initialized directly from the constructor parameters, making it cleaner and more concise.
Scala has offered a similar concept since its earliest versions. In Scala, the main constructor parameters are defined along with the class and can be used to initialize the class members directly.
class Person(val name: String, val age: Int)
Both C# 12 and Scala eliminate the need to define a separate constructor and assign the parameters to the class properties manually.
But in C#, properties are assigned inside the class body using an explicit assignment (= name;), while in Scala, this assignment is implicit. And in Scala, you can control the visibility of constructor parameters (val or var) more directly. In C#, the default pattern is to create immutable properties with get; only.
Scala, being a more functional programming oriented language, offers features such as eliminating the need for {} braces for simple class bodies, while C# remains more verbose in its syntax.
In conclusion, the addition of Primary Constructors in C# 12 is a step in the right direction, making the language more expressive and less verbose, approaching the simplicity that Scala has offered for years. This parallel not only demonstrates the influence of functional languages on more traditional languages like C#, but also highlights the trend toward more concise, declarative programming.
And with each passing day I see C# becoming more like Scala ...
Una lambda es una función anónima que se puede definir en línea en el lugar donde se utiliza. Las lambdas permiten crear pequeñas funciones sin necesidad de nombrarlas o declararlas previamente. Fueron introducidas en C++11 y han sido una herramienta clave para el desarrollo moderno en C++.
La sintaxis básica de una lambda en C++ es :
[captura](parametros) -> tipo_retorno {
// cuerpo de la lambda
};
Veamos un ejemplo simple de una lambda que suma dos números:
#include <iostream>
int main() {
auto suma = [](int a, int b) -> int {
return a + b;
};
std::cout << "La suma de 3 y 4 es: " << suma(3, 4) << std::endl;
return 0;
}
En este ejemplo, la lambda captura dos parámetros `a` y `b`, y retorna su suma.
Una de las características más poderosas de las lambdas es su capacidad para capturar variables del entorno donde son definidas. Hay varias formas de hacerlo:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto suma = [x, &y]() {
y = x + y;
};
suma();
std::cout << "El nuevo valor de y es: " << y << std::endl; // Imprime 30
return 0;
}
Aquí, `x` se captura por valor, y `y` se captura por referencia, lo que significa que cualquier modificación de `y` dentro de la lambda afecta a `y` fuera de la lambda.
Las lambdas son particularmente útiles cuando se combinan con las funciones de la STL (Standard Template Library) como `std::sort`, `std::for_each`, etc.
Por ejemplo, ordenar un vector de enteros en orden descendente usando `std::sort` y una lambda:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {3, 1, 4, 1, 5, 9};
std::sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b; // Orden descendente
});
for (int n : vec) {
std::cout << n << " "; // Imprime: 9 5 4 3 1 1
}
return 0;
}
Por defecto, las variables capturadas por valor dentro de una lambda no pueden ser modificadas. Sin embargo, si necesitas modificar las variables capturadas por valor, puedes declarar la lambda como `mutable`:
#include <iostream>
int main() {
int x = 10;
auto incrementa = [x]() mutable {
x++;
std::cout << "Valor dentro de la lambda: " << x << std::endl;
};
incrementa(); // Imprime 11
std::cout << "Valor fuera de la lambda: " << x << std::endl; // Imprime 10
return 0;
}
En este caso, `x` se incrementa dentro de la lambda, pero fuera de ella permanece inalterado.
Las lambdas en C++ son una herramienta poderosa para escribir código más claro y conciso. Facilitan la escritura de funciones pequeñas y de un solo uso y son especialmente útiles cuando se trabaja con funciones de la STL y otras APIs que aceptan funciones como parámetros.
import gleam/list
pub fn main() {
let ints = [0, 1, 2, 3, 4, 5]
io.println("=== map ===")
io.debug(list.map(ints, fn(x) { x * 2 }))
io.println("=== filter ===")
io.debug(list.filter(ints, fn(x) { x % 2 == 0 }))
io.println("=== fold ===")
io.debug(list.fold(ints, 0, fn(count, e) { count + e }))
io.println("=== find ===")
let _ = io.debug(list.find(ints, fn(x) { x > 3 }))
io.debug(list.find(ints, fn(x) { x > 13 }))
}
=== map === [0, 2, 4, 6, 8, 10] === filter === [0, 2, 4] === fold === 15 === find === Ok(4) Error(Nil)
El módulo de la biblioteca estándar gleam/list contiene funciones para trabajar con listas. Es probable que un programa Gleam haga un uso intensivo de este módulo, ya que las distintas funciones sirven como diferentes tipos de bucles sobre listas.
Vale la pena familiarizarse con todas las funciones de este módulo al escribir código Gleam, ¡las usará mucho!
¡Tengo grandes noticias! Estoy emocionado de anunciar que ya están abiertas las inscripciones para los tan esperados cursos Gugler. Si estás buscando avanzar en tu carrera, aprender nuevas habilidades, o simplemente profundizar tus conocimientos en áreas tecnológicas, ¡estos cursos son para ti!
Inscripciones abiertas del segundo cuatrimestre 2024. Inscripciones.gugler.com.ar
De esta forma se presenta la pagina "El libro de python", un libro o web (como quieras llamarlo) super recomendado para aprender python.
Dejo link:
Lua es un lenguaje de scripting ligero y potente, y Redis permite la ejecución de scripts Lua en su entorno. Esto brinda la posibilidad de realizar operaciones más complejas que las que se pueden lograr con los comandos básicos de Redis.
Por qué usar Lua en Redis:
Supongamos que queremos implementar un procedimiento que incremente el valor de una clave solo si la clave existe y su valor es mayor que un umbral dado. Este es un típico ejemplo donde un procedimiento almacenado sería útil en un sistema de bases de datos relacional.
-- Script Lua para incrementar un valor si es mayor que un umbral
local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end
Para ejecutar este script en Redis, puedes usar el comando `EVAL`:
EVAL "local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end" 1 mykey 10 5
Este comando recibe los siguientes parametros :
Aunque Redis no tiene una noción de funciones al estilo SQL, puedes pensar en los scripts Lua como funciones reutilizables. Si bien Redis no permite definir funciones Lua en el mismo sentido que los procedimientos almacenados en SQL, puedes almacenar el script en Redis y llamarlo repetidamente.
Para almacenar un script:
SCRIPT LOAD "local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end
Esto te devolverá un `sha1` hash del script, que puedes usar para invocarlo nuevamente:
EVALSHA <sha1> 1 mykey 10 5
Mientras que Redis no soporta procedimientos almacenados y funciones en el sentido tradicional de bases de datos relacionales, su capacidad para ejecutar scripts Lua te permite realizar operaciones avanzadas y reutilizables de manera similar. Esta funcionalidad es extremadamente útil cuando necesitas lógica compleja o atomicidad en tus operaciones con Redis.
Empecemos por el principio. El comando `dotnet` es la herramienta de línea de comandos que viene con el SDK de .NET y que permite a los desarrolladores realizar una amplia gama de tareas relacionadas con la creación, compilación, depuración y despliegue de aplicaciones .NET. Es la interfaz principal para interactuar con el runtime y las bibliotecas de .NET, así como para gestionar paquetes NuGet, herramientas y otros componentes.
dotnet --version
Este comando muestra la versión del SDK de .NET instalado en tu máquina, lo que es útil para verificar rápidamente qué versión estás utilizando.
El comando `dotnet` se utiliza para una variedad de tareas esenciales en el desarrollo de aplicaciones .NET:
Para crear y ejecutar una simple aplicación de consola, usarías:
dotnet new console -n MyApp
cd MyApp
dotnet run
Este conjunto de comandos crea una nueva aplicación de consola, navega al directorio del proyecto, y ejecuta la aplicación.
Como es de esperar, para usar el comando `dotnet`, necesitas tener instalado el SDK de .NET en tu máquina. El SDK incluye todo lo necesario para desarrollar aplicaciones con .NET, incluyendo el runtime y la herramienta `dotnet`.
Los pasos para instalar el sdk son sencillos:
Bajar el instalador de https://dotnet.microsoft.com/download. Tenes que elegir la plataforma que usas (Windows, macOS, Linux).
Una vez que tengas el instalador doble click y le das next todas las veces que necesite (sin miedo al exito)
Luego abris una terminal o línea de comandos y ejecuta:
dotnet --version
Si el comando devuelve un número de versión, la instalación fue exitosa.
Este SDK es necesario no solo para compilar y ejecutar aplicaciones, sino también para utilizar todas las funcionalidades avanzadas que el comando `dotnet` ofrece.
Los constructores primarios permiten definir un constructor directamente en la declaración de la clase, lo que reduce la necesidad de código repetitivo y simplifica la definición de clases inmutables.
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
En este ejemplo, la clase `Person` tiene un constructor primario que toma dos parámetros: `name` y `age`. Y las propiedades `Name` y `Age` se inicializan directamente desde los parámetros del constructor, haciéndolo más limpio y conciso.
Scala ha ofrecido un concepto similar desde sus primeras versiones. En Scala, los parámetros del constructor principal se definen junto con la clase y se pueden utilizar para inicializar los miembros de la clase de forma directa.
class Person(val name: String, val age: Int)
Tanto C# 12 como Scala eliminan la necesidad de definir un constructor separado y asignar los parámetros a las propiedades de la clase manualmente.
Pero en C#, las propiedades se asignan dentro del cuerpo de la clase usando una asignación explícita (`= name;`), mientras que en Scala, esta asignación es implícita. Y en Scala, se puede controlar la visibilidad de los parámetros del constructor (`val` o `var`) más directamente. En C#, el patrón predeterminado es crear propiedades inmutables con `get;` solamente.
Scala, siendo un lenguaje más orientado a la programación funcional, ofrece características como la eliminación de la necesidad de llaves `{}` para cuerpos de clase simples, mientras que C# sigue siendo más detallado en su sintaxis.
En conclusión, la incorporación de Primary Constructors en C# 12 es un paso en la dirección correcta, haciendo que el lenguaje sea más expresivo y menos verboso, acercándose a la simplicidad que Scala ha ofrecido durante años. Este paralelismo no solo demuestra la influencia de los lenguajes funcionales en lenguajes más tradicionales como C#, sino que también resalta la tendencia hacia una programación más concisa y declarativa.
Y cada día que pasa veo a C# más parecido a Scala ...
1> catch throw(whoa).
whoa
2> catch exit(die).
{'EXIT',die}
3> catch 1/0.
{'EXIT',{badarith,[{erlang,'/',[1,0]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
4> catch 2+2.
4
Lo que podemos ver de esto es que los lanzamientos siguen siendo los mismos, pero que las salidas y los errores se representan como {'EXIT', Reason}. Esto se debe a que los errores se incorporan al lenguaje después de las salidas (mantuvieron una representación similar para compatibilidad con versiones anteriores).
La forma de leer este seguimiento de pila es la siguiente:
5> catch doesnt:exist(a,4).
{'EXIT',{undef,[{doesnt,exist,[a,4]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
El tipo de error es indefinido, lo que significa que la función que llamaste no está definida.
La lista que aparece justo después del tipo de error es un seguimiento de la pila
La tupla que está en la parte superior del seguimiento de la pila representa la última función que se llamó ({Módulo, Función, Argumentos}). Esa es tu función indefinida.
Las tuplas que siguen son las funciones llamadas antes del error. Esta vez tienen la forma {Módulo, Función, Aridad}.
Eso es todo lo que hay que hacer, en realidad.
También se puede obtener un seguimiento de la pila manualmente llamando a erlang:get_stacktrace/0 en el proceso que falló.
A menudo verás que catch está escrito de la siguiente manera:
catcher(X,Y) ->
case catch X/Y of
{'EXIT', {badarith,_}} -> "uh oh";
N -> N
end.
Y como era de esperar:
6> c(exceptions).
{ok,exceptions}
7> exceptions:catcher(3,3).
1.0
8> exceptions:catcher(6,3).
2.0
9> exceptions:catcher(6,0).
"uh oh"
Suena compacto y fácil de capturar excepciones, pero hay algunos problemas con catch. El primero de ellos es la precedencia de operadores:
10> X = catch 4+2.
* 1: syntax error before: 'catch'
10> X = (catch 4+2).
6
Esto no es exactamente intuitivo, dado que la mayoría de las expresiones no necesitan estar entre paréntesis de esta manera. Otro problema con catch es que no se puede ver la diferencia entre lo que parece ser la representación subyacente de una excepción y una excepción real:
11> catch erlang:boat().
{'EXIT',{undef,[{erlang,boat,[]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
12> catch exit({undef, [{erlang,boat,[]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}).
{'EXIT',{undef,[{erlang,boat,[]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
Y no puedes saber la diferencia entre un error y una salida real. También podrías haber usado throw/1 para generar la excepción anterior. De hecho, un throw/1 en un catch también podría ser problemático en otro escenario:
one_or_two(1) -> return;
one_or_two(2) -> throw(return).
Y ahora el problema mortal:
13> c(exceptions).
{ok,exceptions}
14> catch exceptions:one_or_two(1).
return
15> catch exceptions:one_or_two(2).
return
Como estamos detrás de un catch, nunca podemos saber si la función generó una excepción o si devolvió un valor real. Es posible que esto no suceda con mucha frecuencia en la práctica, pero sigue siendo un problema lo suficientemente grave como para justificar la incorporación de la construcción try...catch.
Un procedimiento almacenado es un conjunto de instrucciones SQL que pueden ser almacenadas en la base de datos y ejecutadas cuando sea necesario. Los procedimientos almacenados son útiles porque permiten encapsular lógica compleja en un solo lugar, lo que facilita su mantenimiento y reutilización.
Las funciones son similares a los procedimientos almacenados, pero con algunas diferencias clave. Mientras que un procedimiento almacenado puede ejecutar una serie de comandos SQL y no necesariamente retornar un valor, una función siempre retorna un valor y está diseñada para ser utilizada en expresiones SQL.
En H2, los procedimientos almacenados se crean utilizando la palabra clave `CREATE ALIAS`. Veamos un ejemplo sencillo de cómo crear un procedimiento almacenado que inserta un nuevo registro en una tabla:
CREATE ALIAS insert_user AS $$
void insert_user(Connection conn, String username, String email) throws SQLException {
PreparedStatement prep = conn.prepareStatement("INSERT INTO users(username, email) VALUES(?, ?)");
prep.setString(1, username);
prep.setString(2, email);
prep.execute();
prep.close();
}
$$;
Una vez que el procedimiento almacenado ha sido creado, puedes llamarlo usando `CALL`:
CALL insert_user('JohnDoe', 'john.doe@example.com');
Si necesitas que tu procedimiento devuelva resultados, puedes modificarlo ligeramente:
CREATE ALIAS get_user_by_id AS $$
ResultSet get_user_by_id(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
prep.setInt(1, id);
return prep.executeQuery();
}
$$;
Luego puedes utilizar el procedimiento en una consulta:
CALL get_user_by_id(1);
Al igual que los procedimientos, las funciones en H2 se crean utilizando `CREATE ALIAS`, pero la diferencia es que las funciones siempre retornan un valor. Aquí tienes un ejemplo de una función que calcula el IVA de un precio dado:
CREATE ALIAS calculate_vat AS $$
double calculate_vat(double price) {
return price * 0.21;
}
$$;
Una vez creada, puedes utilizar la función en tus consultas SQL:
SELECT calculate_vat(100) AS vat_amount;
Esto te devolverá el valor con el IVA aplicado.
H2 ofrece un soporte robusto para procedimientos almacenados y funciones, lo que lo hace muy útil incluso en aplicaciones más allá del simple almacenamiento de datos en memoria. Con la capacidad de encapsular lógica SQL compleja en procedimientos y funciones, puedes mantener tu código más limpio y organizado.
try Expression of
SuccessfulPattern1 [Guards] ->
Expression1;
SuccessfulPattern2 [Guards] ->
Expression2
catch
TypeOfError:ExceptionPattern1 ->
Expression3;
TypeOfError:ExceptionPattern2 ->
Expression4
end.
Se dice que la expresión entre try y of está protegida. Esto significa que cualquier tipo de excepción que ocurra dentro de esa llamada será capturada. Los patrones y expresiones entre try ... of y catch se comportan exactamente de la misma manera que un case ... of. Finalmente, la parte catch: aquí, puedes reemplazar TypeOfError por error, throw o exit. Si no se proporciona ningún tipo, se asume un throw.
En primer lugar, comencemos un módulo llamado excepciones. Vamos a optar por algo simple:
-module(exceptions).
-compile(export_all).
throws(F) ->
try F() of
_ -> ok
catch
Throw -> {throw, caught, Throw}
end.
Podemos compilarlo y probarlo con diferentes tipos de excepciones:
1> c(exceptions).
{ok,exceptions}
2> exceptions:throws(fun() -> throw(thrown) end).
{throw,caught,thrown}
3> exceptions:throws(fun() -> erlang:error(pang) end).
** exception error: pang
Este try... catch solo recibe Throw. Como se dijo anteriormente, esto se debe a que cuando no se menciona ningún tipo, se asume un Throw. Entonces tenemos funciones con cláusulas catch de cada tipo:
errors(F) ->
try F() of
_ -> ok
catch
error:Error -> {error, caught, Error}
end.
exits(F) ->
try F() of
_ -> ok
catch
exit:Exit -> {exit, caught, Exit}
end.
Y para probarlos:
4> c(exceptions).
{ok,exceptions}
5> exceptions:errors(fun() -> erlang:error("Die!") end).
{error,caught,"Die!"}
6> exceptions:exits(fun() -> exit(goodbye) end).
{exit,caught,goodbye}
Y ahora vamos con throws. Un throws es una clase de excepción que se utiliza para los casos que se espera que el programador maneje. En comparación con exits y errors, en realidad no tienen ninguna intención de "bloquear ese proceso", sino que controlan el flujo. Como se utilizan excepciones mientras se espera que el programador las maneje, suele ser una buena idea documentar su uso dentro de un módulo que las utilice.
La sintaxis para generar una excepción es:
1> throw(permission_denied).
** exception throw: permission_denied
Donde puedes reemplazar permission_denied por cualquier cosa que quieras (incluso "todo está bien", pero eso no es útil y perderás amigos).
Los throws también se pueden usar para retornos no locales cuando se está en una recursión profunda. Un ejemplo de eso es el módulo ssl que usa throw/1 como una forma de enviar tuplas {error, Reason} de regreso a una función de nivel superior. Esta función simplemente devuelve esa tupla al usuario. Esto permite que el implementador solo escriba para los casos exitosos y tenga una función que se ocupe de las excepciones además de todo.
Otro ejemplo podría ser el módulo de matriz, donde hay una función de búsqueda que puede devolver un valor predeterminado proporcionado por el usuario si no puede encontrar el elemento necesario. Cuando no se puede encontrar el elemento, el valor predeterminado se lanza como una excepción y la función de nivel superior lo maneja y lo sustituye con el valor predeterminado proporcionado por el usuario. Esto evita que el programador del módulo tenga que pasar el valor predeterminado como parámetro de cada función del algoritmo de búsqueda, centrándose nuevamente solo en los casos exitosos.
Como regla general, intente limitar el uso de sus lanzamientos para retornos no locales a un solo módulo para facilitar la depuración de su código. También le permitirá cambiar las partes internas de su módulo sin requerir cambios en su interfaz.