Una de las características que quería tener desde el principio en mi intérprete AjSharp era que las funciones y rutinas sean ciudadanos de primera clase en el lenguaje (en este post uso la palabra clave function pero se puede usar también sub para definir una subrutina).
El código está en el proyecto de código de Google:
http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjLanguage
AjLanguage es el lenguaje núcleo (que no tiene ni parser ni lexer, sólo lo esencial del intérprete). AjSharp es el parser y el lexer implementado sobre el AjLanguage. De esta forma, podría implementar AjBasic u otros lenguajes similares, usando AjLanguage como base y núcleo.
En AjSharp, podemos definir funciones como en otros lenguajes:
function Square(n) { return n*n; }
Podemos definir una función anónimca y asignarla a una variable:
MySquare = function (n) { return n*n; };
Un functional value puede ser usado como parámetro:
function Apply(func,values) { list = new List(); foreach (value in values) list.Add(func(value)); return list; } numbers = new List(); numbers.Add(1); numbers.Add(2); numbers.Add(3); function Square(n) { return n*n; } squared = Apply(Square, numbers); squared2 = Apply(function (n) { return n*n; }, numbers);La función Apply definida arriba, toma una función, una lista, y retorna otra lista de elementos que resultan de evaluar f(e), donde f es la función recibida, aplicada a cada elemento e de la lista que se pasó como parámetro. Podemos invocar Apply con el nombre de la función (que en realidad es el nombre de la variable que contiene al functional value) o definidiendo la función directamente en la invocación.
Estoy pensando sobre algunas alternativas de implementación del alcance y visibilidad de variables en expresiones funcionales (dará para otro post el tema). Si definimos una función, AjLanguaje (el núcleo de AjSharp) usa una closure (clausura), así que cuando la función es invocada, el binding environment original es usado. En los ejemplos de más abajo se usa esa característica.
Cada función implementa:
public interface ICallable { int Arity { get; } IBindingEnvironment Environment { get; } object Invoke(IBindingEnvironment environment, object[] arguments); object Invoke(object[] arguments); }esto es, cuando es invocada, recibe un binding environment (un diccionario que dado el nombre de una variable nos da su valor asociado). Un funcional value no usa ese binding environment recibido, y lo reemplaza por el environment original que tenía en el momento de haber sido definida, creada.
Podemos retornar una función como retorno de una función:
function MakeIncrement(x) { return function(n) { return n + x; }; // x is bounded to local parameter } Increment2 = MakeIncrement(2); result = Increment2(2); // result == 4 Increment3 = MakeIncrement(3); result2 = Increment3(2); // result2 == 5 result3 = MakeIncrement(4)(3); // result3 == 7 x = 4; result4 = function(n) { return n+x; }(5); // result4 == 9MakeIncrement retorna una función que usa y accede a la variable x, ligada en el binding environment de llamada.
En el último comando, la función es definida e invocada en el mismo comando.
AjSharp tiene clases, y podemos definir funciones y rutinas como métodos:
class Person { var Name; var Age; function AddYears(years) { this.Age = this.Age + years; } }Pero también podemos agregar funciones en cualquier momento:
class Person { var Name; var Age; function Person() { x = 100; this.GetYears = function () { return this.Age; }; this.GetX = function() { return x; }; } } adam = new Person() { Name = "Adam", Age = 800 }; result = adam.GetYears(); // result == 800 result2 = adam.GetX(); // result2 == 100o agregar funciones/rutinas directamente a objetos:
adam.AddYears = sub(n) { this.Age = this.Age + n; };Hace unas semanas, leí el post:
Functional Programming in Javascript
de James Carr, así que decidí probar las capacidades de AjSharp para manejar functional values adaptando algunos de esos ejemplos, como:
function runningSum(start){ sum = start; // you could use var sum, it's local return function(a){ sum = sum + a; // function access the "outer" sum return sum; }; } sum = runningSum(3); // makes function result = sum(2); // returns 5 result2 = sum(10); // returns 15Podemos usar directamente el parámetro como acumulador:
function runningSum(start){ return function(a){ start = start + a; // function access the "outer" start return start; }; } sum = runningSum(3); // makes function result = sum(2); // returns 5 result2 = sum(10); // returns 15Agregué soporto para aplicar una función como si fuera un método a un objeto, como en uno de los ejemplos de Carr (este tipo de llamada es soportada en Javascript que tiene la clase Function):
person = new { Name = "Adam", Age = 800 }; GetAdjustedAge = function (x) { return x + this.Age; }; result = GetAdjustedAge.Call(person, 10); // result == 810Próximos pasos: estabilizar el alcance de las varaibles y el acceso al environment global. Por ahora, me divertí bastante implementando valores funcionales
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com