基础概念
- 什么是纯函数式编程?
无副作用、引用透明、纯粹性、符合代换模型 - 纯函数式并行计算应该遵循什么样的原则?
1.映射法则(规约的约束 见Theorem for free中的免费定理)2.分流法则(并行函数的实现不应该影响并行计算的结果) - 什么是函数式的代数形式?
代数形式的库api表现为一批数据类型和基于这些类型的函数,以及表示这些函数之间关联的定律和性质 - 什么叫基于性质的测试?
把编程行为的说明和测试用例的创建分离。还能够将测试用例最小化,并且在测试范围足够小时穷尽测试用例。 - 什么是gradual typing
允许一部分使用动态类型一部分使用静态类型的语言 - 什么是type classes
对类型进行操作的类 - type-class polymorphic
类型类多态,相当于type classes的接口函数 - trait 构造顺序
调用超类的构造器;
特征构造器在超类构造器之后、类构造器之前执行;
特征由左到右被构造;
每个特征当中,父特征先被构造;
如果多个特征共有一个父特征,父特征不会被重复构造
所有特征被构造完毕,子类被构造。 - trait和type classes有什么区别?
1.typeclasses以单独的方式处理而非将对象固定到表面
2.trait不支持functor和applicative等高阶函数 - 什么是皮亚诺系统?什么是后继运算?什么是归纳集?什么是λ演算?β系统中α和β两种变换分别是什么?
https://zhuanlan.zhihu.com/p/34679052
要注意的是:给λ演算的语法项符号不同的推导规则,得到的公理系统也不同,其中最简单的是β系统,它指定了α和β两种变换 - 什么是卫生宏?
lisp引入的宏规则,
1.宏展开后,原表达式处于新的词法环境中
2.宏展开式中的自由标识符,处于宏定义时的词法作用域中,保留其引用透明性 - 为什么Rust比C++慢?
1.没有RVO 返回类型优化防止新的临时对象创建 2.Arc不能去掉弱引用计数 3.如果不优化move,那么会多占用栈空间 4.没有placement new,不能在已分配的内存中分配对象。 - 柯里化的性能损耗在哪?
大量嵌套的作用域和闭包会带来开销,影响内存占用和作用域链查找速度 - 什么是Polymorphic variants
OCaml语法,多态的变量,变量声明时并不指定类型 - 乔姆斯基文法: 0型文法(从包含非终结符的左式到任意右式)1型文法(从包含非终结符的左式到任意右式,而且右式大于等于左式长度)2型文法[上下文无关文法](从包含全为非终结符的左式到任意右式,因此上下文无关)3型文法[正规文法](从包含非终结符的左式到右式,右式必须包含一个非终结符和一个终结符)
TAPL
-
评估形式化语义的三种方法是什么?
评估形式化的语义,就是评估项term的语义,
主要有:
操作语义(将语言视为抽象的自动机)
指称语义(定义一集数学的对象domains,并且定义一个解释函数,将terms映射到domains实体中)
公理化语义(公理化语义从定义的自身出发推导出语言的含义) -
one-step evaluation relation:
满足规则的最小二进制关系 -
类型系统是指一种根据计算出的值的种类对于词语进行分类,从而证明某程序行为不会发生的可行语法手段。
-
给定语言中能够使用类型系统消除的错误一般称为运行时类型错误runtime type error,一般的类型系统相对直接的检查能够校验简单的类型错误和数组越界。
-
一个类型安全的语言是编程时不会危害到自身的语言,是一个保护其抽象的语言,
-
语言安全性可以由运行时或静态类型检查保证,Scheme没有静态检查,但是仍然类型安全。如果建立了动态检查的设施,那么检查的代价将会很小(但是也存在一些Basic方言 提供底层原语,因此不安全)
-
不安全的语言通常提供效果最佳的静态类型检查器,但是一般不能够保证良类型的程序是良行为的
-
在对象变量上取值的变量称为元变量,不含自由变量的项称为封闭项,封闭项也称为组合子,最简单的组合子称为恒等函数 id = lambdax(x)只输出其变元。 不动点组合式可以用来定义递归函数
-
Beta规约的策略有哪些?
full beta规约:任何时候可以以任意顺序规约 规范顺序
normal order策略:最左侧最外面先被规约
按名调用call by name:不针对抽象进行规约。
haskell采用按需规约call by need的方式
一般的语言则采用call by value的方式 -
使用lambda项表示的数称之为church数值 Church 编码是一种“抽象方法”,它将“数字”、“运算”等概念全数“抽象”成λ演算
-
简单代换一个项x到t,使自由变量编程收到限制的变量的情况称为变量捕捉
-
纯lambda演算是图灵完备的,为了解决结果的发散问题,有两种方案,第一种使用项的类型注释来显式检查类型的结果,第二种是使用类型检查器自己推导或者重构这个信息。
-
curry-howard对应:一个命题P的证明是由P的具体证据组成的。Curry-Howard correspondence揭示了计算机程序和数学证明的对应关系,或者说类型系统和形式逻辑的对应关系。在curry-howard同构下,类型证明就是程序
-
什么是curry-howard对应/同构?
对应可以在两个层面上看到,首先是类比层次,它声称对一个函数计算出的值的类型的断言可类比于一个逻辑定理,计算这个值的程序可类比于这个定理的证明。也就是说“证明就是程序,类型就是命题”。更加正式的,它指定了在两个数学领域之间的同构,就是以一种特定方式形式化的自然演绎和简单类型λ演算之间是双射,首先是证明和项,其次是证明归约步骤和beta归约。 -
curry形式和church形式的不同在哪里?
Curry形式指的是首先定义项,然后定义一个语义说明它们的行为如何,语义优先于类型。Church类型首先定义项,然后确定良类型,之后再给出其语义。 -
dynamic类型使用值v和类型T的序对,使用类型安全的typecase结构来检查
进击的plt
- 什么是CPS?
CPS将函数调用完之后接下来要执行的代码通过闭包包裹并且作为函数参数调用要执行的函数。 - 什么是delta规约?
((+ 1) 2) →δ 3 is a δ-reduction.
((+ 1) ((+ 1) 1)) is a not a δ-redex.
agda特别优化为当fully apply时reduce,降低了颗粒化的性能损耗 - 很有意思的图
- 假设你想创建一个多态函数(polymorphic function), 广义来讲,有两种方法:
- 函数操作所有实现都有相同的外观和行为,通常在堆上分配对象,然后将指针传递给函数。由于所有的对象都有相同的形状(它们都是指针!),我们对它们操作所需要的就是知道,这些方法在哪里。因此,传递给我们的通常伴随一个函数指针表,通常称为 虚拟方法表或是 vtable. 大家对这个肯定有印象,这就是 go interface 实现方式,也是 rust 中 dyn Traits, 以及 c++ 中的虚拟类。这些都是多态的形式,在实践中容易使用,但由于运行时的开销比较高而受到限制;
- 我们常讲的单态化(monomorphization), 名字听起来吓人,但实现去相对简单。理解为为每个必须操作的类型单独,创建一个函数副本。比如,你想实现两数相加的函数,当调用 float64 类型时,编译器会创建一个函数的副本,并将通用类型占位符替换为 float64. 这是迄今为止实泛型最简单的,同时对于编译器来讲也带来开销。go同样也用GCShape做单态化。
- 过程宏分为三种:派生宏(Derive macro):用于结构体(struct)、枚举(enum)、联合(union)类型,可为其实现函数或特征(Trait)。属性宏(Attribute macro):用在结构体、字段、函数等地方,为其指定属性等功能。如标准库中的#[inline]、#[derive(…)]等都是属性宏。函数式宏(Function-like macro):用法与普通的规则宏类似,但功能更加强大,可实现任意语法树层面的转换功能
- variants/alternation/tagged union 的区别:
tagged union在C++中使用variant这样实现:(另外两个暂时还不清楚是啥)
template<typename...Ts>
using Variant = std::variant<Ts...>;
template<typename E, typename...Vs>
auto MatchVar(E &&e, Vs...vs)
{
struct overloaded : Vs...
{
explicit overloaded(Vs...vss)
: Vs(vss)...
{
}
};
return std::visit(overloaded(vs...), std::forward<E>(e));
}
- 使用scala实现柯里-霍华德同构的union:这里用到了type-lambda
type ¬[A] = A => Nothing
type ∨[T, U] = ¬[¬[T] with ¬[U]]
type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = {
type λ[X] = ¬¬[X] <:< (T ∨ U) }
def size[T : (Int |∨| String)#λ](t : T) = t match {
case i : Int => i
case s : String => s.length
}
scala> size(3)
res0: Int = 3
scala> size("three")
res1: Int = 5
- scala泛型类型约束和implict类型约束之间的关系,二者的区别在于:泛型类型约束会向上寻找基类,找到Any认为二者相同,而implicit则不会
object A{
def test[T <: java.io.Serializable](i:T) {
}
test(1) // 编译时报错
def test2[T](i:T)(implicit ev: T <:< java.io.Serializable) {
}
test2(1) // 同样编译时报错
}