今天讲消息分发的一种编译期实现法。 编程是一门非常依赖逻辑的学科,逻辑分为形式逻辑和非形式逻辑,编程就属于形式逻辑。形式逻辑指的是用数学的方式去抽象地分析命题,它有一套严谨的标准和公理系统,对错分明;而日常生活中使用的是非形式逻辑,它不存在标准和公理,也没有绝对的对与错。 根据哲学家大卫·休谟在《人性论》中对于观念之间连接的分类,我们能够把逻辑关系分成三大类:相似关系、因果关系、承接关系。相似关系表示两个组件结构相同,去掉其中一个组件,也只会使功能不够全面,并不会影响程序;因果关系表示两个组件之间依赖性极强,没有第一个组件,就没有第二个组件,第二个组件依赖于第一个组件;承接关系表示两个组件都是局部,只有组合起来,才能构成一个整体。 消息分发就属于因果关系,我们需要依赖 A,去执行 B,没有 A 就没有 B。同样属于因果关系的术语还有逻辑分派、模式匹配、定制点的表示方式等等,它们本质都是在描述一类东西,只是有时候侧重点不同。 条件关系也属于因果关系的范畴,是编程中逻辑最重的关系。试想没有 if else,你还能写出多少程序?世界是复杂的,问题也是复杂的,因果关系必不可少。 消息分发,或称为逻辑分派,就是一种简化条件关系表达方式的技术。它适用于存在大量因果的情境,此时若是使用原始的 if else,则无法适应动态发展的世界。 C++ 中,最典型、也非常有用的一种方式就是采用 map,因作为 key,果作为 value,因是标识符,果是回调函数。由于这种方式发生于运行期,所以也称为动态消息分发。 本文要讲的,是 C++20 才得以实现的另外一种方式,发生于编译期的静态消息分发技术。 称为消息分发,一般是在网络通信的情境下。正常情境下,程序是顺序执行的,所以完全可以使用 if else 来实现因果逻辑,因为组件与组件之间距离较近,属于同一模块;而网络情境下,一个组件可以瞬间跳跃到距离非常远的另一个组件,这两个组件甚至不在同一台设备上,一台设备可能在上海,另一台在北京,此时如何让这两个组件进行沟通?也就是说,A 组件里面的某个函数执行条件不满足,如何简单地跳到 B、C、D、E…… 这些组件的某个函数中去处理?这种远距离的程序因果逻辑,通过消息分发组件能够非常丝滑地表示。 消息分发的标识符一般采用字符串表示,到了 C++20 支持 string literal NTTP 才得以在编译期实现一套可用的相关组件。 因此首先,我们得实现一个 string literal 以在编译期使用。 template <std::size_t N> struct string_literal { // str is… Continue Reading Compile time dispatching in C++20

今天这篇讲 Monads,对 C++ devs 来说是一个比较新的概念,也是一个比较难理解的概念。本文将概念和实践串起来讲,先讲抽象理论,后面讲具体实现和使用,以全面理解其定位。 Language 编程语言用于操作机器以辅助人类解决问题,主体是人和机器,目的是解决问题。人通过机器解决问题,编程语言是人和机器之间沟通的桥梁。 问题解决最基本的流程为定义问题、分析问题、寻找策略及选择策略。简单来说,你有一些东西,想通过一些行为,再得到一些东西。问题就是理想和现实之间的差距。 这其中最基本的构成要素就是东西和行为,也就是编程语言当中的数据和函数。一个大问题不可能通过一个行为就能解决,往往需要拆分为一个个小问题,再逐个击破,故根据问题复杂程度,数据和函数的数量也会随之变化。 主体虽分为人和机器,但却是由机器来完成具体工作。最初的语言,靠机器更近,尽管效率高,人和机器沟通起来却是多有不便;随后的语言,便靠人更近,使人和机器的沟通方式,更加接近人与人之间的沟通,这才简化了编程难度。 常言道,月满则亏,水满则溢,凡事过犹不及。太靠近机器的语言,尽管效率高,但却难以理解;太靠近人的语言,虽说易理解,效率却颇低。因此,只有在明确仅注重效率或简易的情况下,才会使用这类语言,许多语言其实都处于中间,保持效率的同时也避免太过复杂。 Imperative versus Declarative 由机器到人,编程语言是把机器所能识别的具体指令,抽象为人所能理解的词句。 声明式语言相比命令式语言,离人更近,命令式更关注具体,声明式更关注抽象。抽象是把不变的都封装起来,从而避免去写一个个重复的指令,比如原本想要修改 std::vector 的元素,你需要使用 if、for 等等指令,这些再往上抽象一层就是过滤和变换两个步骤,直接使用 v | filter | transform 则更加言简意赅。因此,声明式用起来要更加简洁,使人更加专注于问题解决步骤。 具体中包含了变与不变,抽象中提取了不变的部分。具体就像是白话文,抽象就像是文言文,前者通俗易懂,后者简洁精炼。命令式让人更加专注于行为的细枝末节,事无具细,一切尽在掌握之中;声明式让人更加关注于行为步骤,抽离细节,一切环环相扣。 也可以说命令式注重问题解决过程,而声明式注重问题解决结果。不关心过程,只想要结果,展现在语言上就是只调用接口,不关心实现。因此,从视角上来看,命令式更偏向于微观,声明式更偏向于宏观。宏观适合思考问题,微观适合解决问题,前者像是将军,只谋战略,后者则像是小兵,具体实施。 将无兵而不行,兵无将而不动。抽象与具体当中,虽然真正起作用的是具体的指令,但使用的却是抽象的接口。接口使来结构清晰,能更加专注于问题逻辑,跳出问题细节。抽象与具体紧密连接,只要抽象,抽象从何来?只要具体,具体何以用? 是以范式不同,本质是解决问题思维的不同,是侧重点的不同。 What is Monads C++ 有许多范式,面向过程、面向对象、泛型编程、函数式编程…… 许多人发现 Modern C++ 写来越发简单,这种简单写法就谓之 Modern,但这种简洁从何而来?本质就是范式的转变。 C++11 至今,C++ 已逐步从函数式编程中汲取了许多特性,Monads 就是其中非常重要的一个特性。C++23 中也加入不少 Monads,简化了一些原本较为繁琐的操作。 那么,什么是 Monads? 这个概念来自范畴论,称为单子,牵扯概念颇多,且不易理解,因此我们结合 C++… Continue Reading Monads in Modern C++, What, Why, and How

今天再来看看 C++23 Monadic std::optional,在 Overview of C++23 Features 只是简单介绍了用法,这里来说说设计原理。 std::optional 是一个 Monad,这个概念源于 FP (Functional Programming),对于 C++ coder 而言,理解起来并非易事。若是你熟悉 Haskell,这个就相当于里面的 Maybe。Monads 叫作单子,是范畴论里面的概念,若要完全弄懂的话需要扩展许多概念,不符合 TGS 的定位,因此这里只是小做介绍。 简单来说,Monads 是 FP 当中的一个抽象数据类型,抽象的目的在于表示计算过程。通过这种方式,能够减少重复代码,简化组合流程。 用下面的公式来解释一下: m a \rarr (a \to m b) \rarr m b (a \rarr m b) 表示操作,a 是输入类型,b 是输出类型。操作必须满足某种上下文,才能知道怎样处理,m 就指定了这个上下文。如果将其替换为一个针对 std::optional 的函数,会更加容易理解: std::optional<int> f(std::optional<std::string>… Continue Reading T230807 Reviewing C++23 Monadic std::optional