2016-07-07 14:24 GMT+03:00 Bram Moolenaar <[email protected]>:
>
> Nikolay Pavlov wrote:
>
>> >> > I have mixed feelings about this implementation.  This needs more
>> >> > thoughts and discussion.
>> >> >
>> >> >
>> >> > One of the problems with the current use of a string for an expression,
>> >> > as it's passed to map(), filter(), etc., is that this is a string, which
>> >> > requires taking care of quotes.
>> >> >
>> >> > Using lambda() for that doesn't have an advantage, it's only longer:
>> >> >
>> >> >         :call map(mylist, '"> " . v:val . " <"')
>> >> >         :call map(mylist, lambda('"> " . v:val . " <"'))
>> >> >
>> >> > Perhaps we can define the lambda not as a function call, but as an
>> >> > operator.  Then it will be parsed differently and we don't need to put
>> >> > the expression in quotes.  We do want to be able to define it inline, as
>> >> > a function argument.  We do need something around it, so that it's clear
>> >> > where the end is.  Using {} would work, it's similar to a statement
>> >> > block in most languages.
>> >> >
>> >> > We would also like to specify arguments.  We could separate the
>> >> > arguments from the statements with a "gives" symbol.  That could be ->.
>> >> > This avoid the strange use of v:val and v:key to pass values to the
>> >> > expression, like map() does.
>> >> >
>> >> >         call Func(arg, lambda{v -> return v * 3.12})
>> >> >
>> >> > That way we can do this:
>> >> >         call map(mylist, lambda{v -> return "> " . v . " <"})
>> >> >
>> >> > In most cases only one statement is needed, but having a few more should
>> >> > be possible.  Using | to separate statements hopefully works:
>> >> >
>> >> >         call Func(arg, lambda{ v -> if v < 0 | return v * 3.12 | else | 
>> >> > return v * -3.12 | endif})
>> >> >
>> >> > Something like that.  If it gets too long it's better to just define a
>> >> > function.
>> >>
>> >> `lambda{ v -> if v < 0 | return v * 3.12 | else | return v * -3.12 |
>> >> endif}` is too verbose. AFAIR I already suggested lambdas
>> >> implementation once and it was just
>> >>
>> >>     \arg1, arg2 : expr1
>> >>
>> >> . I.e. lambda functions do *not* allow to use commands, only
>> >> expressions. Documentation for my attempt was
>> >
>> > Yes, that is an option.  It does restrict the situations when it can be
>> > used, but since a multi-statement lambda quickly becomes unreadable,
>> > there may not be much use for it anyway.
>> >
>> > It does require the possibility to create a closure with a nested
>> > function, in case we do need multiple statements, but if we do support
>> > closures it should be done for both lambda and normally defined
>> > functions anyway.
>> >
>> > I do not like the cryptic syntax.  I think the word "lambda" is good,
>> > unless we can think of something that is easy to recognize.
>> > One possible option is to focus on using the "gives" token:
>> >
>> >         { a1, a2 -> a1 + a2 }
>> >
>> > Even without an argument it can still be recognized:
>> >
>> >         { -> string(Today()) }
>>
>> This is way harder to implement, `{` already defines a dictionary and
>> it may also be used for variable names, what already requires a “call
>> eval1 twice” hack. `\` has no such problem, and I do not see this too
>> cryptic. If you think that `->` is necessary, it is better to prefer
>> `\args -> expr1` over `\args : expr1`, not `{ args -> expr1 }`.
>>
>> Prefixing lambda with `lambda` keyword makes this easy, but way too verbose.
>
> I do think that the backslash (or any other special character that might
> be available) makes it ugly.
>
> Although we can find the end of the expression without the "}", mistakes
> will be very hard to figure out.  Also reading back a lambda that's
> passed as an agument to a function with many arguments will make the {}
> part easy to spot.  I have done this in Zimbu and I'm happy with how it
> looks.
>
> I don't think it's hard to implement: When finding a "{" look forward,
> skipping over [a-zA-Z0-9 ], if "->" is found it's a lambda.
>
>> >> |  +lambda                                                      
>> >> *expr-lambda*
>> >> |  +------
>> >> |  +\arg1, ...: expr1   lambda function
>> >> |  +
>> >> |  +Lambda functions are the lightweight variant of |user-functions|.
>> >> Differences
>> >> |  +between them are:
>> >> |  +
>> >> |  +1. Body of the lambda function is an |expr1| and not a sequence of 
>> >> |Ex|
>> >> |  +   commands.
>> >> |  +2. Lambda function does not have its own scope dictionary. It does
>> >> have scope
>> >> |  +   dictionary for |a:var| though. |l:| scope dictionary is
>> >> inherited from the
>> >> |  +   place where lambda was defined (thus it is either |g:| scope
>> >> dictionary or
>> >> |  +   |l:| dictionary of the function that defined the lambda).
>> >> |  +   You see, it is poor man closure.
>> >> |  +3. If |a:var| is not found in lambda own scope dictionary it is 
>> >> searched in
>> >> |  +   |a:| scope dictionary of the function where lambda was defined (if 
>> >> any).
>> >> |  +   This does not apply to |a:000|, |a:0|, |a:1|, ... variables.
>> >> |  +4. There are no dictionary lambdas.
>> >> |  +5. Lambda is not globally recorded anywhere like |anonymous-function|.
>> >> |  +
>> >> |  +NOTE: lambda function records the whole scope dictionary in its
>> >> body and also
>> >> |  +      |a:| dictionary. That means that extensive generation of 
>> >> lambdas and
>> >> |  +      exporting them outside of generator function may lead to a huge 
>> >> memory
>> >> |  +      overhead.
>> >>
>> >> and point 5. here required my extended-funcref branch.
>> >>
>> >> I do not really see any sense in allowing commands in lambdas, in
>> >> cases when commands are really needed lambdas should become
>> >> unreadable. Your example in my syntax is simply
>> >>
>> >>     \v: (a:v < 0 ? a:v * 3.12 : a:v * -3.12)
>> >
>> > Yes, for some places a ternary expression works.  However, Vim doesn't
>> > have an expression with side effects.  Well, except when calling a
>> > function, but that defeats the idea of the lambda.
>> >
>> > Theoretically we could use ";" to separate expressions (like the comma
>> > operator in C), but I wonder we should actually do this:
>> >
>> >         { a -> tmp = a * 5; [tmp, tmp + 2, tmp + 5] }
>> >
>> > In this context it is useful, but in other places it's an invitation to
>> > write confusing code.
>>
>> If I absolutely want to write lambda with a variable defined inside
>> and not a regular function I use
>>
>>     \a -> ((\tmp -> [a, a + 2, a + 5])(a * 5))
>
> Hmm, I don't like that at all.  It's hiding the intention.
>
>> , this needs absolutely no extensions to expression syntax should it
>> define lambdas (and I really used this in Python). Such things are
>> also a reason why `extended-funcref` was required: the more plugins
>> and the longer Vim session lives, the more likely that anonymous
>> functions counter gets overflown. My `extended-funcref` removed global
>> registry which contained *all* functions: specifically lambdas in
>> extended-funcref-lambdas preview and regular anonymous functions from
>> the main feature branch no longer lived with other user functions:
>> they were connected to funcref only and nothing else, no global name.
>
> What patch are you referring to?

I refer to my branches in https://bitbucket.org/ZyX_I/vim. They were
posted as patches here AFAIR, but not accepted. In the current state
extended-funcref branch does not work because something was broken
during last merge and I did not fix this because nobody seemed to be
interested; and actually more work is needed to implement partials on
top of new funcrefs (BTW, in this branch no VAR_PARTIAL would be
needed, everything is VAR_FUNC).

>
>> >> > The implementation also takes items from the context, thus creating a
>> >> > closure.  I don't think that should be specific to a lambda, defining a
>> >> > function inside another function should be able to do the same thing.
>> >> > After all, a lambda is just a short way of defining a nameless function.
>> >> >
>> >> > In the implementation it seems the dictionary storing the function-local
>> >> > variables is kept for a very long time.  This relies on the garbage
>> >> > collector.  It's better to use reference counting to be able to free the
>> >> > dictionary as soon as it's unused.
>> >> >
>> >> > Also, the lambda always keeps the function-local variable dict, even
>> >> > when it's not actually used.  That makes lambdas a expensive.
>> >> > It would be better to explicitly state the lambda is using its context.
>> >> > Then we can also do that with ":function", so that we are not forced to
>> >> > use a lambda if we want a closure.
>> >>
>> >> Note that my variant did not avoid these problems: I simply stored a
>> >> whole funccall structure in addition to storing pointers to in-lambdas
>> >> l:.
>> >>
>> >> If there was VimL parser I would suggest to determine which variables
>> >> are “captured” from the outer scope, AFAIR Python does something like
>> >> this. But VimL has no parser… though evalX probably may be modified to
>> >> do this when skipping, with Ex commands this would be harder. There is
>> >> another possible solution: explicitly define which variables from the
>> >> outer scope are used: e.g.
>> >>
>> >>     function Foo(arg1, arg2)
>> >>         let l = 42
>> >>         function Bar(arg1, <a:arg2, <l)
>> >>             return [a:arg1, a:arg2, l]
>> >>         endfunction
>> >>         echo Bar(45)
>> >>     endfunction
>> >>     call Foo(47, 43)
>> >>     " Echoes [45, 43, 42]
>> >>
>> >> In this case ellipsis for varargs functions should be the last entity
>> >> before the first `<…` argument, for lambdas syntax is the same.
>> >
>> > Specifying which variables from the environment are used makes it a lot
>> > more efficient, but it can also be a hassle.  A simpler, brute-force way
>> > is to specify that the function (or lambda) is a closure.  It would only
>> > use variables from the context that it's in, not a level up.  So keeping
>> > a reference to the dict with the local variables.
>> >
>> > Since it's also possible to bind a function to a dict, and use that dict
>> > to store any state, I think that this is actually not all that important
>> > to add.  Perhaps we should first have a good example where a closure is
>> > required or much nicer than a partial bound to a dict.
>> >
>> > So, of the three things that the CL was split up into:
>> > 1. pass function to filter() and map()
>> > 2. implement lambda
>> > 3. implement closure
>> >
>> > The first is done.  Implementing lambda seems useful enough.  I'm not so
>> > sure about the need for supporting closure.
>>
>> I have another impression: lambdas are mostly useful for sort(),
>> map()/filter() already accept fine strings and though lambdas may be
>> useful to convert `map(l, 'map(…)')` into something that contains less
>> quoting madness, such constructs exist purely for optimization* and I
>> am not sure that lambdas fit in here. No idea where @mattn is going to
>> find real-world one-line callbacks, I do not think lambdas for
>> timers/jobs/… are useful anywhere else, but in code that tests them.
>>
>> On the other hand closures are needed to not bother oneself with
>> copying variables into self dictionary, partial argument or whatever,
>> it is more convenient to just write nested :function and access
>> variables right away then first create a dictionary for this function,
>> put all needed variables there and use `self.varname`. Lambdas with
>> closures are more useful then without them: it is not unusual for me
>> to write something like `map(l, 'add(l2, v:val)')` and obviously I
>> cannot switch this code to use lambdas if they are not closures (as it
>> will look like `map(l, function(\l2, v -> add(l2, v), [l2]))` which is
>> rather lengthy).
>>
>> * Main VimL optimization principle: the less Ex commands, the faster
>> your code runs; I have a plugin where the whole :while cycle
>> consisting of :if’s, :let’s (and, probably, :call’s) was squashed into
>> a single :while condition, turning into `:while
>> {very-very-long-condition}|endwhile`. Bad I did not yet lay down my
>> hands on making this squashing automatic (cycle in question is a part
>> of the generated function which is already subject to some
>> optimizations).
>
> To make it faster we would need a just-in-time compiler of some kind.
> Or rewrite it in Python.  But it's unlikely that this happens.
> Usually, only a very small part of the code needs to be optimized.
>
> Although closures can make code compact (and thus less time spent on
> parsing), it also introduces overhead, thus I'm not sure if you end up
> with something that's actually faster.
>
> There is also the goal to keep Vim scripting as simple as possible, only
> add features that are really useful, and not make new features cryptic.
>
> --
> hundred-and-one symptoms of being an internet addict:
> 215. Your mouse-clicking forearm rivals Popeye's.
>
>  /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
> ///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
> \\\  an exciting new programming language -- http://www.Zimbu.org        ///
>  \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui