17 | 函数式编程:一种越来越流行的编程范式
17 | 函数式编程:一种越来越流行的编程范式
讲述:吴咏炜
时长13:16大小12.12M
一个小例子
函数式编程的特点
高阶函数
命令式编程和说明式编程
不可变性和并发
Y 组合子
内容小结
课后思考
参考资料
赞 1
提建议
精选留言(18)
- 驰骋2020-01-07Y-Combinator被你说高深了。递归就是自己调用自己。lamda表达式想递归,困难在于不知道自己的函数名,怎么办?调用不了自己,难道还调用不了别人。所以lamda表达式调用了Y-Combinator去间接调用自己,而Y-Combinator只不过:一,记录lamda表达式;二,转调lamda表达式。这就好比普京受制于连任时间限制,如果想继续连任,则找个代言人Y-Combinator继任。代言人的唯一作用就是到期传位普京。
作者回复: 哈哈,有意思的比喻。以前学过函数式编程?
共 2 条评论46 - 罗 乾 林2020-01-03参考 istream_line_reader 实现的,望老师斧正 template<typename _InIt, typename _Fun> class filter_view { public: class iterator { // 实现 InputIterator public: using iterator_category = input_iterator_tag; using value_type = typename _InIt::value_type; using difference_type = typename _InIt::difference_type; using pointer = typename _InIt::pointer; using reference = value_type&; iterator(_InIt _First, _InIt _Last, _Fun f) :_First(_First), _Last(_Last), _fun(f) { ++(*this); } reference operator*() const noexcept { return *_Cur; } pointer operator->() const noexcept { return &(*_Cur); } iterator& operator++() { while (_First != _Last && !_fun(*_First)) { _First++; } _Cur = _First; if (_First != _Last) { _First++; } return *this; } iterator operator++(int) { iterator temp(*this); ++(*this); return temp; } bool operator==(const iterator& rhs) const noexcept { return _Cur == rhs._Cur; } bool operator!=(const iterator& rhs) const noexcept { return !operator==(rhs); } private: _InIt _First; _InIt _Last; _InIt _Cur; _Fun _fun; }; filter_view(_InIt _First, _InIt _Last, _Fun f) :_First(_First), _Last(_Last), _fun(f) { } iterator begin() const noexcept { return iterator(_First, _Last, _fun); } iterator end() const noexcept { return iterator(_Last, _Last, _fun); } private: _InIt _First; _InIt _Last; _Fun _fun; };展开
作者回复: OK,没啥大问题。 代码风格要稍微说明一下。你似乎是模拟了库代码的风格,这还是有点风险的。在一般的用户代码里,不应该出现双下划线打头、或者下划线加大写字母打头的标识符——这是给系统保留的。 详见: https://zh.cppreference.com/w/cpp/language/identifiers
共 4 条评论5 - Frank2021-03-31吴老师,为什么c++不能在返回值优化,支持把rvalue通过move构造给一个lvalue引用。我想实现模板中的协变,函数返回local variable,只能通过声明返回值类型为引用和指针才能协变支持返回值多态。把一个生命周期短的variable自动move给返回值不是应该的吗?这个场景在工厂场景下应该很常见吧。 class Base{ } class Derive : public Base {} class Factory { Bae & create(){ return Base(); } class SubFactory : public Factory{ Base & create (){ return Derive(); } } 这里的返回值类型也有可能是泛型参数,现在看来只能用智能指针包一层,通过move构造返回,这看起来不太方便。 }展开
作者回复: 首先,按你目前代码的写法(非virtual方法),把create函数的返回值改成 Base 和 Derived 是可以工作的。即,如果能做到静态多态的话,各个函数返回实际类型即可。 可是,如果按照一般的做法,Base 加上虚析构,create 标成 virtual 的话,情况就不一样了。虚函数必须返回同样的类型,或者如果是指针/引用类型的话,按“协变”的要求,覆写的虚函数返回的指针/引用是被覆写的虚函数的返回类型的子类。因为虚函数被调用时,编译器是不知道实际调用的是哪个函数,如果返回非指针/引用的子类对象的话,编译器连怎么拷贝、移动这个对象都不知道了……
共 6 条评论2 - 泰伦卢2020-01-03请问老师,map和reduce.那是最新的语句吗?还是有第三方库?那个TBB?
作者回复: map-reduce 是一种方法,已经有很久了。在 C++ 里的直接对应是 transform 和 accumulate。TBB 见参考资料 [7]。
2 - geek2021-03-17课后思考的一种实现,请老师指正一下其中存在的问题。 template <typename T = std::vector<int>> class filter_view { public: filter_view(){} filter_view(typename T::iterator begin, typename T::iterator end, std::function<bool(int)> func):begin_(begin),end_(end),func_(func){} class iterator { public: iterator(typename T::iterator end):end_(end){} iterator(typename T::iterator begin, std::function<bool(int)> func):begin_(begin), func_(func) {} int operator*() { if (!func_(*begin_)) return 0; return *begin_; } int operator->() { if (!func_(*begin_)) return 0; return *begin_; } iterator operator++(int n) { ++begin_; return *this; } iterator operator++() { iterator t = *this; operator++(0); return t; } bool operator==(const iterator& o) const { return begin_ == o.begin_; } bool operator!=(const iterator& o) const { return !(begin_ == o.end_); } private: typename T::iterator begin_; typename T::iterator end_; std::function<bool(int)> func_; }; iterator begin() { if (!func_) { throw std::logic_error("func_ is null"); } return iterator(begin_,func_); } iterator end() { if (!func_) { throw std::logic_error("func_ is null"); } return iterator(end_); } private: typename T::iterator begin_; typename T::iterator end_; std::function<bool(int)> func_; };展开
作者回复: 看一下“加餐”里我的参考实现吧。你的这个问题还不少。不够通用,而且前置和后置++的理解反了,返回类型也不对——前置应返回引用,后置返回对象。后置++的参数是假的,永远不需要传递,也不应该给出名字。你写 it++; 时,编译器会自动调用 it.operator++(int)(示意,不是合法代码)。
共 2 条评论1 - 常振华2021-10-15函数式编程在并发场合下的确有优势,但是普通应用,比如递归,实现起来比命令式复杂那么多,可读性更差,意义何在。而且函数式编程的代码简洁性不如直接命令式+注释
作者回复: 可读性问题,仁者见仁,智者见智。我不认为函数式编程一定更优越,但有些场景,函数式写法就是有优势的(反过来的场合当然也有)。C++的多范式,就是允许你在同一程序里根据需求自由使用不同的范式。 说函数式编程代码简洁性不如直接命令式,我想我开头的例子已经可以反驳这个观点了。函数式风格通常代码还容易重用,这也是特点之一。
- chang2021-06-09我也贴下我的实现,参考了前面一些同学的,望老师指正不足之处: template<typename It, typename Comp> class filter_view { public: class iterator { public: //支持内置指针 using value_type = typename std::iterator_traits<It>::value_type; using difference_type = typename std::iterator_traits<It>::difference_type; using pointer = value_type*; using reference = value_type&; using iterator_category = std::input_iterator_tag; iterator(It it, It e, Comp cmp) : it_(it), e_(e), cmp_(cmp) { skipNoMatch(); } iterator& operator++() { ++it_; skipNoMatch(); return *this; } auto operator*() { return *it_; } bool operator==(const iterator &rhs) const { return it_ == rhs.it_; } bool operator!=(const iterator &rhs) const { return !(*this == rhs); } private: void skipNoMatch() { while (it_ != e_ && !cmp_(*it_)) { ++it_; } } private: It it_; It e_; Comp cmp_; }; filter_view(It b, It e, Comp cmp) : b_(b), e_(e), cmp_(cmp) {} iterator begin() const { return iterator(b_, e_, cmp_); } iterator end() const { return iterator(e_, e_, cmp_); } private: It b_; It e_; Comp cmp_; };展开
作者回复: OK,没什么大问题。 要完全满足将来的概念检查的话,最好再加上后置 ++ 和 -> 的实现。
- Frank2021-04-06吴老师,我重写了map-reduce逻辑(lazy evaluation),但是目前的flatten功能输出有点混乱,我暂时没排查出结果。能帮我看下吗?https://github.com/franklucky001/fp-map-reduce/blob/master/main.cpp
作者回复: 对不起,我目前没时间看这么复杂的代码。
共 2 条评论 - Frank2021-03-31吴老师, 我想实现类似rust的lazy evaluation特征的map reduce,代码https://github.com/franklucky001/template_programming/blob/master/collection/iterator.hpp 但是模板中,不方便实现协变,声明成引用局部变量会析构,begin和end虚函数必须声明成智能指针才能通过move构造返回局部变量,有没有更好的方式。
作者回复: 这跟前面的问题本质上是一回事吧?所以,你要么使用静态多态,这样所有的类型必须能够在编译时分析出来;要么使用动态多态,必须把对象放堆上,并且对象有虚析构函数。
- Geek_845be12020-12-23用 std::function 保存 lambda,通过引用捕获,可以很方便的实现了lambda 递归。
作者回复: 使用引用要小心生命周期问题……到下一个 } 引用指向的对象就被销毁了。
- zhengfan2020-07-01吴老师,您好。 此讲内容和之前内容比较,颇有些道与术的分别。 个人感觉函数式编程和ADT范式强调的isolation有几份神似。 您在文中的几条建议,我有些困惑地方,向您请教: 第二条建议:”使用有意义的变量,但尽量不要去修改变量内容——变量的修改非常容易导致程序员的思维错误“;请问您指的是抽象层面上”不要改变变量的含义/意义“?亦或是具体层面上”不要改变传入参数以及环境变量的内容“?从上下文来看似乎是后者,但这样一来好像和第三条建议有些重复了。 请您指教,多谢。展开
作者回复: 就是不修改变量的内容,前面一种含义。
- 易轻尘2020-06-13没有看istream_line_reader前,个人的实现,有点丑陋: template<typename InputIt, typename Pred> auto filter_view(InputIt a, InputIt b, Pred pred) { struct { InputIt _begin, _end; function<bool(decltype(*_begin))> _pred; struct iterator { InputIt _it, _end; function<bool(decltype(*_it))> _pred; iterator(InputIt it, InputIt e, decltype(_pred) p):_it(it), _end(e), _pred(p){} bool operator != (const iterator& other) {return _it != other._it;} bool operator == (const iterator& other) {return _it == other._it;} auto operator*() {return *_it;} auto operator->() {return &(*_it);} iterator& operator ++ () { do { if(_it == _end) break; ++ _it; if(_it == _end || _pred(*_it)) break; } while(true); return *this; } }; iterator begin() { while(_begin != _end && !_pred(*_begin)) ++ _begin; return iterator(_begin, _end, _pred); } iterator end() { return iterator(_end, _end, _pred); } } object; object._begin = a; object._end = b; object._pred = [pred](decltype(*a) x){return pred(x);}; return object; }展开
作者回复: 不一样的做法,也挺好。 不过,再花点力气,是可以去掉对 std::function 的使用的。这可以提升提点效率。
- Leon📷2020-02-11老师,我问下,文章中返回类型都是单独一行,以前是电脑屏幕太小写不下,所以很多老代码都这么写,现在难道又重新流行这种写法了吗
作者回复: 啊,现在是因为手机屏幕太小……😁 读到第 21 讲你就自然明白了。可以先去读一下,那讲是独立的。
- 微风漂过2020-01-30istream_line_reader 的实现在哪里找?cppreference里面没有搜到。
作者回复: 哦,这个是我写的,不是标准库里的。 源代码在第 7 讲讲解了。 https://github.com/adah1972/nvwa/blob/master/nvwa/istream_line_reader.h
共 2 条评论 - 三味2020-01-10关于这个y_combinator, 代码中有很多我不太理解的: 1. 为什么lambda函数作为参数传递的时候,都是Fun&&?我实际测试直接用Fun,对于当前代码没什么问题,直接用Fun不好么?; 2. 关于forward,题目中所有带有forward的地方,我都直接替换为不带forward的方式,编译和运行也没有什么问题。这里的forward作用究竟是怎样的?
作者回复: 性能。不用引用,对于比较重(比如,有较多按值捕获)的函数对象,拷贝的开销就比较大了。用 Fun&& 和 std::forward,就是要把拷贝尽可能转变成移动。
- 三味2020-01-09这一节是我耗时最长的一节。。因为来回翻阅迭代器那一节。。 写了个std::copy(fv.begin(), fv.end(), std::ostream_iterator<int>(std::cout, " ")); 结果编译失败。。查了半天。。因为没定义pointer和reference类型。。 还有就是,之前的章节看得不仔细,看别人答案觉得好奇怪,为什么一上来要++(*this)。。后来对比自己的实现,我是按照forward_iterator_tag来定义的,所以写法有些不同。代码比较长。。我还是贴一下,放到留言的留言中吧,不然太长了。自己测试没啥问题,不过不保证。。没问题。。展开
作者回复: 功能看起来没啥问题。嗯,太长,所以贴到别的留言下去了,还是有点怪怪的。 还是要提醒,在一般的用户代码里,不应该出现双下划线打头、或者下划线加大写字母打头的标识符——这是给系统保留的。 详见: https://zh.cppreference.com/w/cpp/language/identifiers
- じJRisenづジ2020-01-07#include <iostream> #include <chrono> #include <execution> #include <numeric> #include <vector> 老师怎么学习库知识? 提点思路
作者回复: 跟学外语一样,基本诀窍就是多读多写。文档现在都很齐全的,但除了极少数天才式的人物,看了不用就会忘掉吧。而且,没真正用过,碰到一些坑,看了的理解都不一定正确。 另外就是看书了。书比文档更有体系性,更适合完整的学习,需要投入整块的时间。标准库的书,应该就是 Nicolai Josuttis 的那本 The C++ Standard Library 第二版了。中文版刚查了一下,侯捷译,一千多页。
共 2 条评论 - 廖熊猫2020-01-03Y-Combinator主要用到了一个不动点理论,刘未鹏老师的《康托尔、哥德尔、图灵——永恒的金色对角线》这篇文章里面说的相对详细一些。玩过一段函数式...就只有haskell那段代码看懂了😂
作者回复: Y Combinator 只是好玩展示一下,刺激一下大家的好奇心。要进一步了解,是需要看参考资料,或者其他中英文资料的。你说的这篇我之前没看过,内容也不错。