[erlang-questions] Updates, lenses, and why cross-module inlining would be nice

Tristan Sloughter t@REDACTED
Thu Nov 26 14:35:49 CET 2015


Jesper also created https://github.com/jlouis/erl-lenses a few years
back for this.

-- 
  Tristan Sloughter
  t@REDACTED

On Wed, Nov 25, 2015, at 10:52 PM, Richard A. O'Keefe wrote:
> Summary:
>  (1) A promising way to solve the nested updates problem
>      is to be more functional, not less.
>  (2) There's an approach we could steal from Haskell.
>  (3) There's free code to play with, worth every penny.
> 
> Consider a simplified version of Pascal:
> 
>   <variable> ::= <identifier> <selector>*
>   <selector> ::= <dereference> | <field> | <index>
>   <dereference> ::= '^'
>   <field> ::= '.' <identifier>
>   <index> ::= '[' <expression> ']'
> 
> If we think of a selector as a function
>   f :: var T1 -> var T2
> it's clear that <identifier> <selector> is function
> application and <selector 1> <selector 2> is
> function composition.
> 
> Yet in Pascal, and C, and languages like them,
> while you can *apply* selectors and *compose* them,
> they cannot be passed as parameters or even named.
> 
> If a functional language has something like selectors,
> they should be first class values, just like other
> more-or-less-function-like things.  But what kind of
> value?
> 
> I think it was Reynolds, analysing Algol 60, who came
> up with the idea that a pass-by-name parameter was
> really a *pair* of functions:  one to fetch a value
> and one to store it, and thereby freed us from the
> limiting idea that there needed to be a 'var T' type.
> 
> The Haskell community have taken this idea and
> developed it.  There this concept is called a "Lens".
> 
> http://www.cs.otago.ac.nz/staffpriv/ok/lens.erl
> 
> is a crude implementation of lenses in Erlang.
> A lens is a selector from (values of) type A to
> (values of) type B.  It's represented as a triple
> 
>     {Get, Put, Upd}
>     Get :: (A) -> B
>     Put :: (A, B) -> A
>     Upd :: (A, (B) -> B) -> A
> 
> The Get function says how to extract a B value from an
> A value.  The Put function says how to put a B value
> into an existing A value, returning a new A value.
> The Upd function says how to compute a new A value by
> extracting a B, applying a function to it, and putting
> it back.  We expect the following laws to hold:
> 
>     Get(Put(A, B)) = B
>     Put(A, Get(A)) = A
>     Upd(A, Fun) = Put(A, Fun(Get(A)))
> 
> That is, if you make a lens by hand, you should ensure
> that this is true.
> 
> There are functions for using lenses so you don't have
> to think about the representation.
> 
>     lens:get(Lens, A) -> B
>     lens:put(Lens, A, B) -> A'
>     lens:update(Lens, A, F) -> A'
> 
> There are also functions for making simple lenses.
> For example, if you have a record 'rec' with a field
> 'fld', lens:tuple(#rec.fld) will give you a lens
> for that field.
> 
> Warning: the functions in the lens returned by
> lens:tuple(#rec.fld)
> do NOT check at run time that they are dealing with
> a 'rec' record.  They can't, because #rec.fld is just
> a number, and you can't get back from that number to
> 'rec' or the arity of the record.
> 
> The important thing is that these functions compose.
> 
> For example, suppose you have
>   -record(foo, {boo,coo,goo,zoo}).
> and Foozle is a list of these, and you want to find
> the first record with boo=42, and increment its zoo
> field by 137.
> 
>     Find = lens:where(fun (#foo{boo=Boo}) -> Boo == 42 end)
> 
> is a lens for finding the record you want and
> 
>     Field = lens:tuple(#foo.zoo)
> 
> is a lens for getting at the zoo field of such a record.
> lens.erl provides composition of 2 .. 6 lenses.  So
> 
>     Selector = lens:c(Find, Field)
> 
> is a selector that refers to a field of a record in a list,
> and
> 
>     lens:update(Selector, Foozle, fun (Zoo) -> Zoo + 137 end)
> 
> does the whole job (in just one pass through the list, too,
> which is why the Upd function is there).
> 
> More simply,
>     lens:c(lens:tuple(#a.b), lens:tuple(#c.d),
>            lens:tuple(#e.f), lens:tuple(#g.h))
> is the equivalent of .b.d.f.h in Pascal, except that it is
> a value you can pass around like any other.
> 
> You are not limited to fields that actually exist as such.
> For example, you could do
> 
> gb_set(Element) ->
>     {   fun (Set) -> gb_sets:is_member(Element, Set) end
>     ,   fun (Set, false) -> gb_sets:delete_any(Element, Set)
>           ; (Set, true)  -> gb_sets:add(Element, Set)
>         end
>     ,   fun (Set, Fun) ->
>             Old = gb_sets:is_member(Element, Set),
>             case Fun(Old)
>               of Old   -> Set
>                ; true  -> gb_sets:insert(Element, Set)
>                ; false -> gb_sets:delete(Element, Set)
>             end
>         end
>     }.
> 
> and make a gb-set act like a dictionary with Boolean elements.
> 
> There's even a combinator all/1 so that
>     All = lens:c(lens:tuple(1)),
>     Data = [{3,a},{1,b},{4,c},{6,d}]
>     lens:get(All, Data)
>  => [3,1,4,6]
>     lens:update(All, Data, fun (N) -> N*10 end)
>  => [{30,a},{10,b},{40,c},{60,d}]
> 
> So what about cross-module inlining?
> 
> By the time you've made and composed a couple of lenses,
> you have a tangle of funs.  If the compiler (a) always
> inlines (fun (...) -> ... end)(...) and (b) inlines the
> functions from the lens module, then the tangle resolves
> tidily into reasonably straightforward code.
> 
> But the cross-module inlining step is crucial.  Without
> that, the compiler has nothing to work on.
> 
> At its crudest, consider
> 
>     lens:get(lens:tuple(#rec.fld), Rec)
> 
> *With* cross-module inlining and inlining of funs that
> are applied to known arguments and dead code elimination,
> you end up with
> 
>     element(#rec.fld, Rec)
> 
> *Without* cross-module inlining, three funs are built,
> only one of which will ever be called.
> 
> 
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list