前言

本文将介绍如何使用C++11模板元编程实现Scheme中的list及相关函数式编程接口,如listconscarcdrlengthis_emptyreverseappend,maptransformenumeratelambda等。

预备知识

Scheme简介

Scheme语言是lisp>语言的一个方言(或说成变种),它诞生于1975年的MIT,对于这个有近三十年历史的编程语言来说,它并没有象 C++,java,C#那样受到商业领域的青睐,在国内更是鲜为人知。但它在国外的计算机教育领域内却是有着广泛应用的,有很多人学的第一门计算机语言就 是Scheme语言(SICP就是以Scheme为教学语言)。

它是一个小巧而又强大的语言,作为一个多用途的编程语言,它可以作为脚本语言使用,也可以作为应用软件的扩展语言来使用,它具有元语言特性,还有很多独到的特色,以致于它被称为编程语言中的”皇后”。

如果你对Scheme感兴趣,推荐使用drracket这个GUI解释器,入门教程有:How to Design Programs,高级教程有:SICP。

Scheme中的list及相关操作

list可以说是Lisp系语言的根基,其名就得自于**LIS**t **P**rocessor,其重要性就像文件概念之于unix。

list示例:

> (list "red" "green" "blue")
'("red" "green" "blue")
> (list 1 2 3)
'(1 2 3)

上面的语法糖list其实是通过递归调用点对cons实现的,因此上面的语法等价于:

> (cons "red" (cons "green" (cons "blue" empty)))
'("red" "green" "blue")
> (cons 1 (cons 2 (cons 3 empty)))
'(1 2 3)

另外两个重要的点对操作是carcdr,名字有点奇怪但是是有历史的:**C**urrent **A**ddress **R**egister and **C**urrent **D**ecrement **R**egister,其实就相当于firstsecond的意思。

(car (cons 1 2))  ==> 1
(cdr (cons 1 2))  ==> 2

模板元编程

C++中的Meta Programming,即模板元编程,是图灵完备的,而且是编译期间完成的。模板元编程通常用于编写工具库,如STL、Boost等。

比如通常我们使用递归来实现实现阶乘:

#include <iostream>unsigned int factorial(unsigned int n) {return n == 0 ? 1 : n * factorial(n - 1);
}int main() {std::cout << factorial(5) << std::endl;return 0;
}

我们也可以通过模板元编程来实现:

#include <iostream>template <unsigned int n>
struct factorial {static constexpr unsigned int value = n * factorial<n - 1>::value;
};template <>
struct factorial<0> {static constexpr unsigned int value = 1;
};int main() {std::cout << factorial<5>::value << std::endl; // 120return 0;
}

实现

本文完整代码可以在这里查看:点击查看代码

基本数据结构

为了用模板元编程来模拟Scheme中list即相关操作,我们需要先定义一些模板数据结构。这些数据结构非常简单,即重新定义基本数据类型,为了简化,在这里我只特化了必须的intuintbool以及empty的实现。empty是递归实现list的最后一个元素,其作用相当于'\0'之于字符串。

// type_
//
template <typename T, T N>
struct type_ {using type = type_<T, N>;using value_type = T;static constexpr T value = N;
};// int_
//
template <int N>
struct int_ {using type = int_<N>;using value_type = int;static constexpr int value = N;
};// uint_
//
template <unsigned int N>
struct uint_ {using type = uint_<N>;using value_type = unsigned int;static constexpr unsigned int value = N;
};template <>
struct uint_<0> {using type = uint_<0>;using value_type = unsigned int;static constexpr unsigned int value = 0;
};// bool_
template <bool N>
struct bool_ {using type = bool_<N>;using value_type = bool;static constexpr bool value = N;
};// empty
//
struct empty {using type = empty;using value = empty;
};

下面我们先来个小示例,看看怎么使用这些模板数据结构。这个示例的作用是将仅仅用0和1表示的十进制数字当成二进制看,转换为十进制数值。如:101 转换为十进制数值为 5.

template <unsigned int N>
struct binary : uint_ < binary < N / 10 >::type::value * 2 + (N % 10) > {};template <>
struct binary<0> : uint_<0> {};

测试示例:

std::cout << binary<101>::value << std::endl;               // 5

cons & car & cdr实现

cons的实现原理很简单:就是能够递归调用自己结合成点对pair。在Scheme中示例如下:

(cons 1 (cons 2 (cons 3 '())))

其中'()表示空的点对pair,在我们的实现里面就是empty

因此cons用C++元编程实现就是:

template <typename h, typename t>
struct cons {using type = cons<h, t>;using head = h;using tail = t;
};

使用示例:

std::cout << cons<int_<1>, int_<2>>::head::value << std::endl;  // 1

同样,我们可以实现用于获取headcar与获取tailcdr操作:

struct car_t {template <typename cons>struct apply {using type = typename cons::type::head;};
};template <typename cons>
struct car : car_t::template apply<cons>::type {};struct cdr_t {template <typename cons>struct apply {using type = typename cons::type::tail::type;};
};template <typename cons>
struct cdr : cdr_t::template apply<cons>::type {};

使用示例:

    using c1 = cons<int_<1>, cons<int_<2>, int_<3>>>;std::cout << car<c1>::value << ", " << cdr<c1>::head::value << std::endl; // 1, 2std::cout << car<c1>::value << ", " << car<cdr<c1>>::value << std::endl; // 1, 2

对于上面的实现,稍微解释一下:car是对car_t的封装,这样使用起来更为方便,对比如下用法就能明了,后面这样的封装手法还会用到:

car<cons<int_<1>, int_<2>>::value                   // == 1
car_f::template apply<cons<int_<1>, int_<2>>::value // == 1

list的实现

list其实一种特殊的cons,其实现如下:

template <typename first = empty, typename ...rest>
struct list_t : std::conditional <sizeof...(rest) == 0,cons<first, empty>,cons<first, typename list_t<rest...>::type>>::type
{};template <>
struct list_t<empty> : empty {};template <typename T, T ...elements>
struct list : list_t<type_<T, elements>...> {};

这里用到了C++11中的变长模板参数,std::conditional以及对empty的特化处理。
- 变长模板参数:list接收变长模板参数elements,然后封装类型为成type_的变长模板参数forward给list_t
- std::conditional:相当于if ... else ...,如果第一参数为真,则返回第二参数,否则返回第三参数;
- <第一参数中的code>sizeof…(rest):sizeof是C++11的新用法,用于获取变长参数的个数;
- 第二参数的作用是终止递归;
- 第三参数的作用是递归调用list_t构造点对。
- 因为empty比较特殊,所以需要特化处理

使用示例:

    using l1 = list<int, 1, 2, 3>;using l2 = list<int, 4, 5, 6, 7>;using l3 = list_t<int_<1>, int_<2>, int_<3>>;std::cout << "\n>list" << std::endl;print<l1>();    // 1, 2, 3print<l3>();    // 1, 2, 3std::cout << car<l1>::value << ", " << cdr<l1>::head::value << std::endl;   // 1, 2

length & is_empty的实现

先来看看length的实现,其思路与list的实现一样:递归调用自身,并针对empty特化处理。

template <typename list>
struct length_t
{static constexpr unsigned int value =1 + length_t<typename cdr<list>::type>::value;
};template <>
struct length_t<empty>
{static constexpr unsigned int value = 0;
};template <typename list>
struct length
{static constexpr unsigned int value = length_t<typename list::type>::value;
};

is_empty可以简单实现为判断length为0:

template <typename list>
struct is_empty
{static constexpr bool value = (0 == length<list>::value);
};

当然这样的实现效率并不高,因此可以通过对list以及empty的特化处理来高效实现:

template <typename list>
struct is_empty_t
{static constexpr bool value = false;
};template <>
struct is_empty_t<empty>
{static constexpr bool value = true;
};template <typename list>
struct is_empty
{static constexpr bool value = is_empty_t<typename list::type>::value;
};

使用示例:

    std::cout << "is_empty<empty> : " << is_empty<empty>::value << std::endl;            // 1std::cout << "is_empty<list<int>> : " << is_empty<list<int>>::value << std::endl;    // 1std::cout << "is_empty<list<int, 1, 2, 3>> : " << is_empty<l1>::value << std::endl;  // 0std::cout << "length<empty> : " << length<empty>::value << std::endl;                // 0std::cout << "length<list<int>> : " << length<list<int>>::value << std::endl;        // 0std::cout << "length<list<int, 1, 2, 3>> : " << length<l1>::value << std::endl;      // 3

append & reverse的实现

append是将一个列表list2追加到已有列表list1的后面,其实现思路是递归地将car当做head,然后将cdr作为新的list1递归调用append。不要忘记特化empty的情况。

struct append_t {template <typename list1, typename list2>struct apply : cons<typename car<list1>::type,typename append_t::template apply<typename cdr<list1>::type, list2>::type>{};template<typename list2>struct apply <empty, list2>: list2{};
};template <typename list1, typename list2>
struct append : std::conditional <is_empty<list1>::value,list2,append_t::template apply<list1, list2>>::type
{};

reverse的实现思路与append类似,只不过是要逆序罢了:

struct reverse_t {template <typename reset, typename ready>struct apply : reverse_t::template apply<typename cdr<reset>::type,cons<typename car<reset>::type, ready>>{};template<typename ready>struct apply <empty, ready> : ready{};
};template <typename list>
struct reverse : std::conditional <is_empty<list>::value,list,reverse_t::template apply<typename list::type, empty>>::type
{};

使用示例:

    // reverseusing r1 = reverse<l1>;using r2 = reverse<list<int>>;print<r1>();    // 3, 2, 1print<r2>();// appendusing a1 = append<l1, l2>;using a2 = append<l1, list<int>>;using a3 = append<list<int>, l1>;print<a1>();    // 1, 2, 3, 4, 5, 6, 7print<a2>();    // 1, 2, 3print<a3>();    // 1, 2, 3

函数式编程

lisp系语言的最大特性就是支持函数式编程,它能够把无差别地对待数据与函数,实现了对数据与代码的同等抽象。下面我们来添加对函数式编程的支持:enumerate, mapapply, lambda 以及 transform

map的实现

map的语义是迭代地将某个方法作用于列表中的每个元素,然后得到结果list。先来定义一些辅助的方法:

template <typename T, typename N>
struct plus : int_ < T::value + N::value > {};template <typename T, typename N>
struct minus : int_ < T::value - N::value > {};struct inc_t {template <typename n>struct apply : int_ < n::value + 1 > {};
};template <typename n>
struct inc : int_ < n::value + 1 > {};

下面来看map的实现

struct map_t {template <typename fn, typename list>struct apply : cons <typename fn::template apply<typename car<list>::type>,map_t::template apply<fn, typename cdr<list>::type>>{};template <typename fn>struct apply <fn, empty>: empty{};
};template <typename fn, typename list>
struct map : std::conditional <is_empty<list>::value,list,map_t::template apply<fn, list>>::type
{};

使用示例:

    using m1 = map<inc_t, list<int, 1, 2, 3>>;using m2 = map<inc_t, list<int>>;print<m1>();    // 2, 3, 4print<m2>();

为了让map支持形如inc这样的模板类,而不仅仅是形如inc_t,我们需要定义一个转换器:lambda

struct apply_t {template <template <typename...> class F, typename ...args>struct apply : F<typename args::type...> {};template <template <typename...> class F>struct apply <F, empty> : empty {};
};template <template <typename...> class F, typename ...args>
struct apply : apply_t::template apply<F, args...> {};template <template <typename...> class F>
struct lambda {template <typename ...args>struct apply : apply_t::template apply<F, args...> {};
};

使用示例:

    std::cout << lambda<inc>::template apply<int_<0>>::value << std::endl;  // 1using ml1 = map<lambda<inc>, list<int, 1, 2, 3>>;print<ml1>();  // 2, 3, 4

transform的实现

transform的语义是对迭代地将某个方法作用于两个列表上的元素,然后得到结果list

struct transform_t {template <typename list1, typename list2, typename fn>struct apply : cons <typename fn::template apply<typename car<list1>::type, typename car<list2>::type>::type,typename transform_t::template apply <typename cdr<list1>::type, typename cdr<list2>::type, fn>::type> {};template <typename list1, typename fn>struct apply<list1, empty, fn> : cons <typename fn::template apply<typename car<list1>::type, empty>,typename transform_t::template apply <typename cdr<list1>::type, empty, fn>::type> {};template <typename list2, typename fn>struct apply<empty, list2, fn> : cons <typename fn::template apply<empty, typename car<list2>::type>::type,typename transform_t::template apply <empty, typename cdr<list2>::type, fn>::type> {};template <typename fn>struct apply<empty, empty, fn> : empty {};
};template <typename list1, typename list2, typename fn>
struct transform : std::conditional <is_empty<list1>::value,list1,transform_t::template apply<list1, list2, fn>>::type
{static_assert(length<list1>::value == length<list2>::value, "transform: length of lists mismatch!");
};

其实现是最为复杂的,我们先来看使用示例,再来讲解实现细节。

    using t1 = list<int, 1, 2, 3>;using t2 = list<int, 3, 2, 1>;using ml = transform<t1, t2, lambda<minus>>;using pl = transform<t1, t2, lambda<plus>>;using te = transform<list<int>, list<int>, lambda<plus>>;using el = transform<t1, list<int>, lambda<plus>>;print<ml>();    // -2, 0, 2print<pl>();    // 4, 4, 4print<te>();// print<el>(); // assertion: length mismatch!

实现细节:

  • 使用C++11新特性static_assert对两个列表的长度相等做断言;
  • 使用std::conditional处理空列表,如果非空forward给transform_t
  • transform_t特化处理空列表的情况;
  • 如果list1list2均非空,那么通过car取出两个列表的head作用于方法,然后递归调用transform_t作用于两个列表的tail

enumerate的实现

enumerate的语义是迭代将某个方法作用于列表元素。

template <typename fn, typename list, bool is_empty>
struct enumerate_t;template <typename fn, typename list>
void enumerate(fn f)
{enumerate_t<fn, list, is_empty<list>::value> impl;impl(f);
}template <typename fn, typename list, bool is_empty = false>
struct enumerate_t
{void operator()(fn f) {f(car<list>::value);enumerate<fn, typename cdr<list>::type>(f);}
};template <typename fn, typename list>
struct enumerate_t<fn, list, true>
{void operator()(fn f) {// nothing for empty}
};

enumerate的实现与之前的map的实现很不一样,它是通过模板方法与函数子来实现的。模板方法内部调用函数子来做事情,函数子又是一个模板类,并对空列表做了特化处理。

使用示例:

    using value_type = typename car<e1>::value_type;auto sqr_print = [](value_type val) { std::cout << val * val << " "; };enumerate<decltype(sqr_print), e1>(sqr_print);      // 1 4 9

equal的实现

equal用于判断两个列表是否等价。

struct equal_t {// both lists are not emptytemplate <typename list1, typename list2, int empty_value = 0,typename pred = lambda<std::is_same>>struct apply : std::conditional <!pred::template apply <typename car<list1>::type,typename car<list2>::type>::type::value,bool_<false>,typename equal_t::template apply <typename cdr<list1>::type,typename cdr<list2>::type,(is_empty<typename cdr<list1>::type>::value+ is_empty<typename cdr<list2>::type>::value),pred>::type> {};// one of the list is empty.template <typename list1, typename list2, typename pred>struct apply<list1, list2, 1, pred>: bool_<false>{};// both lists are empty.template <typename list1, typename list2, typename pred>struct apply<list1, list2, 2, pred>: bool_<true>{};
};template <typename list1, typename list2, typename pred = lambda<std::is_same>>
struct equal : equal_t::template apply<list1, list2,(is_empty<list1>::value + is_empty<list2>::value), pred>::type
{};

equal的实现也有点复杂。

  • pred是等价比较谓词,默认是使用std::is_same来做比较;
  • 关键部分依然是通过std::conditional来实现的;
  • 第一参数是判断两个列表的head是否相等;
  • 如果不等就返回第二参数;
  • 如果相等就递归比较两个列表的剩余元素;
  • 这里使用了一个小小的技巧来简化模板类特化的情况:如果其中一个列表为空,那么empty_value为1;如果两个列表均为空,那么empty_value为2,这两种情况都会调用特化版本。

使用示例:

    using e1 = list<int, 1, 2, 3>;using e2 = list<int, 1, 2, 3>;using e3 = list<int, 1, 2, 1>;std::cout << "equal<e1, e2> : " << equal<e1, e2>::value << std::endl;   // 1std::cout << "equal<e1, e3> : " << equal<e1, e3>::value << std::endl;   // 0std::cout << "equal<e1, list<int>> : " << equal<e1, list<int>>::value << std::endl; // 0std::cout << "equal<list<int>, e1> : " << equal<list<int>, e1>::value << std::endl; // 0std::cout << "equal<list<int>, list<int>> : " << equal<list<int>, list<int>>::value << std::endl;   // 1

print的实现

print是依次打印列表元素,也可以使用enumerate来实现:

template <typename list, bool is_empty>
struct print_t;template <typename list>
void print()
{print_t<list, is_empty<list>::value> impl;impl();
}template <typename list, bool is_empty = true>
struct print_t
{void operator()() {std::cout << std::endl;}
};template <typename list>
struct print_t<list, false>
{void operator()() {std::cout << car<list>::value;using rest = typename cdr<list>::type;if (false == is_empty<rest>::value) {std::cout << ", ";}print<rest>();}
};

print的实现思路与enumerate,是通过模板方法与函数子来实现的。模板方法内部调用函数子来做事情,函数子又是一个模板类,并对空列表做了特化处理。

总结

C++尤其是C++11,14,17等新特性使得这把实用的瑞士军刀越发锋利与实用,虽然实现的形式上不如SchemePython等优雅,但它确实能够,而且无需获得语言层面上的支持。纸上得来终觉浅,绝知此事要躬行。看过本文的读者不妨自己实现一番本文中的提到的相关概念。

参考阅读

《计算机程序的构造与解释》

基于C++11模板元编程实现Scheme中的list及相关函数式编程接口相关推荐

  1. 编程软件python中的if用法-python 函数式编程工具

    1.Lambda函数 Python使用lambda支持在运行时创建匿名函数(没有绑定名称的函数). >>> g=lambda x:x+2 >>> >>& ...

  2. Python中的匿名函数和函数式编程

    Python中的匿名函数和函数式编程 文章目录 Python中的匿名函数和函数式编程 一.匿名函数 匿名函数的格式: 二.函数式编程 map() filter() reduce() 区别 三.'三目运 ...

  3. C++11模板元编程—std::enable_if使用说明

    std::enable_if 顾名思义,满足条件时类型有效.作为选择类型的小工具,其广泛的应用在 C++ 的模板元编程中.它的定义也非常的简单: // STRUCT TEMPLATE enable_i ...

  4. python函数式编程读取数据-python学习笔记9:函数式编程

    函数式编程(FunctionalProgramming) 基于lambda演算的一种编程方式 程序中只有函数 函数可以作为参数,同样可以作为返回值 纯函数式编程语言: LISP, Haskell Py ...

  5. 编程软件python中的if用法-总结Python编程中函数的使用要点

    为何使用函数 最大化代码的重用和最小化代码冗余 流程的分解 编写函数 >>def语句 在Python中创建一个函数是通过def关键字进行的,def语句将创建一个函数对象并将其赋值给一个变量 ...

  6. java8 函数式编程_您必须学习Java 8的函数式编程吗?

    java8 函数式编程 我最近一直在研究Java 8,并掌握了Manning出版的" Java 8 In Action" . 让我印象深刻的第一件事是Java 8独特的销售主张是函 ...

  7. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 1 章:为什么使用函数式编程?...

    为什么80%的码农都做不了架构师?>>>    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS> ...

  8. 小程序高级电商前端第2周深入理解REST API开发规范 开启三端分离编程之旅<一>----优惠券、函数式编程、重构Theme

    优惠券的一些基本概念: 在上一次小程序高级电商前端第1周走进Web全栈工程师<四>----自定义组件与Lin UI的小试牛刀已经使用Lin-UI完成了商品类别的展示,接下来则来实现它了: ...

  9. android xml java混合编程_Java学习中注解与多线程,网络编程与XML技术

    本部分内容主要有集合框架及泛型,实用类,输入和输出处理,注解与多线程,网络编程与XML技术.初次学习这部分会感觉很难,主要是概念难于理解,最好是多看看例子,多练习.下面是个人的总结 拉勾IT课小编为大 ...

最新文章

  1. 配置flutter For IOS
  2. 200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]
  3. P3181-[HAOI2016]找相同字符【SAM】
  4. 工业交换机的几大“择机”标准,你学会了吗?
  5. 自动化审批决策树助你面试更上一层楼
  6. KCdoes NetUSB 严重漏洞影响多家厂商的数百万台路由器
  7. 线程池ThreadPoolExecutor使用
  8. 整理python小爬虫
  9. 线程相关函数(1)-pthread_create(), pthread_join(), pthread_exit(), pthread_cancel() 创建取消线程
  10. python爬虫音乐犯法么_Python爬虫实战之爬取QQ音乐数据!QQ音乐限制太多了
  11. 普通高中信息技术课程标准( 必修 选修科目)
  12. python量化交易策略技巧与实战_量化交易策略基本框架
  13. matlab中怎么正弦计算,matlab计算结果中的正弦余弦问题
  14. hive日期函数,求日期差等,datediff,date_add,date_sub,add_months
  15. wps在线编辑梳理(此处整理了对接过后容易出错的地方)
  16. 全民都是评选专家,提前一天泄露 2020 博客之星最终结果
  17. Libgdx粒子效果介绍与使用心得
  18. 基于YOLOv3的车辆号牌定位算法【文末送书】
  19. 「涪陵榨菜」使用区块链溯源系统?回应:看榨菜集团的安排
  20. 实时系统vxWorks - timer定时应用

热门文章

  1. ESP8266使用blinker WiFi接入
  2. 英语写作替换词与上下文连接词
  3. Linux之SSH远程执行多条命令
  4. Java程序员面试为什么失败?面试挂掉的5大原因
  5. 40 岁的中年失业人怎么活下去?,android开发环境搭建
  6. android 耳机数据线,如果买安卓手机不送充电器和耳机?你还买不买
  7. Z3求解器指南(一)
  8. Z3求解器指南(二)
  9. 家庭用户适宜选择什么样的计算机,电脑内存越大越好吗 怎么选择适合自己电脑内存【详细介绍】...
  10. android 适配全面屏手机