My Blogs

Posts tagged with "程序设计语言理论"

通过实例看解释器和编译器的区别

Tags: JavaScript , 编译器 , 解释器 , 程序设计语言理论

Published 2015年04月25日 23:00 by james

本文将通过一个简单例子来对比一下解释器和编译器的区别。

在实现Compiler(编译器)和Interpreter(解释器)之前,我们需要先选择一种目标语言,这里我们选用计算器语言,它的文法非常简单,而且可以很方便地扩展,除了四则运算之外,如果有需要,我们可以很方便地加入负数,平方,开方,幂,括号及变量等。

本例,为了演示简单,我们的计算器仅支持四则运算,负数和括号。

expression : term | expression '+' term | expression '-' term
term : primary_expression | term '*' primary_expression | term '/' primary_expression
primary_expression : DOUBLE_LITERAL | '(' …

零基础构建语言解释器

Tags: 函数式编程 , 程序设计语言理论 , Lambda , Lisp , JavaScript , 编译器 , 解释器

Published 2015年04月01日 23:00 by james

在编写Interpreter之前,我们需要先了解Lexer(词法分析器),Parser(语法解析器),AST(抽象语法树)。

一般情况下,Interpreter在解释执行程序时,一般会经过如下步骤。

  1. Lexer读入程序代码,把代码转换token序列。
  2. Parser把读到的token序列转换为AST(大部分情况下,Lexer内嵌为Parser的一部分)。
  3. 对AST进行Lowering(化简AST)或者desugar(把语法糖的AST节点转换为标准等价AST节点)处理。
  4. Interpreter递归执行AST,AST决定了代码的执行顺序。

alternative text

介绍完了基本的一些概念,我们现在开始来实现语言解释器。

首先,我们需要先定制文法规则,一般情况下,我们只要制定好了文法规则,就可以找一些工具来帮我们生成AST,对于复杂的文法规则,手写Parser是非常麻烦并且乏味的。 这里,为了简单,我们选用S-Expression来作为我们的文法规则来实现Lambda Calculus(Lambda演算)的解释器。 通过该Interpreter的实现,大家可以很容易就搞明白程序设计语言中耳熟能详却又未必深刻了解的一些概念,比如:类型,Lexical Scope(词法作用域),Dynamic Scope(动态作用域),闭包等概念。

Lambda Calculus

虽然规则简单,Lambda演算却是一门强大的语言。它的三个元素分别是是:变量,函数,调用。用传统的表达法,它们看起来就是:
    变量:x
    函数:λx.t
    调用:t1 t2

每个程序语言里面都有这三个元素,只不过具体的语法不同,所以你其实每天都在使用 lambda calculus。换成S-Expression就是:
    变量:x
    函数:(lambda (x) e)
    调用:(e1 …

Y-Combinator不同语言实现方案

Tags: 函数式编程 , 程序设计语言理论 , Lambda , 数学 , JavaScript

Published 2014年09月19日 19:00 by james

递归和定点

纯λ演算的一大特色是可以通过使用一种自应用技巧来书写递归函数。

f(n) = if n = 0 then 1 else n*f(n-1) 
f = λn.if n = 0 then 1 else n*f(n-1)

把f移到等式的后面,得到函数G

G = λf.λn.if n = 0 then …

Lambda演算之Y-Combinator的推导(JS描述)

Tags: 函数式编程 , 程序设计语言理论 , Lambda , 数学 , JavaScript

Published 2014年09月18日 19:00 by james

上一节中,我们讲到了如何使用λ演算来描述自然数,可以看出λ演算的表现力确实非常强大,然而遗憾的是,由于lambda演算中使用的都是匿名函数,所以它无法很直观地表述递归。 如果缺少了递归,λ演算的能力无疑会大打折扣。

所有基于λ演算的语言中,其实都是支持对过程进行命名的,那为什么这里我们还需要探讨匿名函数的递归呢?

  1. 为了保持纯洁性,我们希望仅仅通过λ演算的规则本身来解决递归的问题。
  2. 部分语言对过程的命名必须等到该过程定义完成后才可以进行,这个时候我们必须找到一种方式使其能够很容易地获得递归的能力。

下面我们就是用factorial函数为原型,来看看如何使用lambda演算基本的规则,为其增加递归的能力。

传统定义方式,不过这个方式不符合我们的需求,因为它使用到了函数自身fact。

var fact = function(n){
    return n <= 1 ? 1 : n * fact(n - 1);
}; …

Lambda演算之Y-Combinator的推导

Tags: 函数式编程 , 程序设计语言理论 , Clojure , Lambda , Lisp , 数学

Published 2014年09月18日 12:00 by james

上一节中,我们讲到了如何使用λ演算来描述自然数,可以看出λ演算的表现力确实非常强大,然而遗憾的是,由于lambda演算中使用的都是匿名函数,所以它无法很直观地表述递归。 如果缺少了递归,λ演算的能力无疑会大打折扣。

所有基于λ演算的语言中,其实都是支持对过程进行命名的,那为什么这里我们还需要探讨匿名函数的递归呢?

  1. 为了保持纯洁性,我们希望仅仅通过λ演算的规则本身来解决递归的问题。
  2. 部分语言对过程的命名必须等到该过程定义完成后才可以进行,这个时候我们必须找到一种方式使其能够很容易地获得递归的能力。

下面我们就是用factorial函数为原型,来看看如何使用lambda演算基本的规则,为其增加递归的能力。

传统定义方式,不过这个方式不符合我们的需求,因为它使用到了函数自身fact。

(defn fact [n] (if (<= n 0) 1 (* n (fact (dec n))))) …