Is there a name for this tuple-creation idiom?
Fun with Lambdas: C++14 Style (part 3)
Fun with Lambdas: C++14 Style (part 3)
#include <iostream>
auto List = [](auto ...xs) {
return [=](auto access) { return access(xs...); };
};
auto length = [](auto xs) {
return xs([](auto ...z) { return sizeof...(z); });
};
auto fmap = [](auto func) {
return [func] (auto alist) {
return alist([func](auto... xs) { return List(func(xs)...); });
};
};
int main()
{
std::cout << length(List(1, '2', "3")); // 3
auto twice = [](auto i) { return 2*i; };
auto print = [](auto i) { std::cout << i << " "; return i; };
auto l1 = List(1, 2, 3, 4);
auto l2 = fmap(twice)(l1);
auto l3 = fmap(print)(l2); // 8 6 4 2
}
代码说明
- 这里List是一个泛型lambda,它接收一个变长的参数包xs,并返回一个闭包[=](auto access) { return access(xs...); }。通过值捕获,这个lambda在它所返回的闭包中存放了参数包xs中所有值的拷贝。也可以说,这个闭包中存放了参数包xs的拷贝。比如List(1, '2', "3")就是一个存放了1, '2', "3"这三个值的闭包,而List(1, 2, 3, 4)则是一个存放了四个整数的闭包。
- 现在我们把参数包xs看成一个tuple(元组)。(为什么可以把变长参数包看成元组?这是因为变长参数包与元组有很多共性:和元组一样,每一个具体的参数包的长度实际上是固定的,各个参数的类型可以不同,参数包的类型由各个参数的类型和顺序决定。)这样List(xs)这个闭包就可以看成是一个存放了tuple的闭包。也可以说这个闭包本身就可以被看成一个tuple。
- 如果我们把List(xs)这个闭包看成一个tuple,那么这个闭包就成了一个类似tuple的数据结构,而List也就成了创建这种数据结构的构造器。比如List(1, '2', "3")就成了一个类似tuple<int, char, const char*>(1, '2', "3")的数据结构,其作用相当于make_tuple(1, '2', "3")。可以看出这是一种借助于“将lambda表达式展开成函数对象”这种编译器的魔法来创建tuple的新的惯用法。
- 除了可以被看成一个tuple,List(xs)本身还是一个闭包。这个闭包接收一个名为access的函数(也可以是其他可调用的实体,比如lambda表达式和函数对象)参数,在函数体内将自身所存放的参数包作为参数传递给access函数,然后返回access函数的返回值。
- 下面看看List(xs)这种新的tuple有什么具体应用。
length函数返回List所带参数包xs的长度。
fmap函数使用List所带参数包xs的各个参数值调用某个函数,并将所有的返回值重新组装成List。 - length(List(1, '2', "3"))
= List(1, '2', "3")([](auto ...z) { return sizeof...(z); })
= [](auto ...z) { return sizeof...(z); }(1, '2', "3")
= sizeof...(pack(1, '2', "3"))
= 3 - l2 = fmap(twice)(l1)
= l1([](auto... xs) { return List(twice(xs)...); })
= List(1, 2, 3, 4)([](auto... xs) { return List(twice(xs)...); })
= [](auto... xs) { return List(twice(xs)...); }(1, 2, 3, 4)
= List(twice(pack(1, 2, 3, 4))...)
= List(twice(1), twice(2), twice(3), twice(4))
= List([](auto i) { return 2*i; }(1), [](auto i) { return 2*i; }(2), [](auto i) { return 2*i; }(3), [](auto i) { return 2*i; }(4))
= List(2, 4, 6, 8)
#include <iostream>
auto List = [](auto ...xs) {
return [=](auto access) { return access(xs...); };
};
auto fmap = [](auto func) {
return [func] (auto alist) {
return alist([func](auto... xs) { return List(func(xs)...); });
};
};
auto concat = [](auto l1, auto l2) {
return l1([=](auto... p) {
return l2([=](auto... q) {
return List(p..., q...);
});
});
};
template <class Func>
auto flatten(Func)
{
return List();
}
template <class Func, class A, class... B>
auto flatten(Func f, A a, B... b)
{
return concat(f(a), flatten(f, b...));
}
auto flatmap = [](auto func) {
return [func](auto alist) {
return alist([func](auto... xs) { return flatten(func, xs...); });
};
};
int main()
{
auto pair = [](auto i) { return List(-i, i); };
auto print = [](auto i) { std::cout << i << " "; return i; };
auto l1 = List(1, 2, 3);
auto l2 = flatmap(pair)(l1);
auto l3 = fmap(print)(l2); // 3 -3 2 -2 1 -1
}
代码说明
- 这段代码为List(xs)这种新的tuple实现了一个新的操作flatmap。
与fmap函数一样,flatmap函数也将使用List所带参数包xs的各个参数值调用某个函数,并将所有的返回值重新组装成List。
但是与fmap函数有所不同的是,每次调用函数所返回的并不是单个值,而是一个List,所以实际上flatmap还需要将所有返回的List平坦化,然后再重新组装成List。 - flatmap调用flattern函数来实际完成调用函数及平坦化任务,而flattern函数则通过递归调用自身及concat函数来完成平坦化任务。
- 这里concat函数负责将两个List连接为一个List。
concat(List(-1, 1), List(-2, 2))
= List(-1, 1)([](auto... p){ return List(-2, 2)([](auto... q){List(p..., q...); }); })
= [](auto... p){ List(-2, 2)([](auto... q){ return List(p..., q...); }); }(-1, 1)
= List(-2, 2)([](auto... q){ return List(-1, 1, q...); })
= [](auto... q){ return List(-1, 1, q...); }(-2, 2)
= List(-1, 1, -2, 2)
- l2 = flatmap(pair)(l1)
= l1([](auto... xs){return flatten(pair, xs...);})
= List(1, 2, 3)([](auto... xs){return flatten(pair, xs...);})
= [](auto... xs){return flatten(pair, xs...);}(1, 2, 3)
= flatten(pair, 1, 2, 3)
= concat(pair(1), flattern(pair, 2, 3))
= concat(pair(1), (concat(pair(2), flattern(pair, 3))))
= concat(pair(1), (concat(pair(2), (concat(pair(3), flattern(pair))))))
= concat(pair(1), (concat(pair(2), (concat(pair(3), List())))))
= concat([](auto i){return List(-i, i);}(1), (concat([](auto i){return List(-i, i);}(2), (concat([](auto i){return List(-i, i);}(3), List())))))
= concat(List(-1, 1), (concat(List(-2, 2), (concat(List(-3, 3), List())))))
= concat(List(-1, 1), (concat(List(-2, 2), List(-3, 3))))
= concat(List(-1, 1), List(-2, 2, -3, 3))
= List(-1, 1, -2, 2, -3, 3)