Translate

jueves, 4 de mayo de 2023

Primeros pasos con Phoenix parte 9

 


Seguimos la idea del post anterior: Phoenix adopta el diseño de enchufe o plugs para funcionalidad componible. 

Por ejemplo los Endpoints estan organizados por plugs: 

defmodule HelloWeb.Endpoint do

  ...


  plug :introspect

  plug HelloWeb.Router


Los conectores Endpoint predeterminados hacen bastante trabajo. Aquí están en orden:

Plug.Static: sirve activos estáticos. Dado que este complemento viene antes que el registrador, las solicitudes de activos estáticos no se registran.

Phoenix.LiveDashboard.RequestLogger: configura el registrador de solicitudes para Phoenix LiveDashboard, esto le permitirá tener la opción de pasar un parámetro de consulta para transmitir registros de solicitudes o habilitar/deshabilitar una cookie que transmite registros de solicitudes desde su tablero.

Plug.RequestId: genera un ID de solicitud único para cada solicitud.

Plug.Telemetry: agrega puntos de instrumentación para que Phoenix pueda registrar la ruta de la solicitud, el código de estado y la hora de la solicitud de forma predeterminada.

Plug.Parsers: analiza el cuerpo de la solicitud cuando hay disponible un analizador conocido. De forma predeterminada, este complemento puede manejar contenido codificado en URL, de varias partes y JSON (con Jason). El cuerpo de la solicitud se deja intacto si el tipo de contenido de la solicitud no se puede analizar.

Plug.MethodOverride: convierte el método de solicitud en PUT, PATCH o DELETE para solicitudes POST con un parámetro _method válido.

Plug.Head: convierte las solicitudes HEAD en solicitudes GET y elimina el cuerpo de la respuesta

Plug.Session: un complemento que configura la gestión de sesiones. Tenga en cuenta que fetch_session/2 aún debe llamarse explícitamente antes de usar la sesión, ya que este complemento solo configura cómo se obtiene la sesión.

En el medio del endpoint, también hay un bloque condicional:


  if code_reloading? do

    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket

    plug Phoenix.LiveReloader

    plug Phoenix.CodeReloader

    plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello

  end


Este bloque solo se ejecuta en desarrollo. Permite:

  • recarga en vivo: si cambia un archivo CSS, se actualizan en el navegador sin actualizar la página;
  • recarga de código, para que podamos ver los cambios en nuestra aplicación sin reiniciar el servidor;
  • verificar el estado del repositorio, lo que garantiza que nuestra base de datos esté actualizada y, de lo contrario, genera un error legible y procesable.


En el enrutador, podemos declarar enchufes dentro de tuberías:


defmodule HelloWeb.Router do

  use HelloWeb, :router

  pipeline :browser do

    plug :accepts, ["html"]

    plug :fetch_session

    plug :fetch_live_flash

    plug :put_root_layout, {HelloWeb.LayoutView, :root}

    plug :protect_from_forgery

    plug :put_secure_browser_headers

    plug HelloWeb.Plugs.Locale, "en"

  end

  scope "/", HelloWeb do

   pipe_through :browser

    get "/", PageController, :index

  end


Las rutas se definen dentro de los ámbitos y los ámbitos pueden canalizarse a través de múltiples canalizaciones. Una vez que una ruta coincide, Phoenix invoca todos los complementos definidos en todas las tuberías asociadas a esa ruta. Por ejemplo, acceder a "/" se canalizará a través de la canalización del navegador, invocando en consecuencia todos sus conectores.

Como veremos en la guía de enrutamiento, las tuberías en sí mismas son tapones. Allí, también discutiremos todos los complementos en la tubería :browser.

Finalmente, los controladores también son enchufes, por lo que podemos hacer:


defmodule HelloWeb.PageController do

  use HelloWeb, :controller

  plug HelloWeb.Plugs.Locale, "en"


En particular, los complementos del controlador brindan una función que nos permite ejecutar complementos solo dentro de ciertas acciones. Por ejemplo, puedes hacer:

defmodule HelloWeb.PageController do

  use HelloWeb, :controller

 plug HelloWeb.Plugs.Locale, "en" when action in [:index]


Y el complemento solo se ejecutará para la acción de índice.

Al cumplir con el contrato de conexión, convertimos una solicitud de aplicación en una serie de transformaciones explícitas. No se detiene ahí. Para ver realmente qué tan efectivo es el diseño de Plug, imaginemos un escenario en el que necesitamos verificar una serie de condiciones y luego redirigir o detenernos si una condición falla. Sin enchufe, terminaríamos con algo como esto:



defmodule HelloWeb.MessageController do

  use HelloWeb, :controller

  def show(conn, params) do

    case Authenticator.find_user(conn) do

      {:ok, user} ->

        case find_message(params["id"]) do

          nil ->

            conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/")

          message ->

            if Authorizer.can_access?(user, message) do

              render(conn, :show, page: message)

            else

              conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/")

            end

        end

      :error ->

        conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/")


    end

  end

end


¿Observa cómo unos pocos pasos de autenticación y autorización requieren anidamiento y duplicación complicados? Mejoremos esto con un par de enchufes.


defmodule HelloWeb.MessageController do

  use HelloWeb, :controller

  plug :authenticate

  plug :fetch_message

  plug :authorize_message

  def show(conn, params) do

    render(conn, :show, page: conn.assigns[:message])

  end


  defp authenticate(conn, _) do

   case Authenticator.find_user(conn) do

      {:ok, user} ->

        assign(conn, :user, user)

      :error ->

        conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") |> halt()

    end

  end

  defp fetch_message(conn, _) do

    case find_message(conn.params["id"]) do

      nil ->

        conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") |> halt()

      message ->

        assign(conn, :message, message)

    end

  end


  defp authorize_message(conn, _) do

    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do

      conn

    else

      conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt()

    end

  end

end


Para que todo esto funcione, convertimos los bloques de código anidados y usamos halt(conn) cada vez que llegamos a una ruta de error. La funcionalidad halt(conn) es esencial aquí: le dice a Plug que no se debe invocar el siguiente plug.

Al final del día, al reemplazar los bloques de código anidados con una serie aplanada de transformaciones de complemento, podemos lograr la misma funcionalidad de una manera mucho más componible, clara y reutilizable.


miércoles, 3 de mayo de 2023

Introducción a el modelo semantico de Roslyn

Hasta este punto, hemos estado trabajando con código C# en un nivel puramente sintáctico. Podemos encontrar declaraciones de propiedades, pero no podemos rastrear referencias a esta propiedad dentro de nuestro código fuente. Podemos identificar invocaciones, pero no podemos decir qué se está invocando. Y Dios nos ayude si queremos tratar de resolver los problemas realmente difíciles como la resolución de sobrecarga.

La capa semántica es donde realmente brilla el poder de Roslyn. El modelo semántico de Roslyn puede responder todas las preguntas difíciles de tiempo de compilación que podamos tener. Sin embargo, este poder tiene un costo. Consultar el modelo semántico suele ser más costoso que consultar los árboles de sintaxis. Esto se debe a que solicitar un modelo semántico a menudo desencadena una compilación.

Hay 3 formas diferentes de solicitar el modelo semántico:

1. Document.GetSemanticModel()

2. Compilation.GetSemanticModel(SyntaxTree)

3. Diferentes contextos como AnalysisContexts, CodeBlockStartAnalysisContext.SemanticModel y SemanticModelAnalysisContext.SemanticModel

Para evitar el problema de configurar nuestro propio espacio de trabajo, simplemente crearemos compilaciones para árboles de sintaxis individuales de la siguiente manera:


var tree = CSharpSyntaxTree.ParseText(@"

public class MyClass 

{

int MyMethod() { return 0; }

}");


var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


Antes de continuar, vale la pena tomarse un momento para analizar los Símbolos. Los programas de C# se componen de elementos únicos, como tipos, métodos, propiedades, etc. Los símbolos representan casi todo lo que el compilador sabe sobre cada uno de estos elementos únicos.

En un nivel alto, cada símbolo contiene información sobre:

  • Donde estos elementos se declaran en fuente o metadatos (Puede haber venido de un ensamblado externo)
  • Dentro de qué espacio de nombres y tipo existe este símbolo
  • Varias verdades acerca de que el símbolo es abstracto, estático, sellado, etc.

También se puede descubrir otra información más dependiente del contexto. Cuando se trata de métodos, IMethodSymbol nos permite determinar:

  • Si el método oculta un método base.
  • El símbolo que representa el tipo de retorno del método.
  • El método de extensión del que se sobreescribio este símbolo.

El modelo semántico es nuestro puente entre el mundo de la sintaxis y el mundo de los símbolos.

SemanticModel.GetDeclaredSymbol() acepta la sintaxis de declaración y proporciona el símbolo correspondiente.

SemanticModel.GetSymbolInfo() acepta la sintaxis de expresión (p. ej., InvocationExpressionSyntax) y devuelve un símbolo. Si el modelo no pudo resolver con éxito un símbolo, proporciona símbolos candidatos que pueden servir como mejores conjeturas.

A continuación, recuperamos el símbolo de un método a través de su sintaxis de declaración. Luego recuperamos el mismo símbolo, pero a través de una invocación (InvocaciónExpresiónSyntax) en su lugar.


var tree = CSharpSyntaxTree.ParseText(@"

public class MyClass {

int Method1() { return 0; }

void Method2()

{

int x = Method1();

}

}

}");


var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);

var compilation = CSharpCompilation.Create("MyCompilation",

syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


//Looking at the first method symbol

var methodSyntax = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().First();

var methodSymbol = model.GetDeclaredSymbol(methodSyntax);


Console.WriteLine(methodSymbol.ToString());         //MyClass.Method1()

Console.WriteLine(methodSymbol.ContainingSymbol);   //MyClass

Console.WriteLine(methodSymbol.IsAbstract);         //false


//Looking at the first invocation

var invocationSyntax = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>().First();

var invokedSymbol = model.GetSymbolInfo(invocationSyntax).Symbol; //Same as MyClass.Method1


Console.WriteLine(invokedSymbol.ToString());         //MyClass.Method1()

Console.WriteLine(invokedSymbol.ContainingSymbol);   //MyClass

Console.WriteLine(invokedSymbol.IsAbstract);         //false


Console.WriteLine(invokedSymbol.Equals(methodSymbol)); //true


Una instancia de SemanticModel almacena en caché símbolos locales e información semántica. Por lo tanto, es mucho más eficiente usar una sola instancia de SemanticModel cuando se hacen varias preguntas sobre un árbol de sintaxis, porque la información de la primera pregunta se puede reutilizar. Esto también significa que mantener una instancia de SemanticModel durante mucho tiempo puede evitar que una cantidad significativa de memoria se recopile como basura.

Esencialmente, Roslyn le permite hacer el equilibrio entre la memoria y la computación. Al consultar el modelo semántico de forma repetitiva, puede ser de su interés mantener una instancia del mismo, en lugar de solicitar un nuevo modelo de una compilación o documento.

jueves, 27 de abril de 2023

Trabajando con Workspaces con Roslyn

Hasta este punto, simplemente hemos estado construyendo árboles de sintaxis a partir de cadenas. Este enfoque funciona bien cuando se crean muestras cortas, pero a menudo nos gustaría trabajar con soluciones completas. 

Los Workspaces son el nodo raíz de una jerarquía de C# que consta de una solución, proyectos secundarios y documentos secundarios. Un principio fundamental dentro de Roslyn es que la mayoría de los objetos son inmutables. Esto significa que no podemos aferrarnos a una referencia a una solución y esperar que esté actualizada para siempre. En el momento en que se realice un cambio, esta solución quedará obsoleta y se habrá creado una nueva solución actualizada. Los espacios de trabajo son nuestro nodo raíz. A diferencia de las soluciones, los proyectos y los documentos, no dejarán de ser válidos y siempre contendrán una referencia a la solución actual más actualizada. Hay cuatro variantes de Workspace a considerar:

Workspaces:  La clase base abstracta para todos los demás espacios de trabajo. Es un poco falso afirmar que es una variante del espacio de trabajo, ya que nunca tendrás una instancia de ella. En cambio, esta clase sirve como una especie de API en torno a la cual se pueden crear implementaciones de espacios de trabajo reales. Puede ser tentador pensar en áreas de trabajo únicamente dentro del contexto de Visual Studio. Después de todo, para la mayoría de los desarrolladores de C#, esta es la única forma en que hemos tratado las soluciones y los proyectos. Sin embargo, Workspace está destinado a ser agnóstico en cuanto a la fuente física de los archivos que representa. Las implementaciones individuales pueden almacenar los archivos en el sistema de archivos local, dentro de una base de datos o incluso en una máquina remota. Uno simplemente hereda de esta clase y anula las implementaciones vacías de Workspace como mejor le parezca.

MSBuildWorkspace:  Un espacio de trabajo creado para manejar archivos de solución (.sln) y proyecto (.csproj, .vbproj) de MSBuild. Desafortunadamente, actualmente no puede escribir en archivos .sln, lo que significa que no podemos usarlo para agregar proyectos o crear nuevas soluciones.

El siguiente ejemplo muestra cómo podemos iterar sobre todos los documentos en una solución:


string solutionPath = @"C:\Users\…\PathToSolution\MySolution.sln";

var msWorkspace = MSBuildWorkspace.Create();


var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;

foreach (var project in solution.Projects)

{

foreach (var document in project.Documents)

{

Console.WriteLine(project.Name + "\t\t\t" + document.Name);

}

}


AdhocWorkspace : Un espacio de trabajo que permite agregar archivos de solución y proyecto manualmente. Se debe tener en cuenta que la API para agregar y eliminar elementos de la solución es diferente dentro de AdhocWorkspace en comparación con los otros espacios de trabajo. En lugar de llamar a TryApplyChanges(), se proporcionan métodos para agregar proyectos y documentos en el nivel del espacio de trabajo. Este espacio de trabajo está destinado a aquellos que solo necesitan una forma rápida y sencilla de crear un espacio de trabajo y agregarle proyectos y documentos.


var workspace = new AdhocWorkspace();

string projName = "NewProject";

var projectId = ProjectId.CreateNewId();

var versionStamp = VersionStamp.Create();

var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp);

var newProject = workspace.AddProject(projectInfo);

var sourceText = SourceText.From("class A {}");

var newDocument = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText);


foreach (var project in workspace.CurrentSolution.Projects)

{

foreach (var document in project.Documents)

{

Console.WriteLine(project.Name + "\t\t\t" + document.Name);

}

}

VisualStudioWorkspace : El espacio de trabajo activo consumido dentro de los paquetes de Visual Studio. Como este espacio de trabajo está estrechamente integrado con Visual Studio, es difícil proporcionar un pequeño ejemplo sobre cómo usar este espacio de trabajo. 



Clean

 


Clean es un lenguaje de programación funcional de propósito general, diseñado para hacer que la programación sea más segura, eficiente y fácil de entender. Clean es un lenguaje de programación de alto nivel que ofrece muchas características avanzadas de programación funcional, como la inferencia de tipos, el orden superior y la recursividad estructural.

Características principales de Clean:

  • Sintaxis clara: Clean ofrece una sintaxis clara y legible que permite una fácil comprensión del código y facilita el mantenimiento y la depuración.
  • Inferencia de tipos: Clean utiliza la inferencia de tipos, lo que significa que el compilador puede determinar el tipo de datos de una variable sin que el programador tenga que especificarlo explícitamente.
  • Recursividad estructural: Clean ofrece una técnica de recursividad estructural que garantiza la terminación de una función recursiva y previene la ocurrencia de bucles infinitos.
  • Orden superior: Clean admite el uso de funciones de orden superior, lo que significa que las funciones pueden tomar otras funciones como argumentos y devolver funciones como resultados.
  • Gestión automática de memoria: Clean maneja automáticamente la gestión de memoria, lo que significa que el programador no tiene que preocuparse por la asignación y desasignación de memoria.


Ventajas de Clean:

  • Facilita el mantenimiento y la depuración: La sintaxis clara y legible de Clean facilita el mantenimiento y la depuración del código.
  • Seguridad: Clean garantiza que los programas sean seguros mediante la eliminación de errores comunes de programación, como la asignación de memoria no válida y la división por cero.
  • Eficiencia: Clean es un lenguaje de programación muy eficiente que utiliza técnicas avanzadas de optimización de código para maximizar el rendimiento.
  • Escalabilidad: Clean es un lenguaje de programación escalable que se adapta bien a proyectos grandes y complejos.


En resumen, Clean es un lenguaje de programación funcional que ofrece muchas características avanzadas de programación funcional, como la inferencia de tipos, el orden superior y la recursividad estructural. Clean es un lenguaje de programación seguro, eficiente, escalable y fácil de entender que es ideal para proyectos de desarrollo de software de alta calidad.


Veamos un ejemplo : 


module Main

import StdEnv


factorial :: Int -> Int

factorial n = if n == 0 then 1 else n * factorial (n - 1)


Start w = writeln (show (factorial 5))


En este ejemplo, la función factorial toma un argumento entero n y calcula el factorial de n utilizando una definición recursiva. Si n es cero, la función devuelve 1. De lo contrario, la función multiplica n por el factorial de n-1. La función show convierte el resultado en una cadena y la función writeln imprime la cadena en la salida estándar.

El programa principal es la función Start, que utiliza writeln para imprimir el resultado del cálculo del factorial de 5. El tipo w en la declaración Start w indica que w no se utiliza en el programa y se puede ignorar.

Dejo link : https://clean-lang.org/


lunes, 24 de abril de 2023

Analisis del flujo de control

El análisis de flujo de control se utiliza para comprender los diversos puntos de entrada y salida dentro de un bloque de código y para responder preguntas sobre accesibilidad. Si estamos analizando un método, es posible que nos interesen todos los puntos return . Si estamos analizando un bucle for, es posible que nos interesen todos los lugares en los que hacemos break o continue.

Activamos el análisis de flujo de control a través de un método de SemanticModel. Esto nos devuelve una instancia de ControlFlowAnalysis que expone las siguientes propiedades:

EntryPoints: el conjunto de declaraciones dentro de la región que son el destino de las sucursales fuera de la región.

ExitPoints: el conjunto de declaraciones dentro de una región que salta a ubicaciones fuera de la región.

EndPointIsReachable: indica si una región se completa normalmente. Devuelve verdadero si y solo si se puede llegar al final de la última declaración o si toda la región no contiene declaraciones.

StartPointIsReachable: indica si una región puede comenzar normalmente.

ReturnStatements: el conjunto de declaraciones de devolución dentro de una región.

Succeeded: devuelve verdadero si y solo si el análisis fue exitoso. El análisis puede fallar si la región no abarca correctamente una sola expresión, una sola declaración o una serie contigua de declaraciones dentro del bloque adjunto.


Uso básico de la API:


var tree = CSharpSyntaxTree.ParseText(@"

    class C

    {

        void M()

        {

            for (int i = 0; i < 10; i++)

            {

                if (i == 3)

                    continue;

                if (i == 8)

                    break;

            }

        }

    }

");


var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


var firstFor = tree.GetRoot().DescendantNodes().OfType<ForStatementSyntax>().Single();

ControlFlowAnalysis result = model.AnalyzeControlFlow(firstFor.Statement);


Console.WriteLine(result.Succeeded);            //True

Console.WriteLine(result.ExitPoints.Count());    //2 – continue, and break


Alternativamente, podemos especificar dos declaraciones y analizar las declaraciones entre los dos. El siguiente ejemplo demuestra esto y el uso de EntryPoints:


var tree = CSharpSyntaxTree.ParseText(@"

class C

{

    void M(int x)

    {

        L1: ; // 1

        if (x == 0) goto L1;    //firstIf

        if (x == 1) goto L2;

        if (x == 3) goto L3;

        L3: ;                   //label3

        L2: ; // 2

        if(x == 4) goto L3;

    }

}

");


var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);

var compilation = CSharpCompilation.Create("MyCompilation",

syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


//Choose first and last statements

var firstIf = tree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().First();

var label3 = tree.GetRoot().DescendantNodes().OfType<LabeledStatementSyntax>().Skip(1).Take(1).Single();


ControlFlowAnalysis result = model.AnalyzeControlFlow(firstIf, label3);

Console.WriteLine(result.EntryPoints);      //1 – Label 3 is a candidate entry point within these statements

Console.WriteLine(result.ExitPoints);       //2 – goto L1 and goto L2 and candidate exit points


En el ejemplo anterior, vemos un ejemplo de una posible etiqueta de punto de entrada L3. Que yo sepa, las etiquetas son los únicos puntos de entrada posibles.

Finalmente, veremos cómo responder preguntas sobre la accesibilidad. A continuación, no se puede alcanzar ni el punto inicial ni el punto final:

var tree = CSharpSyntaxTree.ParseText(@"

    class C

    {

        void M(int x)

        {

            return;

            if(x == 0)                                  //-+     Start is unreachable

                System.Console.WriteLine(""Hello"");    // |

            L1:                                            //-+    End is unreachable

        }

    }

");


var Mscorlib = PortableExecutableReference.CreateFromAssembly(typeof(object).Assembly);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);


//Choose first and last statements

var firstIf = tree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().Single();

var label1 = tree.GetRoot().DescendantNodes().OfType<LabeledStatementSyntax>().Single();


ControlFlowAnalysis result = model.AnalyzeControlFlow(firstIf, label1);

Console.WriteLine(result.StartPointIsReachable);    //False

Console.WriteLine(result.EndPointIsReachable);      //False


En general, la API de flujo de control parece mucho más intuitiva que la API de análisis de flujo de datos. Requiere menos conocimiento de la especificación de C# y es sencillo trabajar con él. 


Primeros pasos con Phoenix parte 8


Plug vive en el corazón de la capa HTTP de Phoenix. Interactuamos con los enchufes o Plug en cada paso del ciclo de vida de una request, y los componentes principales de Phoenix, como end points, enrutadores y controladores, son solo enchufes internamente. 

Plug es una especificación para módulos componibles entre aplicaciones web. También es una capa de abstracción para adaptadores de conexión de diferentes servidores web. La idea básica de Plug es unificar el concepto de "conexión" sobre la que operamos. Esto difiere de otras capas de middleware HTTP como Rack, donde la solicitud y la respuesta están separadas en la pila de middleware.

En el nivel más simple, la especificación Plug viene en dos formas: complementos de funciones y complementos de módulos.

Para actuar como un enchufe, una función necesita: aceptar una estructura de conexión (%Plug.Conn{}) como primer argumento y opciones de conexión como segundo;

Cualquier función que cumpla con estos dos criterios servirá. Por ejemplo:


def introspect(conn, _opts) do

  IO.puts """

  Verb: #{inspect(conn.method)}

  Host: #{inspect(conn.host)}

  Headers: #{inspect(conn.req_headers)}

  """


  conn

end


Esta función hace lo siguiente:

  1. Recibe una conexión y opciones (que no usamos)
  2. Imprime alguna información de conexión en el terminal.
  3. Devuelve la conexión

Veamos esta función en acción agregándola a nuestro end point en lib/hello_web/endpoint.ex. Podemos conectarlo en cualquier lugar, así que hagámoslo insertando plug :introspect justo antes de delegar la solicitud al enrutador:

defmodule HelloWeb.Endpoint do

  ...


  plug :introspect

  plug HelloWeb.Router


  def introspect(conn, _opts) do

    IO.puts """

    Verb: #{inspect(conn.method)}

    Host: #{inspect(conn.host)}

    Headers: #{inspect(conn.req_headers)}

    """


    conn

  end

end


Los complementos de función se conectan pasando el nombre de la función como un átomo. Para probar esto, vamos al navegador y buscamos http://localhost:4000. Debería ver algo como esto impreso en su terminal de shell:


Verb: "GET"

Host: "localhost"

Headers: [...]


Nuestro enchufe simplemente imprime información de la conexión. Aunque nuestro complemento inicial es muy simple, puede hacer prácticamente cualquier cosa que desee dentro de él. 

Ahora veamos la otra variante de enchufe, los enchufes de módulo.

Los conectores de módulo son otro tipo de conector que nos permite definir una transformación de conexión en un módulo. El módulo solo necesita implementar dos funciones:

  • init/1 que inicializa cualquier argumento u opción que se pase a call/2
  • call/2 que realiza la transformación de la conexión. call/2 es solo un complemento de función como vimos anteriormente

Para ver esto en acción, escribamos un complemento de módulo que coloque la clave y el valor :locale en la conexión para uso posterior en otros complementos, acciones de controlador y nuestras vistas. Coloquemos el contenido a continuación en un archivo llamado lib/hello_web/plugs/locale.ex:


defmodule HelloWeb.Plugs.Locale do

  import Plug.Conn


  @locales ["en", "fr", "de"]


  def init(default), do: default


  def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do

    assign(conn, :locale, loc)

  end


  def call(conn, default) do

    assign(conn, :locale, default)

  end

end


Para probarlo, agreguemos este complemento de módulo a nuestro enrutador, agregando el complemento HelloWeb.Plugs.Locale, "en" a nuestra tubería de navegación en lib/hello_web/router.ex:


defmodule HelloWeb.Router do

  use HelloWeb, :router


  pipeline :browser do

    plug :accepts, ["html"]

    plug :fetch_session

    plug :fetch_flash

    plug :protect_from_forgery

    plug :put_secure_browser_headers

    plug HelloWeb.Plugs.Locale, "en"

  end

  ...


En la devolución de llamada init/1, pasamos una configuración regional predeterminada para usar si no hay ninguna presente en los parámetros. También utilizamos la coincidencia de patrones para definir varios encabezados de función call/2 para validar la configuración regional en los parámetros, y recurrimos a "en" si no hay ninguna coincidencia. assign/3  es parte del módulo Plug.Conn y es cómo almacenamos valores en la estructura de datos de conn.

Para ver la asignación en acción, vayamos a la plantilla en lib/hello_web/components/layouts/home.html.heex y agreguamos el siguiente código después del cierre de la etiqueta </h1>:


<p>Locale: <%= @locale %></p>


Si vamos a http://localhost:4000/ y debería ver la configuración regional exhibida. Y si vamos a http://localhost:4000/?locale=fr y debería ver la asignación cambiada a "fr". Alguien puede usar esta información junto con Gettext para proporcionar una aplicación web totalmente internacionalizada.

Eso es todo lo que hay que hacer con Plug. Phoenix adopta el diseño de enchufe de las transformaciones componibles en toda la pila. 



jueves, 20 de abril de 2023

CSharpSyntaxRewriter

En el post anterior hablamos de CSharpSyntaxWalker y cómo podríamos navegar el árbol de sintaxis con el patrón de visitor. Ahora vamos un paso más allá con CSharpSyntaxRewriter y "modificamos" el árbol sintactico a medida que lo recorremos. Es importante tener en cuenta que en realidad no estamos mutando el árbol sintactico original, ya que los árboles de Roslyn son inmutables. En su lugar, CSharpSyntaxRewriter crea un nuevo árbol como resultado de nuestros cambios.

CSharpSyntaxRewriter puede visitar todos los nodos, tokens o trivias dentro de un árbol sintactico. Al igual que CSharpSyntaxVisitor, podemos elegir de forma selectiva qué fragmentos de sintaxis nos gustaría visitar. Hacemos esto sobreescribiendo varios métodos y devolviendo lo siguiente:

  • El nodo, token o trivia original, sin cambios.
  • Nulo, que indica que se debe eliminar el nodo, el token o la trivia.
  • Un nuevo nodo de sintaxis, token o trivia.

Al igual que con la mayoría de las API, CSharpSyntaxRewriter se comprende mejor a través de ejemplos. Una pregunta reciente sobre Stack Overflow preguntó ¿Cómo puedo eliminar los puntos y comas redundantes en el código con SyntaxRewriter?

Roslyn trata todos los puntos y coma redundantes como parte de un nodo EmptyStatementSyntax. A continuación, demostramos cómo resolver el caso base: un punto y coma innecesario en una línea propia.


public class EmtpyStatementRemoval : CSharpSyntaxRewriter

{

    public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node)

    {

        //Simply remove all Empty Statements

        return null;

    }

}


public static void Main(string[] args)

{

    //A syntax tree with an unnecessary semicolon on its own line

    var tree = CSharpSyntaxTree.ParseText(@"

    public class Sample

    {

       public void Foo()

       {

          Console.WriteLine();

          ;

        }

    }");


    var rewriter = new EmtpyStatementRemoval();

    var result = rewriter.Visit(tree.GetRoot());

    Console.WriteLine(result.ToFullString());

}


La salida de este programa produce un programa simple sin ningún punto y coma redundante.


public class Sample

{

   public void Foo()

   {

      Console.WriteLine();

    }

}

Sin embargo, cuando hay trivia inicial o final, esta trivia se elimina. Esto significa que se eliminarán los comentarios por encima y por debajo del punto y coma. Al construir un EmptyStatementSyntax con un token faltante en lugar de un punto y coma, podemos eliminar el punto y coma del árbol original:

public class EmtpyStatementRemoval : CSharpSyntaxRewriter

{

    public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node)

    {

        //Construct an EmptyStatementSyntax with a missing semicolon

        return node.WithSemicolonToken(

            SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)

                .WithLeadingTrivia(node.SemicolonToken.LeadingTrivia)

                .WithTrailingTrivia(node.SemicolonToken.TrailingTrivia));

    }

}


public static void Main(string[] args)

{

    var tree = CSharpSyntaxTree.ParseText(@"

    public class Sample

    {

       public void Foo()

       {

          Console.WriteLine();

          #region SomeRegion

          //Some other code

          #endregion

          ;

        }

    }");


    var rewriter = new EmtpyStatementRemoval();

    var result = rewriter.Visit(tree.GetRoot());

    Console.WriteLine(result.ToFullString());

}


El resultado de este enfoque es:


public class Sample

{

   public void Foo()

   {

      Console.WriteLine();

      #region SomeRegion

      //Some other code

      #endregion

    }

}

Este enfoque tiene el efecto secundario de dejar una línea en blanco donde haya un punto y coma redundante. Dicho esto, creo que probablemente valga la pena el intercambio, ya que de lo contrario no parece haber una forma de retener las trivias. En última instancia, la trivia solo se puede conservar si se adjunta a un nodo y luego se devuelve ese nodo.

La única forma de eliminar el nodo y conservar las trivias es construir un nodo de reemplazo. El mejor candidato para el reemplazo probablemente sea un EmptyStatementSyntax al que le falte un punto y coma.

Esto también podría indicar una limitación con CSharpSyntaxRewriter. Parece que debería ser más fácil eliminar nodos, manteniendo sus trivias.

miércoles, 19 de abril de 2023

Le preguntamos a ChatGPT ¿cual es el lenguaje de programación que debo estudiar?


Estuve jugando un rato con chatGPT y les cuento : 

YO: ¿cual es el lenguaje de programación que debo estudiar?

ChatGPT : La elección del lenguaje de programación a estudiar dependerá de tus objetivos y del tipo de aplicación que quieras desarrollar. Aquí te presento algunas opciones populares y sus características principales:

Python: Es un lenguaje de programación popular en la actualidad debido a su facilidad de aprendizaje, su versatilidad y su amplio uso en aplicaciones de inteligencia artificial, ciencia de datos y análisis estadístico.

JavaScript: Es un lenguaje de programación utilizado en aplicaciones web para crear interacciones dinámicas en la interfaz de usuario, también es muy utilizado en el desarrollo de aplicaciones móviles.

Java: Es un lenguaje de programación orientado a objetos y es utilizado en aplicaciones empresariales, de escritorio y móviles.

C++: Es un lenguaje de programación de bajo nivel que se utiliza en aplicaciones de sistemas y de rendimiento.

Swift: Es el lenguaje de programación utilizado para desarrollar aplicaciones para dispositivos Apple, como iPhone y iPad.

Te recomiendo investigar más sobre estas opciones y elegir la que mejor se adapte a tus objetivos y necesidades. También es importante tener en cuenta que el aprendizaje de un lenguaje de programación no es algo que se logre de la noche a la mañana, sino que requiere práctica y dedicación.


YO: Con cual encontraré trabajo más rápido?

ChatGPT : No hay una respuesta definitiva a esta pregunta, ya que el mercado laboral y las oportunidades de trabajo varían de acuerdo a la ubicación geográfica, las tendencias de la industria y la experiencia previa que tengas.

Sin embargo, algunos lenguajes de programación son más demandados que otros en la actualidad. Por ejemplo, Python es muy utilizado en la industria de la inteligencia artificial, análisis de datos y aprendizaje automático, lo que puede hacer que sea más fácil encontrar trabajo en estas áreas.

También es importante destacar que el dominio de un solo lenguaje de programación no garantiza la obtención de trabajo en el campo de la programación. Muchas empresas buscan programadores que tengan habilidades en múltiples lenguajes y tecnologías, así como una comprensión profunda de los conceptos de programación y las metodologías de desarrollo de software.

En resumen, la elección del lenguaje de programación que te ayudará a encontrar trabajo más rápido dependerá de tus habilidades, intereses y las oportunidades laborales disponibles en tu área geográfica. Lo mejor es enfocarse en aprender bien un lenguaje de programación y tener una sólida comprensión de los fundamentos de la programación para destacar en el mercado laboral.


YO: Y cual te gusta más?

ChatGPT : Como modelo de lenguaje de programación, como soy, no tengo emociones ni preferencias. Mi función es brindar información objetiva y responder a preguntas de manera imparcial.

Sin embargo, en la comunidad de programadores, cada uno tiene sus propias preferencias y opiniones personales sobre qué lenguaje de programación es mejor para diferentes aplicaciones. La elección del lenguaje de programación dependerá de las necesidades del proyecto y del programador en sí. Lo importante es que el lenguaje de programación elegido sea adecuado para el propósito del proyecto y que el programador tenga la experiencia y habilidades necesarias para trabajar con él de manera efectiva.


Interesante conversación que les quería compartír, ustedes que opinan?



domingo, 16 de abril de 2023

Analizando el flujo de datos con Roslyn

Esta API se puede usar para inspeccionar cómo se leen y escriben las variables dentro de un bloque de código determinado. Tal vez le gustaría crear una extensión de Visual Studio que capture y registre todas las asignaciones a una determinada variable. Se puede usar la API de análisis de flujo de datos para encontrar las declaraciones y un reescritor para registrarlas.

Para demostrar las capacidades de esta API, podemos analizar el bucle for en el siguiente código:


var tree = CSharpSyntaxTree.ParseText(@"

public class Sample

{

   public void Foo()

   {

        int[] outerArray = new int[10] { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4};

        for (int index = 0; index < 10; index++)

        {

             int[] innerArray = new int[10] { 0, 1, 2, 3, 4, 0, 1, 2, 3, 4 };

             index = index + 2;

             outerArray[index – 1] = 5;

        }

   }

}");

 

var Mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

 

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { Mscorlib });

var model = compilation.GetSemanticModel(tree);

 

var forStatement = tree.GetRoot().DescendantNodes().OfType<ForStatementSyntax>().Single();

DataFlowAnalysis result = model.AnalyzeDataFlow(forStatement);


En este punto, tenemos acceso a un objeto DataFlowAnalysis.


Quizás la propiedad más importante de este objeto es Succeeded. Esto le indica si el análisis de flujo de datos se completó correctamente. En mi experiencia, la API ha sido bastante buena para lidiar con código semánticamente inválido. Ni las invocaciones a métodos faltantes ni el uso de variables no declaradas parecían hacer tropezar. La documentación señala que si la región analizada no abarca una sola expresión o declaración, es probable que el análisis falle.

El objeto DataFlowAnalysis expone una API bastante rica. Expone información sobre direcciones inseguras, variables locales capturadas por métodos anónimos y mucho más.

En nuestro caso, estamos interesados en las siguientes propiedades:

  • DataFlowAnalysis.AlwaysAssigned: el conjunto de variables locales para las que siempre se asigna un valor dentro de una región.
  • DataFlowAnalysis.ReadInside: el conjunto de variables locales que se leen dentro de una región.
  • DataFlowAnalysis.WrittenOutside: el conjunto de variables locales que se escriben fuera de una región.
  • DataFlowAnalysis.WrittenInside: el conjunto de variables locales que se escriben dentro de una región.
  • DataFlowAnalysis.VariablesDeclared: el conjunto de variables locales que se declaran dentro de una región. Tenga en cuenta que la región debe estar delimitada por el cuerpo de un método o el inicializador de un campo, por lo que los símbolos de parámetros nunca se incluyen en el resultado.


Los resultados del análisis son los siguientes:


AlwaysAssigned: index

index siempre se asigna a, ya que está contenido en el inicializador del bucle for, que se ejecuta incondicionalmente.

WrittenInside: index, innerArray

Tanto index como innerArray están claramente escritos dentro del bucle.

Un punto importante es que externalArray no. Mientras estamos mutando la matriz, no estamos mutando la referencia contenida dentro de la variable outsideArray. Por lo tanto, no aparece en esta lista.

WrittenOutside: outerArray, this

outsideArray está claramente escrito fuera del bucle for.

Sin embargo, me sorprendió que esto apareciera como un símbolo de parámetro dentro de la lista de WriteOutside. Parece como si esto se pasara como un parámetro a la clase y su miembro, lo que significa que también aparece aquí. Esto parece ser por diseño, aunque sospecho que la mayoría de los consumidores de esta API se sorprenderán y probablemente ignoren este valor.

ReadInside: index, outerArray

Está claro que el valor del índice se lee dentro del ciclo.

Me sorprendió que se considere que outsideArray se "lee" dentro del ciclo, ya que no estamos leyendo su valor directamente. Supongo que, técnicamente, primero debemos leer el valor de externalArray para calcular el desplazamiento y recuperar la dirección correcta para el elemento dado de la matriz. Así que estamos realizando una especie de "lectura implícita" dentro del ciclo aquí.


VariablesDeclared: index, innerArray

Esto es bastante sencillo. index se declara dentro del inicializador de bucle e innerArray dentro del cuerpo del bucle for.

La rareza general de la API de análisis de flujo de datos hace que no le vea mucha utilidad, se les ocurre un lugar para usarla?

dotnet new gitignore


Cuando creo un nuevo proyecto en GitHub, lo primero que agrego en el repositorio inicial es el archivo .gitignore porque no quiero tener basura en mi repo. 

No mucha gente sabe que dotnet cli proporciona un comando para crear un archivo .gitignore, prácticamente perfecto.

En la carpeta raíz del proyecto, ejecutamos el siguiente comando :

dotnet new gitignore


Y listo! Se genera un archivo .gitignore con el siguiente contenido : 


## Ignore Visual Studio temporary files, build results, and

## files generated by popular Visual Studio add-ons.

##

## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore


# User-specific files

*.rsuser

*.suo

*.user

*.userosscache

*.sln.docstates


# User-specific files (MonoDevelop/Xamarin Studio)

*.userprefs


# Mono auto generated files

mono_crash.*


# Build results

[Dd]ebug/

[Dd]ebugPublic/

[Rr]elease/

[Rr]eleases/

x64/

x86/

[Ww][Ii][Nn]32/

[Aa][Rr][Mm]/

[Aa][Rr][Mm]64/

bld/

[Bb]in/

[Oo]bj/

[Ll]og/

[Ll]ogs/


# Visual Studio 2015/2017 cache/options directory

.vs/

# Uncomment if you have tasks that create the project's static files in wwwroot

#wwwroot/


# Visual Studio 2017 auto generated files

Generated\ Files/


# MSTest test Results

[Tt]est[Rr]esult*/

[Bb]uild[Ll]og.*


# NUnit

*.VisualState.xml

TestResult.xml

nunit-*.xml


# Build Results of an ATL Project

[Dd]ebugPS/

[Rr]eleasePS/

dlldata.c


# Benchmark Results

BenchmarkDotNet.Artifacts/


# .NET

project.lock.json

project.fragment.lock.json

artifacts/


# Tye

.tye/


# ASP.NET Scaffolding

ScaffoldingReadMe.txt


# StyleCop

StyleCopReport.xml


# Files built by Visual Studio

*_i.c

*_p.c

*_h.h

*.ilk

*.meta

*.obj

*.iobj

*.pch

*.pdb

*.ipdb

*.pgc

*.pgd

*.rsp

*.sbr

*.tlb

*.tli

*.tlh

*.tmp

*.tmp_proj

*_wpftmp.csproj

*.log

*.tlog

*.vspscc

*.vssscc

.builds

*.pidb

*.svclog

*.scc


# Chutzpah Test files

_Chutzpah*


# Visual C++ cache files

ipch/

*.aps

*.ncb

*.opendb

*.opensdf

*.sdf

*.cachefile

*.VC.db

*.VC.VC.opendb


# Visual Studio profiler

*.psess

*.vsp

*.vspx

*.sap


# Visual Studio Trace Files

*.e2e


# TFS 2012 Local Workspace

$tf/


# Guidance Automation Toolkit

*.gpState


# ReSharper is a .NET coding add-in

_ReSharper*/

*.[Rr]e[Ss]harper

*.DotSettings.user


# TeamCity is a build add-in

_TeamCity*


# DotCover is a Code Coverage Tool

*.dotCover


# AxoCover is a Code Coverage Tool

.axoCover/*

!.axoCover/settings.json


# Coverlet is a free, cross platform Code Coverage Tool

coverage*.json

coverage*.xml

coverage*.info


# Visual Studio code coverage results

*.coverage

*.coveragexml


# NCrunch

_NCrunch_*

.*crunch*.local.xml

nCrunchTemp_*


# MightyMoose

*.mm.*

AutoTest.Net/


# Web workbench (sass)

.sass-cache/


# Installshield output folder

[Ee]xpress/


# DocProject is a documentation generator add-in

DocProject/buildhelp/

DocProject/Help/*.HxT

DocProject/Help/*.HxC

DocProject/Help/*.hhc

DocProject/Help/*.hhk

DocProject/Help/*.hhp

DocProject/Help/Html2

DocProject/Help/html


# Click-Once directory

publish/


# Publish Web Output

*.[Pp]ublish.xml

*.azurePubxml

# Note: Comment the next line if you want to checkin your web deploy settings,

# but database connection strings (with potential passwords) will be unencrypted

*.pubxml

*.publishproj


# Microsoft Azure Web App publish settings. Comment the next line if you want to

# checkin your Azure Web App publish settings, but sensitive information contained

# in these scripts will be unencrypted

PublishScripts/


# NuGet Packages

*.nupkg

# NuGet Symbol Packages

*.snupkg

# The packages folder can be ignored because of Package Restore

**/[Pp]ackages/*

# except build/, which is used as an MSBuild target.

!**/[Pp]ackages/build/

# Uncomment if necessary however generally it will be regenerated when needed

#!**/[Pp]ackages/repositories.config

# NuGet v3's project.json files produces more ignorable files

*.nuget.props

*.nuget.targets


# Microsoft Azure Build Output

csx/

*.build.csdef


# Microsoft Azure Emulator

ecf/

rcf/


# Windows Store app package directories and files

AppPackages/

BundleArtifacts/

Package.StoreAssociation.xml

_pkginfo.txt

*.appx

*.appxbundle

*.appxupload


# Visual Studio cache files

# files ending in .cache can be ignored

*.[Cc]ache

# but keep track of directories ending in .cache

!?*.[Cc]ache/


# Others

ClientBin/

~$*

*~

*.dbmdl

*.dbproj.schemaview

*.jfm

*.pfx

*.publishsettings

orleans.codegen.cs


# Including strong name files can present a security risk

# (https://github.com/github/gitignore/pull/2483#issue-259490424)

#*.snk


# Since there are multiple workflows, uncomment next line to ignore bower_components

# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)

#bower_components/


# RIA/Silverlight projects

Generated_Code/


# Backup & report files from converting an old project file

# to a newer Visual Studio version. Backup files are not needed,

# because we have git ;-)

_UpgradeReport_Files/

Backup*/

UpgradeLog*.XML

UpgradeLog*.htm

ServiceFabricBackup/

*.rptproj.bak


# SQL Server files

*.mdf

*.ldf

*.ndf


# Business Intelligence projects

*.rdl.data

*.bim.layout

*.bim_*.settings

*.rptproj.rsuser

*- [Bb]ackup.rdl

*- [Bb]ackup ([0-9]).rdl

*- [Bb]ackup ([0-9][0-9]).rdl


# Microsoft Fakes

FakesAssemblies/


# GhostDoc plugin setting file

*.GhostDoc.xml


# Node.js Tools for Visual Studio

.ntvs_analysis.dat

node_modules/


# Visual Studio 6 build log

*.plg


# Visual Studio 6 workspace options file

*.opt


# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)

*.vbw


# Visual Studio 6 auto-generated project file (contains which files were open etc.)

*.vbp


# Visual Studio 6 workspace and project file (working project files containing files to include in project)

*.dsw

*.dsp


# Visual Studio 6 technical files

*.ncb

*.aps


# Visual Studio LightSwitch build output

**/*.HTMLClient/GeneratedArtifacts

**/*.DesktopClient/GeneratedArtifacts

**/*.DesktopClient/ModelManifest.xml

**/*.Server/GeneratedArtifacts

**/*.Server/ModelManifest.xml

_Pvt_Extensions


# Paket dependency manager

.paket/paket.exe

paket-files/


# FAKE - F# Make

.fake/


# CodeRush personal settings

.cr/personal


# Python Tools for Visual Studio (PTVS)

__pycache__/

*.pyc


# Cake - Uncomment if you are using it

# tools/**

# !tools/packages.config


# Tabs Studio

*.tss


# Telerik's JustMock configuration file

*.jmconfig


# BizTalk build output

*.btp.cs

*.btm.cs

*.odx.cs

*.xsd.cs


# OpenCover UI analysis results

OpenCover/


# Azure Stream Analytics local run output

ASALocalRun/


# MSBuild Binary and Structured Log

*.binlog


# NVidia Nsight GPU debugger configuration file

*.nvuser


# MFractors (Xamarin productivity tool) working folder

.mfractor/


# Local History for Visual Studio

.localhistory/


# Visual Studio History (VSHistory) files

.vshistory/


# BeatPulse healthcheck temp database

healthchecksdb


# Backup folder for Package Reference Convert tool in Visual Studio 2017

MigrationBackup/


# Ionide (cross platform F# VS Code tools) working folder

.ionide/


# Fody - auto-generated XML schema

FodyWeavers.xsd


# VS Code files for those working on multiple tools

.vscode/*

!.vscode/settings.json

!.vscode/tasks.json

!.vscode/launch.json

!.vscode/extensions.json

*.code-workspace


# Local History for Visual Studio Code

.history/


# Windows Installer files from build outputs

*.cab

*.msi

*.msix

*.msm

*.msp


# JetBrains Rider

*.sln.iml


##

## Visual studio for Mac

##



# globs

Makefile.in

*.userprefs

*.usertasks

config.make

config.status

aclocal.m4

install-sh

autom4te.cache/

*.tar.gz

tarballs/

test-results/


# Mac bundle stuff

*.dmg

*.app


# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore

# General

.DS_Store

.AppleDouble

.LSOverride


# Icon must end with two \r

Icon



# Thumbnails

._*


# Files that might appear in the root of a volume

.DocumentRevisions-V100

.fseventsd

.Spotlight-V100

.TemporaryItems

.Trashes

.VolumeIcon.icns

.com.apple.timemachine.donotpresent


# Directories potentially created on remote AFP share

.AppleDB

.AppleDesktop

Network Trash Folder

Temporary Items

.apdisk


# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore

# Windows thumbnail cache files

Thumbs.db

ehthumbs.db

ehthumbs_vista.db


# Dump file

*.stackdump


# Folder config file

[Dd]esktop.ini


# Recycle Bin used on file shares

$RECYCLE.BIN/


# Windows Installer files

*.cab

*.msi

*.msix

*.msm

*.msp


# Windows shortcuts

*.lnk


martes, 11 de abril de 2023

CSharpSyntaxWalker

En el post anterior, exploramos diferentes enfoques para separar partes del árbol de sintaxis. Este enfoque funciona bien cuando solo está interesado en partes específicas de la sintaxis (métodos, clases, declaración de lanzamiento, etc.). Es excelente para identificar ciertas partes del árbol de sintaxis para una mayor investigación.

Sin embargo, a veces nos gustaría operar en todos los nodos y tokens dentro de un árbol. Alternativamente, el orden en que visita estos nodos puede ser importante. Quizás esté intentando convertir C# en VB.Net. O tal vez le gustaría analizar un archivo C# y generar un archivo HTML estático con la coloración correcta. Ambos programas requerirían que visitáramos todos los nodos y tokens dentro de un árbol de sintaxis en el orden correcto.

La clase abstracta CSharpSyntaxWalker nos permite construir nuestro propio objeto que recorre la sintaxis y que puede visitar todos los nodos, tokens y trivia. Simplemente podemos heredar de CSharpSyntaxWalker y sobreescribir el método Visit() para visitar todos los nodos dentro del árbol.


public class CustomWalker : CSharpSyntaxWalker

{

    static int Tabs = 0;

    public override void Visit(SyntaxNode node)

    {

        Tabs++;

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + node.Kind());

        base.Visit(node);

        Tabs—;

    }

}


static void Main(string[] args)

{

    var tree = CSharpSyntaxTree.ParseText(@"

        public class MyClass

        {

            public void MyMethod()

            {

            }

            public void MyMethod(int n)

            {

            }

       ");

    

    var walker = new CustomWalker();

    walker.Visit(tree.GetRoot());

}


Este breve ejemplo contiene una implementación de CSharpSyntaxWalker llamada CustomWalker. CustomWalker sobreescribe el método Visit() e imprime el tipo de nodo que se está visitando actualmente. Es importante tener en cuenta que CustomWalker.Visit() también llama al método base.Visit(SyntaxNode). Esto permite que CSharpSyntaxWalker visite todos los nodos secundarios del nodo actual.


La salida para este programa:


Podemos ver claramente los distintos nodos del árbol de sintaxis y su relación entre sí. Hay dos MethodDeclarations hermanos que comparten la misma ClassDeclaration principal.

Este ejemplo anterior solo visita los nodos de un árbol de sintaxis, pero también podemos modificar CustomWalker para visitar tokens y trivias. La clase abstracta CSharpSyntaxWalker tiene un constructor que nos permite especificar la profundidad con la que queremos visitar.

Podemos modificar el ejemplo anterior para imprimir los nodos y sus tokens correspondientes en cada profundidad del árbol de sintaxis.


public class DeeperWalker : CSharpSyntaxWalker

{

    static int Tabs = 0;

    //NOTE: Make sure you invoke the base constructor with 

    //the correct SyntaxWalkerDepth. Otherwise VisitToken() will never get run.

    public DeeperWalker() : base(SyntaxWalkerDepth.Token)

    {

    }

    public override void Visit(SyntaxNode node)

    {

        Tabs++;

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + node.Kind());

        base.Visit(node);

        Tabs—;

    }


    public override void VisitToken(SyntaxToken token)

    {

        var indents = new String('\t', Tabs);

        Console.WriteLine(indents + token);

        base.VisitToken(token);

    }

}


Es importante pasar el argumento SyntaxWalkerDepth adecuado a CSharpSyntaxWalker. De lo contrario, nunca se llama al método VisitToken().


El resultado cuando usamos este CSharpSyntaxWalker:



La muestra anterior y esta comparten el mismo árbol de sintaxis. La salida contiene los mismos nodos de sintaxis, pero agregamos los tokens de sintaxis correspondientes para cada nodo.

En los ejemplos anteriores, visitamos todos los nodos y todos los tokens dentro de un árbol de sintaxis. Sin embargo, a veces solo nos gustaría visitar ciertos nodos, pero en el orden predefinido que proporciona CSharpSyntaxWalker. Afortunadamente, la API nos permite filtrar los nodos que nos gustaría visitar según su sintaxis.

En lugar de visitar todos los nodos como hicimos en ejemplos anteriores, lo siguiente solo visita los nodos ClassDeclarationSyntax y MethodDeclarationSyntax. Es extremadamente simple, simplemente imprime la concatenación del nombre de la clase con el nombre del método.


public class ClassMethodWalker : CSharpSyntaxWalker

{

    string className = String.Empty;

    public override void VisitClassDeclaration(ClassDeclarationSyntax node)

    {

        className = node.Identifier.ToString();

        base.VisitClassDeclaration(node);

    }


    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)

    {

        string methodName = node.Identifier.ToString();

        Console.WriteLine(className + '.' + methodName);

        base.VisitMethodDeclaration(node);

    }

}


static void Main(string[] args)

{

    var tree = CSharpSyntaxTree.ParseText(@"

    public class MyClass

    {

        public void MyMethod()

        {

        }

    }

    public class MyOtherClass

    {

        public void MyMethod(int n)

        {

        }

    }

   ");


    var walker = new ClassMethodWalker();

    walker.Visit(tree.GetRoot());

}


Esta muestra simplemente genera:

MiClase.MiMétodo

MiOtraClase.MiMétodo


CSharpSyntaxWalker actúa como una gran API para analizar árboles de sintaxis. Permite lograr mucho sin recurrir al modelo semántico y forzar una compilación (posiblemente) costosa. Siempre que sea importante inspeccionar los árboles de sintaxis y el orden, CSharpSyntaxWalker suele ser lo que está buscando.


Tendencias 2023 de datos y AI

 

jueves, 6 de abril de 2023

Primeros pasos con Phoenix parte 7

 


Agreguemos un poco de complejidad a nuestra aplicación. Vamos a agregar una nueva página que reconocerá una parte de la URL, la etiquetará como "mensajero" y la pasará a través del controlador a la plantilla para que nuestro mensajero pueda saludar.

Como hicimos la última vez, lo primero que haremos será crear una nueva ruta.

Vamos a reutilizar HelloController creado en el post anterior y agregaremos una nueva acción de mostrar. Agregaremos una línea justo debajo de nuestra última ruta, así:


scope "/", HelloWeb do

  pipe_through :browser

  get "/", PageController, :home

  get "/hello", HelloController, :index

  get "/hello/:messenger", HelloController, :show

end


Usamos la sintaxis :messenger en la ruta. Phoenix tomará cualquier valor que aparezca en esa posición en la URL y lo convertirá en un parámetro. Por ejemplo, si apuntamos el navegador a: http://localhost:4000/hello/Frank, el valor de "messenger" será "Frank".

Las solicitudes a nuestra nueva ruta serán manejadas por la acción show HelloWeb.HelloController. Ya tenemos el controlador en lib/hello_web/controllers/hello_controller.ex, así que todo lo que tenemos que hacer es editar ese controlador y agregarle una acción show. Esta vez, necesitaremos extraer el messenger de los parámetros para que podamos pasarlo a la plantilla. Para hacer eso, agregamos esta función show al controlador:

def show(conn, %{"messenger" => messenger}) do

  render(conn, :show, messenger: messenger)

end


Dentro del cuerpo de la acción show, también pasamos un tercer argumento a la función render, un par clave-valor donde :messenger es la clave, y la variable messenger se pasa como el valor.

Si el cuerpo de la acción necesita acceso al mapa completo de parámetros enlazados a la variable params, además de la variable messenger enlazada, podríamos definir show/2 así:


def show(conn, %{"messenger" => messenger} = params) do

  ...

end


Es bueno recordar que las claves del mapa de parámetros siempre serán cadenas, y que el signo igual no representa una asignación, sino una afirmación de coincidencia de patrón.

Ahora necesitaremos una nueva plantilla. Dado que es para la acción de mostrar de HelloController, irá al directorio lib/hello_web/controllers/hello_html y se llamará show.html.heex. Se parecerá sorprendentemente a nuestra plantilla index.html.heex, excepto que necesitaremos mostrar el nombre de nuestro mensajero.

Para hacer eso, usaremos las etiquetas HEEx especiales para ejecutar expresiones Elixir: <%= %>. Observe que la etiqueta inicial tiene un signo igual como este: <%= . Eso significa que se ejecutará cualquier código de Elixir que se encuentre entre esas etiquetas, y el valor resultante reemplazará la etiqueta en la salida HTML. Si faltara el signo igual, el código aún se ejecutaría, pero el valor no aparecería en la página.

Nuestras plantillas están escritas en HEEx (HTML+EEx). HEEx es un superconjunto de EEx, por lo que comparte la sintaxis <%= %>.


Y así es como debería verse la plantilla:


<section>

  <h2>Hello World, from <%= @messenger %>!</h2>

</section>


Nuestro messenger aparece como @messenger.

Los valores que pasamos a la vista desde el controlador se denominan colectivamente nuestras "asignaciones". Podríamos acceder a nuestro valor de mensajería a través de asignaciones.messenger pero a través de alguna metaprogramación, Phoenix nos brinda la sintaxis @ mucho más limpia para usar en plantillas.

Si vamos a http://localhost:4000/hello/emanuel, debería ver una página similar a esta:

Hello World, from emanuel!