{"id":1264,"date":"2023-08-03T22:00:43","date_gmt":"2023-08-03T20:00:43","guid":{"rendered":"https:\/\/intsight.com\/?p=1264"},"modified":"2023-10-12T17:31:02","modified_gmt":"2023-10-12T15:31:02","slug":"__trashed","status":"publish","type":"post","link":"https:\/\/intsight.com\/index.php\/2023\/08\/03\/__trashed\/","title":{"rendered":"Safe indexers"},"content":{"rendered":"<p><span style=\"font-variant:small-caps; font-size:107%\">El lenguaje de<\/span> AUSTRA es un sencillo lenguaje de f\u00f3rmulas, inspirado mayormente en la Programaci\u00f3n Funcional. Esto lo hace f\u00e1cil de usar, y sobre todo, lo hace bastante seguro: no nos deja estropear los datos de una serie que hemos obtenido, por ejemplo, desde una fuente de pago. Al mismo tiempo, nos obliga a ser \u00abcreativos\u00bb para resolver problemas que ser\u00edan m\u00e1s sencillos en un lenguaje tradicional.<\/p>\n<p>Por ejemplo, digamos que quiero crear un vector de 1024 elementos, con la serie de n\u00fameros cuadrados. Eso es sencillo, si usamos el constructor (o \u00abm\u00e9todo de clase\u00bb) apropiado. En el lenguaje de AUSTRA, se hace as\u00ed:<\/p>\n<pre class=\"EnlighterJSRAW\">\nvector::new(1024, i => (i + 1)^2)\n<\/pre>\n<p>AUSTRA soporta constructores con nombres: a diferencia de C#, en los que todos los constructores se definen con el nombre de la clase, AUSTRA nos permite distinguir entre constructores por medio de sus nombres. Por ejemplo, estas son maneras alternativas de construir una matriz:<\/p>\n<pre class=\"EnlighterJSRAW\">\n-- Una matriz de 10x10, con una funci\u00f3n lambda para las celdas.\nmatrix::new(10, 10, (fila, columna) => fila * 10 + columna)\n-- Una matriz de 2x4, a la que le pasamos dos vectores.\nmatrix::rows([1, 2, 3, 4], [5, 6, 7, 8])\n-- Una matriz de covarianza de cuatro series temporales.\nmatrix::cov(aapl, msft, dax, esx)\n<\/pre>\n<p>En un lenguaje como el de MATLAB, casi seguramente, tendr\u00edamos tres funciones globales para conseguir lo mismo. Con el truco de AUSTRA, evitamos identificadores globales, que terminan colisionando entre ellos y teniendo que adoptar nombres cr\u00edpticos. Nos evitamos tambi\u00e9n las c\u00e1balas que hay que hacer en lenguajes como C# o Java para averiguar cu\u00e1l es el constructor adecuado de acuerdo a los par\u00e1metros que estamos pasando. En el fondo, un constructor de AUSTRA termina llamando indistintamente a un constructor de C# o a un m\u00e9todo est\u00e1tico. Lo importante es que el lenguaje nos abstrae de estos detalles de implementaci\u00f3n.<\/p>\n<p>Volviendo al ejemplo del vector, hemos utilizado un constructor que recibe el tama\u00f1o deseado, y una funci\u00f3n lambda que devuelve los valores de cada celda. Digamos ahora, para complicarlo, que lo que queremos es la secuencia de Fibonacci. La soluci\u00f3n m\u00e1s sencilla que se me ha ocurrido es permitir que la funci\u00f3n lambda pueda tener un par\u00e1metro adicional que apunte al propio vector que estamos construyendo. Este ejemplo sigue siendo id\u00e9ntico al anterior, pero ya estamos pasando un par\u00e1metro adicional en la funci\u00f3n lambda, aunque no lo utilicemos de momento:<\/p>\n<pre class=\"EnlighterJSRAW\">\nvector::new(1024, (i, v) => (i + 1)^2)\n<\/pre>\n<p>\u00bfEs esto limpio y seguro? Por supuesto. El par\u00e1metro <code>v<\/code> nunca va a ser nulo, y de hecho, ya nos llega medio cocido: la primera vez que se llama la funci\u00f3n lambda, todos sus elementos est\u00e1n a cero. La siguiente vez, estar\u00e1 asignado el primero elemento. Y as\u00ed sucesivamente. Tenemos un contrato que nos garantiza el orden en que se van a inicializar los elementos del vector. Tenga presente que una posible implementaci\u00f3n, para un vector suficientemente grande, podr\u00eda usar paralelismo para rellenar segmentos concurrentemente. Pero cuando usamos esta variante del constructor, tenemos la garant\u00eda de que la inicializaci\u00f3n va a ser secuencial.<\/p>\n<p>Ahora veamos una primera versi\u00f3n de Fibonacci:<\/p>\n<pre class=\"EnlighterJSRAW\">\nvector::new(1024, (i, v) =>\n  if i <= 1 then 1 else v[i-1] + v[i-2])\n<\/pre>\n<p>Esto funciona perfectamente. El <code>if<\/code> de marras no es una instrucci\u00f3n: es una expresi\u00f3n condicional ternaria, como la interrogaci\u00f3n y los dos puntos en C\/C# y familia. En el caso m\u00e1s habitual, sumamos los dos elementos anteriores, que ya estar\u00e1n inicializados. Si nos diese por hacer referencia a un elemento posterior, no ser\u00eda un problema, excepto que su valor ser\u00eda cero. Y la expresi\u00f3n condicional nos ahorra una excepci\u00f3n de acceso fuera de rango.<\/p>\n<p>AUSTRA tiene un mecanismo m\u00e1s potente para estos casos, sin embargo:<\/p>\n<pre class=\"EnlighterJSRAW\">\nvector::new(1024, (i, v) =>\n  if i = 0 then 1 else v{i-1} + v{i-2})\n<\/pre>\n<p>Esta vez, tenemos un solo caso especial: el del primer elemento del vector. El cambio est\u00e1 en la forma en que accedemos a los elementos de <code>v<\/code>: con llaves, en vez de corchetes. Esto es lo que he llamado un <em>safe indexer<\/em>, o indexador seguro. Si el valor del \u00edndice est\u00e1 en rango, todo procede como de costumbre. En caso contrario, la expresi\u00f3n devuelve un cero. Es como si tuvi\u00e9semos una memoria infinita, en la que una regi\u00f3n de la misma puede tener valores distintos de cero, pero fuera de esa regi\u00f3n, todo es cero. Como se trata de un lenguaje funcional, adem\u00e1s, no existe la posibilidad de escribir en la regi\u00f3n \"externa\" de la memoria del vector: no podemos hacer asignaciones ni a <code>v[0]<\/code> ni a <code>v{v.length}<\/code>, por ejemplo, aunque la primera expresi\u00f3n se refiera a un elemento que realmente existe.<\/p>\n<h4>Otras aplicaciones<\/h4>\n<p>En <a href=\"https:\/\/en.wikipedia.org\/wiki\/Econometrics\" rel=\"noopener\" target=\"_blank\">Econometr\u00eda<\/a>, se conoce como serie temporal <em>autoregresiva<\/em> de orden <em>p<\/em> a la que se construye de acuerdo a esta f\u00f3rmula:<br \/>\n$$X_t = \\sum_{i=1}^{p}{\\phi_i X_{t - i}} + \\epsilon_t$$<br \/>\nEl s\u00edmbolo $\\epsilon_t$ se refiere a variables aleatorias con una distribuci\u00f3n normal est\u00e1ndar. Los coeficientes $\\phi_i$ son n\u00fameros reales que definen el comportamiento de la serie. Se trata, por supuesto, de una serie que combina ruido blanco con una retroalimentaci\u00f3n limitado de valores anteriores. Por ejemplo, en <a href=\"https:\/\/en.wikipedia.org\/wiki\/High-frequency_trading\" rel=\"noopener\" target=\"_blank\"><em>high-frequency trading<\/em><\/a>, los precios de ejecuci\u00f3n de un activo suelen poderse representar con una serie autoregresiva, en muchos casos.<\/p>\n<p>Este es el caso de uso que me ha obligado a implementar estos indexadores seguros. La forma de construir una serie autoregresiva en AUSTRA es la siguiente:<\/p>\n<pre class=\"EnlighterJSRAW\">\nlet r=vector::nrandom(1024) in\n    vector::new(r.length, (i, v) =>\n        r[i] + 0.7*v{i-1} + 0.1*v{i-2})\n<\/pre>\n<p>Primero construimos un vector aleatorio usando el constructor <code>vector::nrandom<\/code>. La cl\u00e1usula <code>let<\/code> nos permite crear una variable local a la f\u00f3rmula, a la que podemos hacer referencia en lo que queda de f\u00f3rmula. El resto es f\u00e1cil de adivinar: no hace falta un indexador seguro para acceder a <code>r<\/code>, pero s\u00ed a los elementos anteriores del vector que estamos construyendo.<\/p>\n<h4>Implementaci\u00f3n en C#<\/h4>\n<p>La implementaci\u00f3n del indexador seguro en C# puede resultar interesante, por las t\u00e9cnicas de \"bajo nivel\" que utiliza. Esta es la funci\u00f3n de la clase <code>Vector<\/code> que implementa dicha funcionalidad:<\/p>\n<pre class=\"EnlighterJSRAW\">\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\npublic double SafeThis(int index) =>\n    (uint)index >= values.Length\n    ? 0.0\n    : Unsafe.Add(\n        ref MemoryMarshal.GetArrayDataReference(values),\n        index);\n<\/pre>\n<p><code>Vector<\/code> es, en AUSTRA, una estructura muy ligera que se limita a encapsular un campo <code>values<\/code> de tipo <code>double[]<\/code>. La implementaci\u00f3n podr\u00eda ser una consulta de rango, seguida de un acceso \"normal\" a los datos del vector:<\/p>\n<pre class=\"EnlighterJSRAW\">\npublic double SafeThis(int index) =>\n    index >= 0 && index < values.Length ? values[index] : 0.0;\n<\/pre>\n<p>Pero AUSTRA es una librer\u00eda que se va a utilizar en cosas que a priori no te puedes imaginar, y en estos casos, optimizar bien el c\u00f3digo no puede calificarse de \"optimizaci\u00f3n prematura\".<\/p>\n<p>En este caso, he usado varios trucos, que son bastante usados por el propio c\u00f3digo de <a href=\"https:\/\/source.dot.net\" rel=\"noopener\" target=\"_blank\">.NET Core<\/a>:<\/p>\n<ul>\n<li>Primero, est\u00e1 el truco de convertir el \u00edndice en un entero sin signo, para ahorrarnos una comparaci\u00f3n y un salto. Si nos pasan un \u00edndice negativo, el valor reconsiderado como entero sin signo va a ser inevitablemente mayor que la longitud del <em>array<\/em>.<\/li>\n<li>Luego est\u00e1 el truco indescriptiblemente sucio de usar <em>MemoryMarshal.GetArrayDataReference<\/em> para obtener la direcci\u00f3n de memoria donde comienza el <em>array<\/em>. Este truco s\u00f3lo puede fallar si <em>value<\/em> fuese un puntero nulo, pero no es el caso.<\/li>\n<li>Finalmente, el m\u00e9todo <em>Unsafe.Add<\/em> suma el \u00edndice a esa posici\u00f3n inicial y devuelve el valor del elemento. Este es un truco m\u00e1s tolerable.<\/li>\n<\/ul>\n<p>Al final, el compilador y el JIT generan m\u00e1s o menos el mismo c\u00f3digo que para un acceso normal \"verificado\", pero con la particularidad de devolver cero si el \u00edndice est\u00e1 fuera de rango, que es lo que quer\u00edamos. No se a\u00f1ade c\u00f3digo innecesario.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>El lenguaje de AUSTRA es un sencillo lenguaje de f\u00f3rmulas, inspirado mayormente en la Programaci\u00f3n Funcional. Esto lo hace f\u00e1cil de usar, y sobre todo, lo hace bastante seguro: no nos deja estropear los datos de una serie que hemos obtenido, por ejemplo, desde una fuente de pago. Al mismo tiempo, nos obliga a ser [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1263,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[73],"class_list":["post-1264","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c","tag-austra"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/intsight.com\/wp-content\/uploads\/2023\/07\/river.png?fit=450%2C450&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1264","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/comments?post=1264"}],"version-history":[{"count":32,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1264\/revisions"}],"predecessor-version":[{"id":1542,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1264\/revisions\/1542"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/media\/1263"}],"wp:attachment":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/media?parent=1264"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/categories?post=1264"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/tags?post=1264"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}