[入门] lambda 之路

qiezi 2007-11-09
DMD最近的版本号加入了闭包,感觉非常有用,虽然有些背后动作,不过我是实用派不介意这个。玩的时候忽然想到为什么没有lambda呢?AST还没影,不过可以利用D强大的模板可以使用字符串来先模拟一下。

我假想的语法是这样的:
int[] arr = [1,2,3];
int[] arr1 = arr.map(lambda!("int x -> x * x"));


上面执行的arr1结果将会是[1,4,9]。

在编写过程中发现匿名委托不能够使用模板来这样生成:
template labmda(string expr)
{
    auto lambda = mixin("(int x){return (x);}");
}

所以必须在使用的地方去mixin(如果你可以避免这个请告诉我),像这样的:

int[] arr1 = arr.map(mixin("(int x){return (x);}"));

测试很长时间没有找到突破方法去掉这个mixin,所以就在这个起点上向目标前进了。

测试过程中也发现现在的模板对于字符串参数似乎不像以前那么友好了,编译期执行函数则得到了加强,所以使用它来实现:
int indexof(string s, char ch)
{
	foreach(i, c; s)
		if(c == ch)
			return i;
	return -1;
}

int indexof(string s, string sub)
{
	for(int i=0; i<s.length-sub.length; i++)
	{
		if (s[i..i+sub.length] == sub)
			return i;
	}
	return -1;
}

string _RetType(string expr)
{
	int i = indexof(expr, '|');
	if (i == -1)
		return "";
	else
		return expr[0..i];
}

string _ArgTypes(string expr)
{
	int start = indexof(expr, '|');

	int end = indexof(expr, "->");
	assert(end > 0);

	return expr[start+1 .. end];
}

string _Body(string expr)
{
	int start = indexof(expr, "->");
	if (start == -1)
		return expr;

	return expr[start+2 .. $];
}

string lambda(string expr)
{
	return
		"delegate " ~ _RetType(expr) ~ 
		"(" ~ _ArgTypes(expr) ~ "){return (" ~
		_Body(expr) ~ ");}";
}

它支持的lambda语法是这样的:
//参数列表 -> 表达式:
int x -> x
int x, int y -> x * y

//返回值 | 参数列表 -> 表达式:
string | int x -> toString(x)

测试:
T[] map(T,T1)(T1[] arr, T delegate(T1) dg)
{
	T[] result;
	result.length = arr.length;
	foreach(i, v; arr)
		result[i] = dg(v);
	return result;
}

void main()
{
	int[] arr = [1,2,3];
	// int[] arr1 = arr.map(int x -> x * x);
	int[] arr1 = arr.map(mixin(lambda("int x -> x * x")));
	writefln(arr1);

	string sep = ";\n";
	string[] arr2 = arr.map(mixin(lambda("string | int x -> toString(x) ~ sep")));
	writefln(arr2);
}

运行结果:
引用

[3 6 9]
[1;
2;
3;
]

语法罗嗦了一些,要是有AST宏不知道是否能简化一些。。
qiezi 2007-11-09
加一个例子:
T1 inject(T,T1)(T[] arr, T1 init, T1 delegate(T1, T) dg)
{
	T1 value = init;
	foreach(e; arr)
		value = dg(value, e);
	return value;
}

void main()
{
	int[] arr = [1,2,3];

	// int total = arr.inject(0, (int sum, int elem) -> sum + elem);
	int total = arr.inject(0, mixin(lambda("int sum, int elem -> sum + elem")));
	writefln(total);
}


运行得到的结果是6.

对应的ruby代码:
total = [1,2,3].inject(0){|sum, elem| sum + elem}


从这个例子的注释中可以看到,lambda的参数应该用括号包起来,和参数加以区别;而单参数的lambda则应该省掉括号让它更美观,我上面的解析部分还不包括括号的解析。
qiezi 2007-11-09
忽然发现好久没写blog了,弄过去充一下数。。这边删掉。
Global site tag (gtag.js) - Google Analytics