Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Metaprogramming in Erlang

Metaprogramming in Erlang

Talk given at the Erlang User Group in Stockholm.

Enrique Fernández

March 18, 2015
Tweet

More Decks by Enrique Fernández

Other Decks in Technology

Transcript

  1. © 1999-2015 Erlang Solutions Ltd. • Enrique Fernández - MSc.

    Computer Science, URV, Spain - Working with Erlang since 2011 - AST (URV), Spain (~2.5 years) - Ericsson Research, Sweden (~1.5 years) - Erlang Solutions, Sweden (2+ years) - Long time “black magic”1 enthusiast
 • Contact: [email protected] Speaker 2 1 Enabling Collaboration Transparency with Computational Reflection. CRIWG 2010.
 P.García, E.Fernández-Casado, C.Angles & M.Ferre.
  2. © 1999-2015 Erlang Solutions Ltd. Metaprogramming 3 • Ability to

    treat programs as data. • Ability to write programs that read, analyse and/ or transform other programs.
  3. © 1999-2015 Erlang Solutions Ltd. Metaprogramming (Ruby example) class  Person

         def  initialize(name)          @name  =  name      end   end   jimmy  =  Person.new(“Jimmy")   #  =>  #<Person:...  @name="Jimmy">   jimmy.class   #  =>  Person   jimmy.name   #  =>  NoMethodError:  undefined  method  `name`  for  #<Person:...  @name="Jimmy">   #        ...   Person.instance_eval  do      define_method("name")  {  @name  }   end   jimmy.name   #  =>  "Jimmy" 4
  4. © 1999-2015 Erlang Solutions Ltd. Metaprogramming (Python example) class  Person:

             def  __init__(self,  name):                  self._name  =  name   jimmy  =  Person("Jimmy")   #  =>  <__main__.Person  instance  at  0x10868fbd8>   jimmy.name   #  =>  AttributeError:  Person  instance  has  no  attribute  'name'   jimmy.name()   #  =>  AttributeError:  Person  instance  has  no  attribute  'name'   setattr(Person,  "name",  lambda  self:  self._name)   jimmy.name   #  =>  <bound  method  Person.<lambda>  of  <__main__.Person  instance  at   0x10868fbd8>>   jimmy.name()   #  =>  'Jimmy' 5
  5. © 1999-2015 Erlang Solutions Ltd. Metaprogramming (JS example) function  Person(name)

     {          this._name  =  name;   }   var  jimmy  =  new  Person("Jimmy");   jimmy.name()   //  =>  ReferenceError:  name  is  not  defined   Person.prototype.name  =  function()  {          return  this._name;   };   jimmy.name();   //  =>  'Jimmy' 6
  6. © 1999-2015 Erlang Solutions Ltd. 8 Is there support for

    metaprogramming in Erlang? Yes!
 It may not obvious, though.
  7. © 1999-2015 Erlang Solutions Ltd. Metaprogramming (Erlang example) -­‐module(person).  

    -­‐export([new/1]).   -­‐record(person,  {name}).   new(Name)  -­‐>          #person{name  =  Name}. 9 Jimmy  =  person:new("Jimmy").   %%  =>  {person,  "Jimmy"}   person:name(Jimmy).   %%  =>  exception  error:   undefined  function  person:name/1   NameF  =  forms:to_abstract("
    name(#person{name  =  Name})  -­‐>
          Name.   ").   meta:add_function(      NameF,      _Export  =  true,      person).   person:name(Jimmy).   %%  =>  "Jimmy"
  8. © 1999-2015 Erlang Solutions Ltd. 11 How is metaprogramming supported

    in Erlang? - Abstract format - Parse transformations - Hot code loading (optional)
  9. © 1999-2015 Erlang Solutions Ltd. Abstract Format 12 • Recursive

    representation of Erlang code • Easier to manipulate than vanilla Erlang • Use +debug_info option to include a module’s abstract format in its beam file • Use beam_lib/epp modules to fetch it
  10. © 1999-2015 Erlang Solutions Ltd. Abstract Format 13 -­‐module(person).  

    -­‐export([new/1]).   -­‐record(person,  {name}).   new(Name)  -­‐>          #person{name  =  Name}. [{attribute,1,file,{"person.erl",1}},    {attribute,18,module,person},        {attribute,20,export,[{new,1}]},        {attribute,22,record,      {person,        [{record_field,22,            {atom,22,name}}]}},        {function,24,new,1,      [{clause,24,          [{var,24,'Name'}],          [],          [{record,25,person,              [{record_field,25,                {atom,25,name},                {var,25,'Name'}}]}]}]},        {eof,26}] Lots of Irritating Single Curly Brackets
 (LISCB)
  11. © 1999-2015 Erlang Solutions Ltd. Parse Transformations 14 • Compile-time

    transformations • After the preprocessor and before the compiler • parse_transform/2 • +{parse_transform, Module} • Identity transform2 2 https://github.com/erlang/otp/blob/maint/lib/stdlib/examples/erl_id_trans.erl
  12. © 1999-2015 Erlang Solutions Ltd. Hot Code Loading 15 •

    Load a new version of a module without disrupting your running system • code:load_file/1, code:load_binary/3 • What out for processes running older versions of the module
  13. © 1999-2015 Erlang Solutions Ltd. Metaprogramming Made “Easy” • forms3

    - lists-like interface (e.g. map, reduce, all, any) - convert to/from abstract code - support for defining functions dynamically - forms_only and all traversal modes • meta4 - abstracts away the complexity of modifying an Erlang module - works at both compile and run time - permanent and transient changes 16 4 https://github.com/efcasado/meta 3 https://github.com/efcasado/forms
  14. © 1999-2015 Erlang Solutions Ltd. forms (identity transform) 17 Forms

     =    forms:read(person),   Forms  ==  forms:map(fun(Form)  -­‐>                                io:format("~p~n",                                                  [Form]),                              Form                      end,                      Forms,                      [forms_only]),   %%  =>  true {attribute,1,file,{"person.erl",1}}   {attribute,18,module,person}   {attribute,20,export,[{new,1}]}   {attribute,22,record,    {person,[{record_field,22,{atom,22,name}}]}}   {function,24,new,1,          [{clause,24,                    [{var,24,'Name'}],                    [],                    [{record,25,person,                              [{record_field,25,{atom,25,name}, {var,25,'Name'}}]}]}]}   {clause,24,                  [{var,24,'Name'}],                  [],                  [{record,25,person,                      [{record_field,25,{atom,25,name},                          {var,25,'Name'}}]}]}   {var,24,'Name'}   {record,25,person,    [{record_field,25,{atom,25,name},        {var,25,'Name'}}]}   {record_field,25,{atom,25,name},{var,25,'Name'}}   {atom,25,name}   {var,25,'Name'}   {eof,26}
  15. © 1999-2015 Erlang Solutions Ltd. forms (count anonymous variables) 18

    -­‐module(m).   -­‐export([foo/0]).   -­‐record(r,  {x,  y,  z}).   foo()  -­‐>          _  =  1,          bar(1,  2,  #r{x=1,  y=2,  z=3}).   bar(_,  _,  R  =  #r{x=_,  y=Y,  z=Z})  -­‐>          Z. forms:reduce(          fun({var,  _,  '_'},  Count)  -­‐>                  Count  +  1;          (_,  Count)  -­‐>                  Count          end,          0          forms:read(m)).   %%  =>  4
  16. © 1999-2015 Erlang Solutions Ltd. meta (example) 19 -­‐module(s).  

    -­‐export([start/0]).   -­‐export(['_loop'/0]).   start()  -­‐>          spawn(fun()  -­‐>  '_loop'()  end),          ok.   '_loop'()  -­‐>          X  =  get_value(),          io:format("~p:  I've  got  ~p~n”,                              [self(),  X]),          timer:sleep(1000),          ?MODULE:'_loop'().   get_value()  -­‐>          2015. s:start().   <0.34.0>:  I've  got  2015   <0.34.0>:  I've  got  2015   <0.34.0>:  I've  got  2015   <0.34.0>:  I've  got  2015   NewF  =  forms:to_abstract("          get_value()  -­‐>  -­‐1.   ").   meta:replace_function(get_value,  0,   NewF,  s,  []).   <0.34.0>:  I've  got  -­‐1   <0.34.0>:  I've  got  -­‐1
  17. © 1999-2015 Erlang Solutions Ltd. What can I do with

    forms/meta? • behaviours25 - Erlang behaviours on steroids - Sound defaults for your behaviour’s callbacks - “Interface —> Abstract class” • erldocs6 - Your module’s documentation as a first-class citizen 20 6 https://github.com/efcasado/erldocs 5 https://github.com/efcasado/behaviours2
  18. © 1999-2015 Erlang Solutions Ltd. behaviours2 (dummy behaviour) 21 -­‐module(my_awesome_behaviour).

      -­‐export([export_all]).   -­‐type  t1()  ::  any().   -­‐type  t2()  ::  any().   -­‐callback  f1()  -­‐>  t1().   -­‐callback  f2()  -­‐>  t2().   -­‐callback  f3()  -­‐>  t2().   f1()  -­‐>          'default_f1'.   f2()  -­‐>          'default_f2'.   f3()  -­‐>          f2(). -­‐module(my_awesome_module).   -­‐compile({parse_transform,    
                    bhvs2_pt}).   -­‐behaviour(my_awesome_behaviour).   f2()  -­‐>      'custom_f2'. my_awesome_module:f1().   my_awesome_module:f2().   my_awesome_module:f3().  
  19. © 1999-2015 Erlang Solutions Ltd. behaviours2 (dummy behaviour) 22 -­‐module(my_awesome_behaviour).

      -­‐export([export_all]).   -­‐type  t1()  ::  any().   -­‐type  t2()  ::  any().   -­‐callback  f1()  -­‐>  t1().   -­‐callback  f2()  -­‐>  t2().   -­‐callback  f3()  -­‐>  t2().   f1()  -­‐>          'default_f1'.   f2()  -­‐>          'default_f2'.   f3()  -­‐>          f2(). -­‐module(my_awesome_module).   -­‐compile({parse_transform,    
                    bhvs2_pt}).   -­‐behaviour(my_awesome_behaviour).   f2()  -­‐>      'custom_f2'. my_awesome_module:f1().   %  =>  default_f1   my_awesome_module:f2().   %  =>  custom_f2   my_awesome_module:f3().   %  =>  custom_f2.
  20. © 1999-2015 Erlang Solutions Ltd. behaviours2 (gen_server) 23 -­‐module(echo_server).  

    -­‐compile({parse_transform,  bhv2_pt}).   -­‐behaviour(gen_server).   -­‐export([handle_call/3]).   handle_call(Msg,  From,  State)  -­‐>      Reply  =  Msg,      {reply,  Msg,  State}. -­‐module(echo_server).   -­‐behaviour(gen_server).   -­‐export([init/1,  handle_call/3,  handle_cast/2,                    handle_info/2,  terminate/2,                    code_change/3]).   -­‐record(state,  {}).   -­‐type  state()  ::  #state{}.   -­‐spec  init(Args  ::  [])  -­‐>                      {ok,  state()}  |                      {ok,  state(),  timeout()}  |                      ignore  |                      {stop,  Reason  ::  term()}.   init([])  -­‐>          {ok,  #state{}}.   -­‐spec  handle_call(Request  ::  term(),                                      From  ::  {pid(),  Tag  ::  term()},                                      State  ::  state())  -­‐>                      {reply,  Reply  ::  term(),  state()}  |                      {reply,  Reply  ::  term(),  state(),  timeout()}  |                      {noreply,  state()}  |                      {noreply,  state(),  timeout()}  |                      {stop,  Reason  ::  term(),  Reply  ::  term(),  state()}  |                      {stop,  Reason  ::  term(),  state()}.   handle_call(Msg,  _From,  State)  -­‐>          Reply  =  Msg,          {reply,  Reply,  State}.   -­‐spec  handle_cast(Msg  ::  term(),                                      State  ::  state())  -­‐>                      {noreply,  state()}  |                      {noreply,  state(),  timeout()}  |                      {stop,  Reason  ::  term(),  state()}.   handle_cast(Msg,  State)  -­‐>                  {noreply,  State}.   -­‐spec  handle_info(Info  ::  term(),                                      State  ::  state())  -­‐>                      {noreply,  state()}  |                      {noreply,  state(),  timeout()}  |                      {stop,  Reason  ::  term(),  state()}.   handle_info(Info,  State)  -­‐>          {noreply,  State}.   -­‐spec  terminate(Reason  ::  term(),                                  State  ::  state())  -­‐>  any().   terminate(_Reason,  _State)  -­‐>          ok.   -­‐spec  code_change(OldVsn  ::  term()  |  {down,  Vsn  ::  term()},                                      State  ::  state(),                                      Extra  ::  term())  -­‐>                      {ok,  NewState  ::  state()}.   code_change(_OldVsn,  State,  _Extra)  -­‐>          {ok,  State} Java is verbose, 
 they said.
  21. © 1999-2015 Erlang Solutions Ltd. erldocs (example) 24 -­‐module(dummy_module).  

    -­‐compile({parse_transform,  erldocs_pt}).   -­‐export([f1/0,  f2/1]).   -­‐doc  "This  is  the  documentation  for  f1".   -­‐spec  f1()  -­‐>  'f1'.   f1()  -­‐>  f1.   -­‐doc  "This  is  the  documentation  for  f2".   -­‐spec  f2(X  ::  integer())  -­‐>  boolean().   f2(N)  -­‐>          (N  rem  2)  ==  0. dummy_module:help(f1,  0).   ###  f1/0          f1()  -­‐>  f1   This  is  the  documentation  for  f1