@iamemhn

EM Hernández-Novich

Ask @iamemhn

Sort by:

LatestTop

Previous

Como saber un leng funcional me ayuda si soy un triste desarrollador en un lenguaje OO o aun peor en Java.

El estilo de programación funcional, combinado con un sistema de tipos realmente poderoso, va a cambiar tu manera de enfrentar tanto el diseño como la implantación de los programas, hasta en lenguajes como Java. Incluso si piensas que nunca vas a querer/poder trabajar con lenguajes enteramente funcionales, el cambio de mentalidad te hará progresar.
Es igual que aprender a hablar otro idioma: expande tu mente porque agrega nuevas formas de construir ideas. Hay personas que intentan aprender idiomas tratando de traducir en su mente, o usando equivalencias desde el idioma que conocen. Estos son los que van de «gato» a «cat», y luego quieren ir de «tuqueque» a «you what what» con resultados inesperados. También hay personas que optan por la inmersión total en el nuevo idioma, viéndose obligadas a cambiar su estructura mental de forma abrupta. Hay suficientes estudios que confirman que la segunda forma es mucho más efectiva, sin excluir a los que usan la primera forma y han avanzado lo suficiente para ser «comprensibles».
Si eres del primer tipo de personas, puedes comenzar por aprender Scala, que funciona sobre la máquina virtual de Java. De allí sacarás unas cuántas nociones como para que puedas «defenderte» en el estilo funcional, sin embargo todavía estarás sometido a los grilletes de la JVM, y la creencia de que OO jerárquica es el molde donde hay que meter todas las galletas.
Si eres del último tipo de personas, puedes comenzar por aprender Racket o Haskell. En el primero vas a sentir mucha «libertad», porque el sistema de tipos no te restringe; en el segundo vas a sentir mucha «presión», porque el sistema de tipos no te va a dejar escribir estupideces. Llegará un momento en que dejarás de llamarlo «presión» para llamarlo «razón». Con cualquiera de los dos seguro te vas a sentir extraño al inicio, porque lo que «sabes» (y no lo escribo peyorativamente) no te va a servir de mucho. Sin embargo, el shock será sumamente revelador si tienes la actitud adecuada acerca del aprendizaje.
Te va a servir para darte cuenta que aunque esté muy bien sentirse a gusto con un lenguaje de programación y un estilo de escribir programas, estás dejando de aprovechar maneras de comprender y expresar los programas que son mucho más naturales y notablemente superiores en términos de reusabilidad, que los «patrones» artificiales que existen para poder usar OO efectivamente.
There's no «functional programming» without «fun».

View more

What's youre favourit Disney Movie? And what's youre favourit Song from a Disney Movie? :3

Fantasia 2000. «Rhapsody in Blue», Gershwin.

Como se implementan las soluciones que mencionas en la respuesta a la pregunta ¿Qué opinas de Firefox cambiando por defecto al DNS de Cloudfare?

Related users

How often do you visit skating rink?

I've been to a skating rink once. Learned how to skate on ice. After two hours I was able to complete two full laps around without holding nor falling down. This increased my respect for hockey players thousand fold.

¿Qué opinas de Firefox cambiando por defecto al DNS de Cloudfare?

Terrible.
La resolución de nombres recursiva debe ocurrir lo más cerca posible del dispositivo. DNS sobre HTTPS es una idea terrible desde el punto de vista de operaciones de redes, y de privacidad, pues ahora la resolución depende de un tercero y no tienes ninguna herramienta para determinar si en efecto la consulta siguió el camino que tenía que seguir.
La solución correcta a ese problema es tener tu propio recursivo local, en tu red. Algo tan simple como tenerlo en tu AP WiFi (si sabe poco del tema) o un servidor dedicado en tu red (si sabes un poquito más del tema).
Desafortunadamente se hará muy popular y conseguirá centralizar un servicio que es por naturaleza descentralizado para maximizar su eficiencia y tolerancia a fallas.
Sería igual de terrible que cambiara a los DNS de Google. Seguiría siendo malo incluso si decidieran usar los de OpenDNS, que es un servicio mucho más honesto y práctico que el de Google o Cloudflare.
A mi no me afecta, porque yo llevo mi propio recursivo a todos lados, y es lo que deberia hacer cualquiera que esté preocupado por su privacidad y quiera evitar manipulación de sus consultas.

View more

Liked by: Sizz Lorr

Sabe si APT tendrá un CLI estable en algún momento, o no se planea desarrollar y mantener apt-get para scripts?

Debian apt-get es estable y es lo que deberías usar en scripts. Por ejemplo, es lo que se usa en FAI para instalar cosas.
Hay personas que se «quejan» porque quieren usar apt-get para instalar paquetes que hacen preguntas y no saben cómo. En estos casos lo que hay que hacer es utilizar pre-seeding (man debconf), posiblemente haciendo un paquete virtual que dependa de varios paquetes concretos. Pedir que apt-get haga esas cosas es no comprender completamente cómo funciona APT -- mucha gente piensa que APT «instala» los paquetes y realmente lo que hace es coordinar herramientas de más bajo nivel todavía (como dpkg y debconf).
Alternativas como aptitude o apt están destinadas a usuarios finales (adultos responsables en plenas facultades), agregando suposiciones de intención, entre las cuales está el usarlos desde la línea de comandos interactiva.

View more

Liked by: Walter Vargas

Que herramientas recomiendas para generar debs con caracteristicas peculiares, digamos necesito que se corran scripts despues de instalarse o antes, necesito tocar system.d and so on, como normalmente generas tus deb?

Las que están explicadas en el Debian Policy.
El Debian Policy tiene todos los detalles que necesitas. Léelo completo, incluso las partes que crees que no son relevantes a lo que te interesa hacer -- porque son relevantes, pero todavía no lo sabes.
Construyo mis paquetes Debian usando pbuilder/cowbuilder, al pie de la letra del Debian Policy. Lo que está dentro del directorio `debian` es parte generado por herramientas como dh-make, dh-make-perl o haskell-scripts, y con la colaboración de debhelerp, pero otra parte importante es escrita a mano (porque no puede hacerse de otra forma).
Leer manuales de dh-* y debhelper, sin leer el Debian Policy antes, es una pérdida de tiempo porque son abrumadoramente flexibles e interdependientes. La mayoría de las herramientas y distribuciones derivadas Debian que no les va bien o son francamente mediocres, les pasa por no leer el Debian Policy y meterse a reinvertar la rueda.

View more

Liked by: Marcos Mora

Como se puede implementar una Federated identity con PostgreSQL?

Si quieres usar PostgreSQL para que sea el sustrato de un sistema de FI, siéntate y escríbelo. Porque «implementar» es eso.
Si quieres que PostgreSQL forme parte de una plataforma FI, tienes soporte parcial. Los usuarios de base de datos en PostgreSQL tienen que ser creados (CREATE ROLE), no son tomados implícitamente de ninguna parte, porque sería peligroso. Así mismo, la autorización a los objetos de base de datos se declara internamente (GRANT) y no es posible delegarla al exterior porque, nuevamente, sería peligroso. Una vez que existe el usuario 'foo' en la base de datos y tiene los GRANT necesarios, PostgreSQL si puede verificar sus credenciales en mecanismos externos (GSSAPI, LDAP o PAM), así que sitienes una plataforma FI al menos la clave vas a poder verificarla externamente.
Si quieres seguir ese camino, tendrías que escribir algo que haga la biyección entre el sistema FI externo y los roles de la base de datos. Escribo roles, porque un ROLE puede ser un usuario indiviual, pero también representa una colección de permisos -- spoiler alert: no es trivial escribir un mecanismo generalizado que construya esta biyección, porque las jerarquías organizaciones de identidad y agrupación son demasiado variadas.
Te digo más, el control de acceso a la base de datos se hace a través del archivo pg_hba.conf. No hay ninguna provisión para que ese archivo se lea desde fuentes externas.
Si necesitas tener el mismo conjunto de ROLEs y GRANTs en varias bases de datos que no son parte de un cluster replicado, lo más barato es usar una herramienta de configuración como Puppet para distribuirla (no lo haría), y usar GSSAPI o LDAP para que la clave se verifique externamente.
Desde que recibí la pregunta he estado pensando en algún escenario en el que personalmente quisiera que PostgreSQL estuviera controlado por un FI, pero no lo logro.

View more

Liked by: Marcos Mora

¿El paradigma Orientado a Objetos está dentro del paradigma imperativo o declarativo? Yo lo aprendí como imperativo, pero luego en la vida me dijeron que era "perpendicular" a esos dos paradigmas.

Es imperativo. Un objeto es estado mutable, y encima oculto, que tiene métodos para manipularlo. Los métodos están escritos usando instrucciones imperativas.
instancia.metodo(a,b,c)
no es nada más que
metodo(apuntador a instancia,a,b,c)
Eso es todo -- una ilusión. Un lenguaje orientado a objetos no es más que un lenguaje imperativo que pasa la instancia como un argumento implícito, y que tiene una secuencia de llamada más cara en espacio y tiempo.
Pero sigue siendo imperativo.
Si encima estás usando lenguajes orientados a objetos que fomentan la herencia antes que la composisión, entonces se vuelve aún más imperativo, porque comienzas a tener objetos productores de objetos.
Imperativo con algo de clase, pero no mucha.

¿Está "mal" usar frameworks de JavaScript? ¿Es más ventajoso que el front, por muy desechable que sea, se renderice desde el lado del servidor y no del cliente?

No sé, no hago frontend. El lenguaje me parece malo en cualquier eje en que lo estudio, igual que PHP. Me tiene sin cuidado que sea popular, porque una cosa es ser popular y otra muy diferente es ser el mejor. Así como no usaría PHP directamente, ni con un framework, tampoco usaría JavaScript, ni con un framework. Pero esa es mi posición personal profesional -- por eso no hago frontend.
Ahora bien, yo prefiero generar todo lo que sea generable. Soy del equipo People Programming Programs Producing Programs. Si encuentras una herramienta de muy alto nivel, que te impide escribir idioteces, y luego escupe JavaScript, seguramente es mejor idea.
Piensa que JavaScript es como el lenguaje de máquina. ¿Prefieres programar en eso antes que en un lenguaje de nivel más alto? La mayoría de la gente prefiere un buen compilador que escupe lenguaje de bajo nivel.

View more

Hay alguna practica cuando implementas todo la lógica en la bd, para agrupar las cosas al estilo de los paquetes de java?

Ninguna. Además, sería artificial y contraria al modelo de operación de la base de datos.
En PostgreSQL PL/pgSQL todas las funciones son independientes. Si defines un tipo de datos especial, escribes funciones que usen ese tipo de datos, pero no tienen que estar juntas. De hecho, PL/pgSQL usa sobrecarga paramétrica, así que puedes tener varias funciones con el mismo nombre, pero que se diferencian según los tipos de sus argumentos. Podrías definir las funciones
foo(int)
foo(int,int)
foo(double,int,text)
foo(int,double,text)
foo(email,text)
son todas funciones diferentes e independientes. Si haces
foo(42.0,42,"cuarenta y dos")
se invocará la tercera. Si haces
foo(42.0,42)
tendrás un error de tipos.
El lenguaje no impone ninguna agrupación usando espacios de nombres y alcance. Por otro lado, tratándose de un modelo relacional (relaciones entre conjuntos) lo único natural es que haya funciones libres que operen sobre las relaciones existentes (filas de tablas) o relaciones ad-hoc (n-uplas de argumentos).
Es más, en ocasiones hay funciones que son utilizadas individualmente, y también desde las funciones TRIGGER de diferentes tablas, así que sería una pésima idea pensar que las puedes agrupar bajo un espacio de nombres jerárquico. Nota: no confundir con la herencia entre tablas en PostgreSQL que es una cosa completamente diferente -- y no tiene funciones asociadas a la fuerza.
Hay gente que usa los esquemas (schema) para agrupar funcionalidad, en particular para separar acceso a los objetos («cosas», porque hay gente que lee «objetos» y piensa que uno habla de OOP) en la base de datos. No es obligatorio usarlos, y tampoco es algo que tienes que usar para organizarte. Por ejemplo, si usas PostgREST para hacer un API, usar esquemas para separar los objetos y las funciones ayuda muchísimo, para separar las partes públicas y protegidas *del API*.
El problema con la programación orientada a objetos es que después la quieres ver en todos lados, y eso limita muchísimo la manera de organizar las soluciones. Hay muchas más posibilidades cuando uno deja de pensar en rígidas jerarquías de clases de datos que llevan funciones empegostadas.
En otras bases de datos que tienen procedmientos almacenados, la cosa puede variar. Muchas de ellas usan Java para escribir procedimientos almacenados. Otras usan lenguajes con conceptos similares de agrupamiento de procedimientos incluyendo (asco) variables globales al espacio de nombres. Hace quince años que abandoné ese camino de amargura.
Mucha gente quiere traducir su lógica escrita en $LENGUAJE_CON_PEROLES al frente a PL/pgSQL. Esa no es una buena idea. Es mejor traducir la lógica a SQL -- SQL es Turing Completo -- si usas vistas, CTEs, window functions y UPSERTS (RTFM) no necesitas escribir ciclos, ni variables: todas tus funciones son una construcción SQL -- compleja, si, como todo lo poderoso.

View more

Liked by: Marcos Mora

En lenguajes como haskell ¿Qué tanto debería hacer unittest? ¿Qué técnicas puedo utilizar para mocks de efectos de IO? Como requests http, consultas a la DB. Lo que se me ocurre es tener un reader con las funciones a "mockear", y hacer algo parecido a dependency injection.

En Haskell es mucho más efectivo hacer pruebas de propiedades (Property Testing) con una librería como QuickCheck o SmallCheck. Prefiero la primera. Escribes invariantes que expresen la correctitud del código, e.g.
prop_assoc x y z = (x + y) + z == z + (y + z)
y QuickCheck te ayuda a generar los ejemplos de prueba y reportar si encontró algún contraejemplo.
QuickCheck también sirve para verificar código con efectos prácticamente en cualquier Monad. Para eso, tienes que emplear un generador de secuencias de acciones que tengan sentido para lo que quieres probar, y un interpretador de esas acciones instrumentado con precondiciones y aserciones.
Imagina que quieres probar una pila que funciona en ST o IO. Tienes que diseñar un tipo para expresar operaciones (NewStack, Push, Pop, Top, lo que necesites). Necesitas escribir un generador (Arbitrary) de secuencias de operaciones que tengan sentido. Luego escribes un interpretador de listas de operaciones, que use el API de tu Monad, pero antes/después de cada operación verifique precondiciones y postcondiciones. Entonces le pides a QuickCheck que genere secuencias de operaciones, las simule, y compruebe las aserciones. Revisa QuickCheck.Monadic.
Esta técnica debe funcionar muy bien usando Free Monads. No tengo nada hecho como para comparar.
Hay un transformer monad-mock, pero no lo he usado porque cuando lo vi me pareció que no mejoraba mi vida más que hacer mi interpretador. Al final tienes que escribir todo usando typeclasses que generalicen el monad subyacente, para poder reemplazarlo después con el que hace mock -- me parece impráctico en muchos aspectos.

View more

Liked by: Marcos Mora

Cual es tu criterio de cuanto va en configuration files y cuanto iria en variables de entorno a hacer cargados al programa? De usar configuration files que sintaxis le gusta?

Para programas que se van a correr desde la línea de comandos, y eventualmente desde cron, me parece más flexible que haya valores por omisión, que pueden ser redefinidos con un archivo de configuración, y finalmente por variables de ambiente. De ese modo puedo tener un archivo central con las «opciones comunes» y para cada llamada ajusto con variables de ambiente.
Para programas que corren por mucho tiempo (servidores dedicados, backends HTTP) prefiero un archivo de configuración, y que el programa pueda volver a leerlo y reconfigurarse dinámicamente.
El formato, mientras más simple, mejor. El formato propuesto `configurator` de Haskell o YAML, me parecen más que suficientes. Librerías como Config::Any de Perl hacen que realmente no te importe el formato porque los lee todos -- usar JSON o XML me parece inapropiado, y usar el mismo lenguaje como archivo de configuración me parece peligroso.
Espero la oportunidad de refactorizar algunas cosas a Dhall, porque parece tener un balance razonable.

View more

Liked by: Marcos Mora Rui

Necesito correlacionar información de una docena de logs de diferentes fuentes y diferentes formatos de mensaje incluso dentro de cada log. Pienso subir todo a una base de datos PostgreSQL en un montón de tablas y luego escribir código en PERL. Alguna sugerencia diferente? Alguna aplicación mejor?

Esa es una manera de hacerlo que funciona, sobre todo si la agregación es para análisis post-mortem, por ejemplo para calcular indicadores de operaciones técnicas o comerciales. Puedes escribir la extracción, transformación, y carga (ETL), usando Perl, y luego haces el análisis con SQL.
Sin embargo, si lo que quieres es detección de anomalías lo más rápido posible, ese método no te va a servir porque la fase ETL toma mucho tiempo. En ese caso es preferible enviar todos los mensajes, de todos los origenes, a un único servicio de logs via syslog, y poner una herramienta como `logwatch`, `sagan`, o incluso `sentry`.
Liked by: Marcos Mora

Cual es la diferencia entre multithread y multiprocess y cuando es conveninente una sobre la otra?

Multithread ocurre cuando tienes sólo un proceso del sistema operativo, dentro del cual hay varios hilos de ejecución. Multiprocess ocurre cuando tienes varios procesos del sistema operativo, cada uno de los cuales tiene uno o más hilos de ejecución.
En ambos escenarios necesitas un mecanismo para crear nuevos hilos de ejecución, mecanismos para que esos hilos se comuniquen, y un scheduler que sirva de árbitro para lograr la ejecución simultánea real (muchos núcleos) o simulada (un sólo núcleo).
En el escenario multithread, los nuevos hilos deben ser creados a través de una primitiva propia del lenguaje (como forkIO en Haskell o threads->create() de Perl), los mecanismos para comunicarse se basan en el espacio de memoria común (variables compartidas, semáforos, colas de mensajes, memoria transaccional), y el ambiente de ejecución del lenguaje provee un scheduler independiente del sistema operativo.
En el modelo multiproceso, un proceso existente crea nuevas réplicas usando al sistema operativo (fork() en Unix), los mecanismos de comunicación dependen todos del sistema operativo (pipes, sockets, memoria compartida), y es el kernel el que decide cuál proceso recibirá recursos de cómputo.
El modelo multiproceso es bastante más caro en recursos, porque la llamada fork() es notablemente costosa, y cualquiera de los mecanismos IPC antes mencionados suelen requerir al menos dos copias desde espacio usuario hacia espacio kernel. Es el modelo clásico para procesos que «correrán por mucho tiempo» y que son generalmente independientes entre si. Es relativamente sencillo programarlos, porque la semántica de fork(), exec*(), pipe(), socket(), shm*() y el comportamiento del kernel Unix es muy predecible.
El modelo multihilo es bastante más económico, porque crear un nuevo hilo dentro de un proceso que ya existe requiere mucho menos tiempo y recursos. Los mecanismos de comunicación también son más económicos porque sólo requieren copiar valores dentro del mismo proceso. No obstante, programar sistemas usando este modelo es más complicado, sobre todo si usas un lenguaje en el cual no hay mecanismos sofisticados para el manejo de hilos y su comunicación: lenguajes como Perl, Python y Java tienen hilos, pero sus implantaciones son simplistas y sumamente complejas de aprovechar, sobre todo si se compara con lenguajes como Haskell, Erlang, Golang, o Rust.
Escoger uno o el otro forma parte del análisis del problema particular, y no es algo que puedo contestar de manera general. Para eso están los textos de Programación Concurrente y Paralela.
Nota que existe una tercera opción, que es utilizar una libreria externa (MPI o PVM desde C/C++, por ejemplo) para que un lenguaje que no tiene hilos «parezca» que los tiene. En este caso, lo que hay es un proceso controlando a otros procesos, con la ilusión de que estás usando hilos, posiblemente distribuidos sobre máquinas diferentes, de manera bastante transparente, pero aún dolorosa.

View more

Liked by: Marcos Mora

¿Qué librerías de logging utilizas/recomiendas para Haskell?

Hasta ahora me basta con Control.Monad.Logger y Control.Monad.Logger.Syslog
Liked by: Marcos Mora

he leido el fabuloso manual y sigue ejecutando los contraints antes que los triggers, incluso con el BEFORE, que tan mal puedo estar?

create table t ( s text check (length(s) < 4) );
create function u() returns trigger as
$$
begin
new.s := upper(new.s);
return NEW;
end;
$$ LANGUAGE plpgsql;
create trigger b_u
before insert or update on t
for each row execute procedure u();
insert into t ( s ) values ( 'try' );
insert into t ( s ) values ( 'keep reading' );
update t set s = 'and try again' where s = 'TRY';
La tabla incluye restricción de longitud, mientras el trigger quiere mantener el campo en mayúsculas antes (BEFORE) de insertar o modificar. Corre esto en una base de datos vacía y los mensajes de error son evidencia que las restricciones de longitud se aplicaron después de haber ejecutado el trigger.
¯\_(ツ)_/¯
+1 answer in: “por què se ejecuta los constraints de longitud antes que los triggers previamente asignado a una tabla cuando se insertan datos en la db?”

Algún LDAP que recomiendes?

OpenLDAP.
Hoy en día administro con las herramientas de línea de comando, ldapvi, y el ocasional script Perl para transformaciones o carga masiva. En algún momento usé phpldapadmin, pero hoy en día me molesta más que lo que me ayuda.
Hace años usé uno de esos «ambientes para LDAP» (sic) que están pensados para administrar organizaciones. El problema es que la estructura (¡y extensiones!) del DIT que proponen es exactamente para lo que ellos ofrecen, y es difícil integrar aplicaciones que ellos no tomaron el cuenta. Por eso lo desplegué en algunos clientes, pero no lo usaría para mi. En esta liga están GoSA y Zimbra -- a los que les funciona sin cambios, son felices.

por què se ejecuta los constraints de longitud antes que los triggers previamente asignado a una tabla cuando se insertan datos en la db?

Leyendo el segundo párrafo del fabuloso manual
«The trigger can be specified to fire before the operation is attempted on a row (before constraints are checked and the INSERT, UPDATE, or DELETE is attempted); or after the operation has completed (after constraints are checked and the INSERT, UPDATE, or DELETE has completed);»
lo más probable es que sea un error Capa 8: el trigger fue creado para AFTER y debería ser BEFORE.
Liked by: Francisco
+1 answer Read more

¿Hay formas de escalar horizontalmente una base de datos PostgreSQL? Se que existe Citus, de lo poco que se, se basa en sharding de los datos y un nodo coordinador que se encarga de ejecutar la consulta sobre el cluster. ¿Qué recomiendas, o qué has hecho, cuando alcanzas los límites de escritura?

No he llegado a los límites de escritura, gracias a particionamiento explícito y múltiples discos SSD, y más recientemente NVMe. Es muy práctico mejorar la capacidad de escritura simplemente teniendo varios discos pequeños y rápidos, que un disco grande.
En todo caso, si tuviera que distribuir carga sobre servidores diferentes aprovecharía PL/Proxy. Otra razón por la cual es ventajoso escribir la aplicación enteramente dentro de la base de datos.
Liked by: Marcos Mora
+1 answer in: “PostgreSQL to shard or not to shard? ¿Has encontrado casos donde tuviste que hacerlo? ¿Qué otras vías deberíamos agotar antes de pensar en sharding?”

Qué opinión tiene sobre el lenguaje Scratch?

Me parece que está muy bien para niños pre-adolescentes, siempre y cuando la persona que lo presenta tenga la actitud adecuada. Es muy fácil hacer ver que es un «juguete» o que sólo sirve para atacar problemas lúdicos.
Conducido adecuadamente sirve para llevar a los niños a interesarse en formas más eficientes de expresar las mismas soluciones.
Te digo más, Snap! es superior a Scratch, sólo porque Snap! incluye todas las cosas que Scheme tiene que ofrecer.
Liked by: Marcos Mora

Que lenguaje recomiendas para una actividad padre-hijo adolescente de desarrollar un videojuego en Linux? Pensaba algo como Pac-Man y algún día un clone de Zelda. Agradezco tus consejos. Me interesa fomentar su interés por la ciencia.

No desarrollo videojuegos, de modo que no puedo recomendarte nada. Quizás podrías preguntarle (en Twitter) a @chiguire, @esaulgd, @cris7ian, @cchomiakm, @pctroll, @jennydvr1 o @lavz24.
Ahora bien, antes de correr, primero tienes que caminar. El científico sabe que no hay caminos cortos hasta los resultados, así que parte de fomentar interés en la ciencia es admitir que toma tiempo, práctica, paciencia, y constancia. Eso va a ser lo más difícil de aceptar para un adolescente.
El libro «Problem Solving with LOGO» me parece muy útil, y si por cualquier razón decides no usar LOGO, podría ser interesante adaptarlo a lenguajes como Scratch o cualquier otro que tenga la metáfora de «turtle drawing». A primera vista parece infantil -- quizás alguno de los que mencioné se inspira y lo adapta a herramientas diferentes.
Si no le interesa la programación, déjalo hacer lo que le interese. Hay muchas maneras de hacer ciencia.

View more

Next

Language: English