Hi Bram and mattn,

2016/7/3 Sun 6:27:36 UTC+9 Bram Moolenaar wrote:
> Ken Takata wrote:
> 
> > 2016/5/27 Fri 22:38:34 UTC+9 Ken Takata wrote:
> > > Hi mattn,
> > > 
> > > 2016/4/18 Mon 0:30:19 UTC+9 mattn wrote:
> > > > Hi, Bram and list.
> > > > 
> > > > Updated lambda patch to be applied for latest code.
> > > > 
> > > > https://gist.github.com/mattn/5bc8ded21e1033c9c0ea8cd5ecbbce11
> > > > 
> > > > This include examples for timer on help file. I'm thinking lambda() 
> > > > have cooperative to work with job/timer/channel.
> > > > 
> > > > So I hope to add this into vim8. How do you think?
> > > > 
> > > > Thanks to k-takata, haya14busa, and all of members on vim-jp.
> > > 
> > > I have tested the lambda patch with the latest Vim and I found two 
> > > problems.
> > > 
> > >   1. garbagecollect_for_testing() was renamed.
> > >   2. Test_lambda_with_timer() fails on Cygwin. It seems that we need 
> > > margins
> > >      as we do in test_timers.vim.
> > > 
> > > Please check attached patch.
> > > 
> > > I hope that the lambda patch will be merged in Vim 8.0.
> > > This makes filter(), map() and sort() easy to use.
> > > It also works nicely with job/channel/timer features.
> > 
> > I have merged my patch into your patch, and also fixed that the function
> > declaration was not sorted in alphabetical order.
> > Please check the attached patch.
> 
> 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.
> 
> 
> 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.
> 
> However, I'm not sure we need this.  It is possible to bind a function
> to a dictionary, which can then contain anything that's to be kept
> between calls.
> 
> 
> Being able to pass a function to map(), sort(), etc. is good.  However,
> it looks like in the implementation only VAR_FUNC is supported, not
> VAR_PARTIAL.  It should be both.

It seems that it's better to divide the lambda patch into several patches.
E.g.:

1. lambda expression
2. closure
3. enable to pass a function to filter() and map()

I wrote a patch for #3.  I slightly changed the specification so that a
function has both key and value as arguments.  I also added some tests for
filter() and map().  Please check.

Regards,
Ken Takata

-- 
-- 
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 vim_dev+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
# HG changeset patch
# Parent  e8cf557dd9c2d7d7c9820048ae37d614e2b8d312

diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -3521,11 +3521,12 @@ filewritable({file})					*filewritable()
 		directory, and we can write to it, the result is 2.
 
 
-filter({expr}, {string})					*filter()*
-		{expr} must be a |List| or a |Dictionary|.
-		For each item in {expr} evaluate {string} and when the result
+filter({expr1}, {expr2})				*filter()*
+		{expr1} must be a |List| or a |Dictionary|.
+		For each item in {expr1} evaluate {expr2} and when the result
 		is zero remove the item from the |List| or |Dictionary|.
-		Inside {string} |v:val| has the value of the current item.
+		{expr2} must be a |string| or |Funcref|. If it is a |string|,
+		inside {expr2} |v:val| has the value of the current item.
 		For a |Dictionary| |v:key| has the key of the current item.
 		Examples: >
 			:call filter(mylist, 'v:val !~ "OLD"')
@@ -3535,17 +3536,22 @@ filter({expr}, {string})					*filter()*
 			:call filter(var, 0)
 <		Removes all the items, thus clears the |List| or |Dictionary|.
 
-		Note that {string} is the result of expression and is then
+		Note that {expr2} is the result of expression and is then
 		used as an expression again.  Often it is good to use a
 		|literal-string| to avoid having to double backslashes.
 
+		If {expr2} is a |Funcref|, the first argument has the key or
+		the index of the current item.  The second argument has the
+		value of the current item.  The function should return TRUE
+		if the item should be kept.
+
 		The operation is done in-place.  If you want a |List| or
 		|Dictionary| to remain unmodified make a copy first: >
 			:let l = filter(copy(mylist), 'v:val =~ "KEEP"')
 
-<		Returns {expr}, the |List| or |Dictionary| that was filtered.
-		When an error is encountered while evaluating {string} no
-		further items in {expr} are processed.
+<		Returns {expr1}, the |List| or |Dictionary| that was filtered.
+		When an error is encountered while evaluating {expr2} no
+		further items in {expr1} are processed.
 
 
 finddir({name}[, {path}[, {count}]])				*finddir()*
@@ -5036,29 +5042,35 @@ luaeval({expr}[, {expr}])					*luaeval()
 		See |lua-luaeval| for more details.
 		{only available when compiled with the |+lua| feature}
 
-map({expr}, {string})					*map()*
-		{expr} must be a |List| or a |Dictionary|.
-		Replace each item in {expr} with the result of evaluating
-		{string}.
-		Inside {string} |v:val| has the value of the current item.
+map({expr1}, {expr2})					*map()*
+		{expr1} must be a |List| or a |Dictionary|.
+		Replace each item in {expr1} with the result of evaluating
+		{expr2}.
+		{expr2} must be a |string| or |Funcref|. If it is a |string|,
+		inside {expr2} |v:val| has the value of the current item.
 		For a |Dictionary| |v:key| has the key of the current item
 		and for a |List| |v:key| has the index of the current item.
 		Example: >
 			:call map(mylist, '"> " . v:val . " <"')
 <		This puts "> " before and " <" after each item in "mylist".
 
-		Note that {string} is the result of an expression and is then
+		Note that {expr2} is the result of an expression and is then
 		used as an expression again.  Often it is good to use a
 		|literal-string| to avoid having to double backslashes.  You
 		still have to double ' quotes
 
+		If {expr2} is a |Funcref|, the first argument has the key or
+		the index of the current item.  The second argument has the
+		value of the current item.  The function should return a
+		value to replace the item with it.
+
 		The operation is done in-place.  If you want a |List| or
 		|Dictionary| to remain unmodified make a copy first: >
 			:let tlist = map(copy(mylist), ' v:val . "\t"')
 
-<		Returns {expr}, the |List| or |Dictionary| that was filtered.
-		When an error is encountered while evaluating {string} no
-		further items in {expr} are processed.
+<		Returns {expr1}, the |List| or |Dictionary| that was filtered.
+		When an error is encountered while evaluating {expr2} no
+		further items in {expr1} are processed.
 
 
 maparg({name}[, {mode} [, {abbr} [, {dict}]]])			*maparg()*
diff --git a/src/Makefile b/src/Makefile
--- a/src/Makefile
+++ b/src/Makefile
@@ -2031,6 +2031,7 @@ test_arglist \
 	test_farsi \
 	test_feedkeys \
 	test_file_perm \
+	test_filter_map \
 	test_fnamemodify \
 	test_glob2regpat \
 	test_goto \
diff --git a/src/eval.c b/src/eval.c
--- a/src/eval.c
+++ b/src/eval.c
@@ -11852,7 +11852,7 @@ findfilendir(
 }
 
 static void filter_map(typval_T *argvars, typval_T *rettv, int map);
-static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp);
+static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp);
 
 /*
  * Implementation of map() and filter().
@@ -11860,8 +11860,7 @@ static int filter_map_one(typval_T *tv, 
     static void
 filter_map(typval_T *argvars, typval_T *rettv, int map)
 {
-    char_u	buf[NUMBUFLEN];
-    char_u	*expr;
+    typval_T	*expr;
     listitem_T	*li, *nli;
     list_T	*l = NULL;
     dictitem_T	*di;
@@ -11896,14 +11895,13 @@ filter_map(typval_T *argvars, typval_T *
 	return;
     }
 
-    expr = get_tv_string_buf_chk(&argvars[1], buf);
+    expr = &argvars[1];
     /* On type errors, the preceding call has already displayed an error
      * message.  Avoid a misleading error message for an empty string that
      * was not passed as argument. */
-    if (expr != NULL)
+    if (expr->v_type != VAR_UNKNOWN)
     {
 	prepare_vimvar(VV_VAL, &save_val);
-	expr = skipwhite(expr);
 
 	/* We reset "did_emsg" to be able to detect whether an error
 	 * occurred during evaluation of the expression. */
@@ -11975,21 +11973,44 @@ filter_map(typval_T *argvars, typval_T *
 }
 
     static int
-filter_map_one(typval_T *tv, char_u *expr, int map, int *remp)
+filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
 {
     typval_T	rettv;
+    typval_T	argv[3];
     char_u	*s;
     int		retval = FAIL;
+    int		dummy;
 
     copy_tv(tv, &vimvars[VV_VAL].vv_tv);
-    s = expr;
-    if (eval1(&s, &rettv, TRUE) == FAIL)
-	goto theend;
-    if (*s != NUL)  /* check for trailing chars after expr */
-    {
-	EMSG2(_(e_invexpr2), s);
-	clear_tv(&rettv);
-	goto theend;
+    argv[0] = vimvars[VV_KEY].vv_tv;
+    argv[1] = vimvars[VV_VAL].vv_tv;
+    s = expr->vval.v_string;
+    if (expr->v_type == VAR_FUNC)
+    {
+	if (call_func(s, (int)STRLEN(s),
+		    &rettv, 2, argv, 0L, 0L, &dummy, TRUE, NULL, NULL) == FALSE)
+	    goto theend;
+    }
+    else if (expr->v_type == VAR_PARTIAL)
+    {
+	partial_T   *partial = expr->vval.v_partial;
+
+	s = partial->pt_name;
+	if (call_func(s, (int)STRLEN(s),
+		    &rettv, 2, argv, 0L, 0L, &dummy, TRUE, partial, NULL)
+								    == FALSE)
+	    goto theend;
+    }
+    else
+    {
+	s = skipwhite(s);
+	if (eval1(&s, &rettv, TRUE) == FAIL)
+	    goto theend;
+	if (*s != NUL)  /* check for trailing chars after expr */
+	{
+	    EMSG2(_(e_invexpr2), s);
+	    goto theend;
+	}
     }
     if (map)
     {
diff --git a/src/testdir/test_alot.vim b/src/testdir/test_alot.vim
--- a/src/testdir/test_alot.vim
+++ b/src/testdir/test_alot.vim
@@ -12,6 +12,7 @@ source test_expand_dllpath.vim
 source test_feedkeys.vim
 source test_fnamemodify.vim
 source test_file_perm.vim
+source test_filter_map.vim
 source test_glob2regpat.vim
 source test_goto.vim
 source test_help_tagjump.vim
diff --git a/src/testdir/test_filter_map.vim b/src/testdir/test_filter_map.vim
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_filter_map.vim
@@ -0,0 +1,77 @@
+" Test filter() and map()
+
+" list with expression string
+func Test_filter_map_list_expr_string()
+  " filter()
+  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
+  call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
+
+  " map()
+  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
+  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
+endfunc
+
+" dict with expression string
+func Test_filter_map_dict_expr_string()
+  let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+  " filter()
+  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
+  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
+
+  " map()
+  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
+  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
+endfunc
+
+" list with funcref
+func Test_filter_map_list_expr_funcref()
+  " filter()
+  func! s:filter1(index, val) abort
+    return a:val > 1
+  endfunc
+  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
+
+  func! s:filter2(index, val) abort
+    return a:index > 1
+  endfunc
+  call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
+
+  " map()
+  func! s:filter3(index, val) abort
+    return a:val * 2
+  endfunc
+  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
+
+  func! s:filter4(index, val) abort
+    return a:index * 2
+  endfunc
+  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+endfunc
+
+" dict with funcref
+func Test_filter_map_dict_expr_funcref()
+  let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+  " filter()
+  func! s:filter1(key, val) abort
+    return a:val > 1
+  endfunc
+  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
+
+  func! s:filter2(key, val) abort
+    return a:key > "bar"
+  endfunc
+  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
+
+  " map()
+  func! s:filter3(key, val) abort
+    return a:val * 2
+  endfunc
+  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
+
+  func! s:filter4(key, val) abort
+    return a:key[0]
+  endfunc
+  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+endfunc
diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim
--- a/src/testdir/test_partial.vim
+++ b/src/testdir/test_partial.vim
@@ -14,6 +14,14 @@ func MySort(up, one, two)
   return a:one < a:two ? 1 : -1
 endfunc
 
+func MyMap(sub, index, val)
+  return a:val - a:sub
+endfunc
+
+func MyFilter(threshold, index, val)
+  return a:val > a:threshold
+endfunc
+
 func Test_partial_args()
   let Cb = function('MyFunc', ["foo", "bar"])
 
@@ -36,6 +44,16 @@ func Test_partial_args()
   call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
   let Sort = function('MySort', [0])
   call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
+
+  let Map = function('MyMap', [2])
+  call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
+  let Map = function('MyMap', [3])
+  call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
+
+  let Filter = function('MyFilter', [1])
+  call assert_equal([2, 3], filter([1, 2, 3], Filter))
+  let Filter = function('MyFilter', [2])
+  call assert_equal([3], filter([1, 2, 3], Filter))
 endfunc
 
 func MyDictFunc(arg1, arg2) dict
@@ -60,6 +78,9 @@ func Test_partial_dict()
   call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
   call assert_fails('Cb("fff")', 'E492:')
 
+  let Cb = function('MyDictFunc', dict)
+  call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
+
   let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
   call assert_equal("Hello", dict.tr())
 endfunc

Raspunde prin e-mail lui