Skip to main content

Hi, I'm Mariano Guerra, below is my blog, if you want to learn more about me and what I do check a summary here: marianoguerra.github.io or find me on twitter @warianoguerra or Mastodon @marianoguerra@hachyderm.io

object oriented erlang

el titulo suena sacrilegioso pero es algo asi.

ultimamente estaba pensando como agregarle una construccion mas poderosa que los "records" de erlang a mi lenguaje (http://marianoguerra.com.ar/efene) y me parece que llegue a una version que me cierra:

-module(person).
-compile(export_all).

-record(person, {firstname, lastname, mail}).

person(Firstname, Lastname, Mail) ->
Person = #person{firstname=Firstname, lastname=Lastname, mail=Mail},
wrapper(Person).

wrapper(Person) ->
fun(getfirstname) -> Person#person.firstname;
(getlastname) -> Person#person.lastname;
(getmail) -> Person#person.mail;
({has, Field}) -> lists:member(Field, [firstname, lastname, mail]);
({setfirstname, Value}) -> wrapper(Person#person{firstname=Value});
({setlastname, Value}) -> wrapper(Person#person{lastname=Value});
({setmail, Value}) -> wrapper(Person#person{mail=Value});
(string) -> io_lib:format("person(~p ~p ~p)",
[Person#person.firstname, Person#person.lastname,
Person#person.mail]);
(record) -> Person;
(fields) -> {firstname, lastname, mail};
(name) -> person end.

test() ->
% helper function
Print = fun(X) -> io:format("~p~n", [X]) end,
% create an "object"
P = person("mariano", "guerra", "mail"),
% get firstname
Print(P(getfirstname)),
% get lastname
Print(P(getlastname)),
% get mail
Print(P(getmail)),
% return the "object" as string
Print(P(string)),
% get the "object" as an erlang record
Print(P(record)),
% get the fields of the "object"
Print(P(fields)),
% get the name of the "object"
Print(P(name)),

% check if the "object" has an attr called firstname
Print(P({has, firstname})),
% check if the "object" has an attr called address
Print(P({has, address})),

% create a new "object" changing the firstname attribute
P1 = P({setfirstname, "Mariano"}),
% print the new "object"
Print(P1(string)).


la idea es generar la function person (y poner wrapper adentro) automaticamente en base a una expresion como

person = record(firstname lastname mail)

la cual genera la descripcion del record y la funcion para construir el "objeto"

esto agrega cosas dinamicas que los records no tienen, ya que son transformados a tuplas en tiempo de compilacion y toda la informacion del mismo se pierde, por ejemplo con un record en erlang no se puede saber sus campos, no se puede saber su nombre, no se puede manipular sin incluir el .hrl que lo define, no se puede saber si tiene un atributo y muchas cosas mas como tener que especificar el tipo de record del que estoy hablando en cada expresion (mirar el codigo de wrapper para darse una idea).

basicamente le agregue duck typing a los records en erlang :P

se podria tener una lista de estos "objetos" que en realidad son funciones con closures adentro, y checkear si tienen un atributo y hacer algo con ellos, cosa que no se podria hacer con los records de erlang.

en unas semanas cuando ande con tiempo lo agrego a mi lenguaje y ya casi estaria cerrado para una 0.1 :D

la sintaxis en efene va a quedar algo asi (reimplementando la function test)

person = record(firtname lastname mail)

main = fn () {
P = person("mariano" "guerra" "mail")
# helper function
Print = fn (X) { io.format("~p~n", [X]) }
# create an "object"
P = person("mariano" "guerra" "mail")
# get firstname
Print(P(getfirstname))
# get lastname
Print(P(getlastname))
# get mail
Print(P(getmail))
# return the "object" as string
Print(P(string))
# get the "object" as an erlang record
Print(P(record))
# get the fields of the "object"
Print(P(fields))
# get the name of the "object"
Print(P(name))

# check if the "object" has an attr called firstname
Print(P((has, firstname)))
# check if the "object" has an attr called address
Print(P((has, address)))

# create a new "object" changing the firstname attribute
P1 = P((setfirstname, "Mariano"))
# print the new "object"
Print(P1(string))
}


los parentesis extras en los set* y el has son necesarios porque es una unica function que recibe un solo argumento (se debe mantener la aridad en todos los pattern matching de una misma function) y preferi que los get y los "metodos" sean simples y complicar los sets.

se aceptan criticas