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.
