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

Richard A. O'Keefe ok@REDACTED
Fri Nov 27 05:28:14 CET 2015


On 27/11/2015, at 5:00 am, Dan Gudmundsson <dangud@REDACTED> wrote:

> Nice
> 
> Maybe a stupid question I have not looked at lenses before 
> but why include the "path of keys" into the lens?

It's not at all a stupid question.
At least, since I pondered the same thing myself,
I *hope* it wasn't a stupid question.

> Why not make them reusable and define 
> get({G,_,_}, Key, X) -> G(Key, X).
> put({_,P,_}, Key, X, Y) -> P(Key, X, Y).
> update({_,_,U}, Key, X, F) -> U(Key, X, F).

There are several answers.

(1) There are lenses like lens:hd() that don't *have* an
    argument.  While [get/3,put/4,update/4] *could* exist,
    [get/2,put/3,update/3] *must* exist in order to handle
    those.
    
(2) There are also lenses like
    lens:c(lens:index(I), lens:index(J))
    -- an index into a 2D array represented as a list of lists
    which have *two* arguments.  So now we're up to
      get/2, put/3, update/3,
      get/3, put/4, update/4,
      get/4, put/5, update/5
    and where do we stop?

(3) In terms of *functional sufficiency*, the present scheme
    is enough.  If we want
    tuple() -> fun (Index) -> lens:tuple(Index) end
    we can write it.  So the issue is performance.

(4) If lenses were implemented by a parse transform or by
    cross-module inlining, there wouldn't *be* any performance
    advantage for get/3, put/4, update/4.  While they aren't,
    I'm more troubled by the cost of building three closures
    when only one will be called.

(5) Remember the idea, to make "selectors" first class values?
    Splitting a selector into how-to-select + which-to-select
    pretty much destroys that.

    Here's a C++ analogy, if it's any use.  Think of a Lens as
    being like a pointer-to-member, which in C++ is a thing that
    doesn't point to a member of a specific object (that's a
    plain pointer) but to a pointer to a specific *part* of any
    object (of the right type) it happens to be applied to.

(6) Finally, I for one am a bear of very little brain.
    It took me a while to get my head around lenses enough
    to realise that they *could* work for Erlang; the
    simpler the scheme remains, the more likely it is that
    I'll be able to use it.
> 
> Another question, should/could we add a fourth fun (map) for traversal?

No.

> As it is all/1 and where/1 assumes lists and does not operate on for example gb_trees or dict.

Yes, but all/1 and where/1 are functions that make SPECIALISED
lenses for SPECIALISED data.  Lenses are primarily a way of solving
the update-a-value-in-a-deep-nest-of-records problem; there is
nothing about Lenses *as such* that has anything to do with
containers.

all/1 was just included to show that the data you are
extracting need not exist as a single value in the thing
you're extracting the data from.  (Like you can extract
a column from a matrix that is stored by rows.)

Here's another:

integer() ->
    { fun (N) -> integer_to_list(N) end
    , fun (_, L) -> list_to_integer(L) end
    , fun (N, F) -> list_to_integer(F(integer_to_list(N))) end
    }.

2> lens:get(lens:integer(), 42).
"42"
3> lens:put(lens:integer(), 42, "35").
35
4> lens:update(lens:integer(), 42, fun (L) -> L++"13" end).
4213
5> lens:put(lens:integer(), 'FOO!', "65").
65

In all honesty, I have no idea what a Map function would
look like in a Lens.  If you have any idea of what Map
would mean for a record field, more power to you.

What I *have* wondered about is information that might
or might not exist.  Should it be done with special
functions in every lens, or should it be done by having
special lenses?

%  gb_lookup(K) -> lens(gb_tree(K,V), none | {value,V}).

gb_lookup(Key) ->
    { fun (Tree) -> gb_trees:lookup(Key, Tree) end
    , fun (Tree, none) -> gb_trees:delete_any(Key, Tree)
        ; (Tree, {value,V}) -> gb_trees:enter(Key, V, Tree)
      end
    , fun (Tree, F) ->
          case gb_trees:lookup(Key, Tree)
            of none -> Tree
             ; {value,V} -> gb_trees:update(Key, F(V), Tree)
          end
      end
    }.

The sticking point was list and tuple indexing,
where it does make sense to have an optional fetch,
but not a store which can delete.  So maybe an

    opt(Lens, Data, Default)

might make sense, but not a put or update analogue.





More information about the erlang-questions mailing list