Showing posts with label lambda. Show all posts
Showing posts with label lambda. Show all posts

Tuesday, February 15, 2011

Short notes about LaTeX2e fonts

对LaTeX上的字体概念一直很混淆,所以这里大概总结一下一点概念与机制。
主要涉及到字体的使用与选择,但不包括使用任意自定义字体/系统字体,以及中文字体。
关于中文字体+LaTeX,可以使用CJK[utf8]宏包,可以参考以前一篇旧文
关于使用任意字体,太复杂,此处不表。参考[1]的section 4,文档 [8], 以及tex-font-cheatsheet [5] (also available using texdoc).
以上两个问题比较简单的处理方法是使用XeTeX/LuaTeX,它们都能直接使用系统的字体。因此顺带也把中文的问题给解决了。

所谓的NFSS, New Font Selection Scheme。
参考 fntguide [1], psnfss2e [2](可用texdoc docname打开),
以及Font selection in LATEX: The most frequently asked questions (schmidt.pdf) [3]。
以上三个都是只有十来页的文档,非常值得一读。
另外,有一个非常好的网站 [4] 列出了LaTeX上主要能用的字体(depends on distribution)。
想用某种字体前可以先去参考。

以下为summary:
  • 每种font有五个attributes:
    • encoding, 如OT1, T1 等。
    • family, 如cmr (=Computer Modern Roman), ptm (=Adobe Times)等. 值得注意的是,family 又分为三种:
      • roman (serif),即衬线字体
      • sans serif,即无衬线字体
      • typewriter (monospace),即等款字体
    • series, 如m for medium, b for bold
    • shape, 如n for normal, it for italic, sl for slanted, sc for caps and small caps.
    • size, 如1.5in, 3mm
  • 所以,OT1 cmr m n 10 就是指 Computer Modern Roman 10 point, TeX 里面的font name就是cmr10
  • 所以,平时见的\textxx command就是用来设置这写attr的。如\textrm{..} 用来更改 family, \textbf{..} 更改 series, \footnotesize 更改size 为 8pt
  • 用于底层的font selection command为\font... 系列。如\fontfamily 用来选择family
  • 使用author commands来直接选择字体。上面所说的如\textrm会选择一种现在选定的roman字体(\rmdefault)。因此要更改这个设置,则可以更新\rmdefault:
    • \renewcommand{\rmdefault}{ptm}
  • 数学字体与文本字体不同,并且更复杂。比如上面的命令只改变了文本字体,而没有改变数学字体。所以一般是用package来封装、完成上面所说的这些命令。
    • 比如要把文本和数学字体都改为Times, 则执行 \usepackage{mathptmx}
  • LaTeX包含有可用的minimum font sets ``PSNFSS collection'',除了默认的Knuth大爷的Computer Modern,还有Times, Helvetica, Palatino, Chater等,强烈建议参考 [2].
    • 注:在 PSNFSS 里有一个pifont,包含了一些有趣的符号。如扑克牌……
  • 执行 pdftex testfont 可以测试LaTeX能用的字体。执行后,输入字体名字(如cmr10),然后 \sample, \bye。TeX会生成对应的一个示例。font-change [6] 给出了一份不错的参考。
关于数学字体:
  • 通过\mathbf 等命令来使用某种特别的数学字体。型如 \mathbf 的字体称为 math alphabets。它们只能在数学模式内使用。
  • 数学字体与文本字体一样具有五个attribute。然而,它们并不能被单独设置,而只能通过设置 "math version" 来设置。predefined的math version有normal和bold。它们只能在数学模式外调用。如 \mathversion{bold} 选择使用了 \boldmath 这个math version。
  • 综上所述,要选择特定的数学字体,必须使用如下的步骤:
    • 定义一个新的mathversion:如 \DeclareMathVersion{normal}
    • 定义相应的mathalphabet,如\SetMathAlphabet{\baz}{normal}{OT1}{cmss}{m}{n}, 它设置 \baz 为 normal 这个 math mode 的一个math alphabet,其中的OT1, cmss m, n参数意义如文本字体
    • 定义数学符号字体以及数学符号。以上只是设置了math alphabet的字体,还必须为符号再定义一套字体。参考 [1]。
    • 定义数学字体大小。有四种大小:\textstyle, \displaystyle, \scriptstyle 与 \scriptscriptstyle,使用\DeclareMathSizes设置。
    • 最后,使用 \mathversion 来选用这个 math version.
最后,The UK TeX FAQ [7] 也是一份非常不错的文档,它的section K 包含了字体相关的FAQ。 可以使用 texdoc FAQ-en 来察看一份pdf copy。

Reference:
[1] fntguide
[2] psnfss2e
[3] schmidt.pdf
[4] The LaTeX Font Catalogue, http://www.tug.dk/FontCatalogue/
[5] tex-font-cheatsheet
[6] font-change
[8] 为LaTeX 添加英文 TrueType 字体, http://giantchen.googlepages.com/TrueTypeFonts_en.pdf

Friday, October 5, 2007

Lambda演算

本文介绍了 'lambda 演算'.
原文地址:http://blog.sina.com.cn/s/blog_4b23ee54010006uy.html

演算的概念

在Lambda演算中,我们所做的演算是通过一定的机械规则将一个lambda term 转化为另一个lambda term,这个转化过程就称为lambda calculus广义上来说,对于任何一个发展变化的数学(或物理)系统,只要赋于它的状态以一定的语义,将它从一个状态变换到另一个状态,这个变化过程就是一个演算过程。

比如我们经常做的加法运算:2+13=15,这就是在PA系统中做的演算。我们做数学证明,就是在形式化的公理系统中进行逻辑推导的演算。我们写程序,就 是在图灵机上做演算。一段程序的指令序列就描述了一个演算过程。我们说话,写字,就是在自然语言上做演算。强人工智能主义者认为:人类思考问题,也是在做 某种演算。

在经典的计算理论中,演算的最重要的性质就是机械性,这是“演算”的强大威力所在,也是其局限性所在。但是如果把演算的概念扩展到物理世界中,有可能能突破机械性的局限(量子图灵机)。

起源

在十八世纪初,现代数学取得了辉煌的成果。其中G. W. Leibniz (1646 -- 1716)是当时的杰出者,他是微积分学的创始人之一。作为数学家和哲学家,他为推动现代逻辑的发展也作出过重大的贡献。 Leibniz曾有一个宏伟的理想:(1)建立一个通用语言,使得所有的问题能在其中表述;以及(2)找出一种判定方法,解答所有在通用语言中表述的问 题。

人们为实现这一理想付出了很多努力,直至二十世纪才出现一些重要成果。谓词逻辑和集合论的建立实际上完成了Leibniz的第一条理想,这归功于一些一流的数学家,Frege, Cantor, Russell, Hilbert 和 Godel。Leibniz的第二个理想成为了一个哲学问题:“是否存在一个通用的判定方法求解所有用通用语言表述的问题呢?”这就是所谓的Entscheidungsproblem(德语,意思是“判定问题”)。

在1936年,A Church(这家伙就是McCarthy的导师)和Alan Turing独立地给出了这个问题的否定答案。为了解决此问题,他们以 不同的方式形式化定义了“可判定”的概念(或者“可计算”的概念)。事实上他们给出了两种不同的计算模型:Turing发明了一组机器---Turing 机,并以此定义可计算性;Church发明了一个形式系统---lambda演算,并以此定义了可计算性。后来证明,图灵机和lambda演算其实是等价 的。就这样,λ-演算问世了。

Lambda 演算对函数式程序设计Functional Programming有巨大的影响,特别是Lisp语言的精髓。

非形式化描述

在 lambda 演算中,每个表达式都代表一个只有单独参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。函数是通过 lambda 表达式匿名地定义的,这个表达式说明了此函数将对其参数进行什么操作。lambda演算所以不考虑多元函数的情况,因为多元函数可通过重复作用一元函数的运算而得到。

λ-演算有两种基本运算:“作用(Application)”与“抽象(Abstract)”。

作用:表达式 FA 表示对象 F 作用于对象 A,FA 既可被理解为计算 FA 的过程, 也可被理解为此过程的输出。λ-演算是无类型系统,从而自作用 FF 是合 法的,这将模拟递归。

抽象:在抽象运算中,记号λ将被引入。对于数学式x^2,λx. x^2 表示函数 x |→ x^2,通俗一点的表示,λx.x^2就是函数f(x)=x^2,当然,也可以认为是f(y)=y^2,参数的取名并不重要。
一般来说,若 M[x] 为表达式,则 λx. M[x] 表示函数 x |→ M[x]。

把作用与抽象结合起来就有如下方程 (λx. M[x])N = M[N]

这一方程便是所谓的β-变换。这里 M[N] 通常写成 M[x:=N],表示在M中将所有“自由出现”的 x 替换为 N 所得到的结果(事实上替换过程中 N 的表达式也可能要改变,这一点将在后面详细说明)。

形式化定义(这一节是完全的数学方式的表达,很头痛的东西,我不太能够认真地看下去,所以这部分我完全摘抄其它文章:P)

【定义1】λ-演算的字母表由以下组成:
●变元集合:Δ = {v, v', v'', v''', …}, Δ无穷
●抽象算子:λ
●括号:(, )

【定义2】λ-项的集合Λ归纳定义为满足以下条件的最小集合:
● x ∈Δ → x ∈ Λ
● M, N ∈Λ → (MN) ∈ Λ
● M ∈Λ, x ∈Δ → (λx M) ∈ Λ

若用BNF(Backus Normal Form)表示,则有
Δ ::= v | Δ'
Λ ::= Δ | (ΛΛ) | (λΔΛ)

【定义3】我们做以下约定:
● x, y, z, … 表示任意变元;
● M, N, L, … 表示任意λ项;
● M ≡ N 表示M和N语法恒同;
● 通常采用以下省略括号表示法:
(i) F M_1 M_2 … M_n ≡ (…((F M_1) M_2)…M_n) (左结合)
(ii) λx_1x_2…x_n. M ≡ (λ x_1 (λ x_2 (…(λx_n M)…) ) )
● 设 P ≡ M N_1 N_2 … N_k (k ≥ 0),当k=0时,P ≡ M;
设 P ≡ λx_1x_2…x_k. M (k ≥ 0),当k=0时,P ≡ M。

【定义4】设M ∈ Λ,M的长度ρ(M)被定义为M中变元出现的次数,即
● ρ(x) = 1, x ∈Δ
● ρ(MN) = ρ(M) + ρ(N)
● ρ(λx. M) = ρ(M) + 1

以后我们说对M的结构作归纳是指对M的长度ρ(M)作归纳,这是自然数上的归纳。

【定义5】设M∈Λ,对M的结构作归纳,定义M的子项集合Sub(M)如下:
● Sub(x) = {x} , x ∈Δ
● Sub(N_1 N_2) = Sub(N_1) ∪ Sub(N_2) ∪ { N_1N_2 }
● Sub(λx.N ) = Sub(N) ∪ {λx.N }
若N ∈ Sub(M),则称N为M的子项。

例如:y为 λx.yy 的子项,但是x不是 λx.yy 的子项。

【定义6】设M ∈Λ,
● M 的自由变元集合 FV(M) 归纳定义如下:
FV(x) = {x}
FV(N_1N_2) = FV(N1) ∪ FV(N_2)
FV(λx.N ) = FV(N) - {x}
● 若x出现于M中,且x不属于FV(M),则称x是约束的。
● 若FV(M)为空集,则称M为闭λ-项(或组合子),且记全体闭λ-项的集合为 Λ* 。

例如 M ≡ λx. yxy ,则其中x 是约束变元,y是自由变元。N ≡ λxy. yxy 是一个闭λ-项。

事实上约束变元和自由变元的概念在数学的其他领域也出现过,例如在微积分中
∫(3xy+x-y) dx
这里dx其实就表明在3xy+x-y中x是约束变元。

【定义7】上下文(Contexts)
λ上下文集合C[]定义为满足下列条件的最小集合:
● x ∈C[]
● [] ∈ C[]
● C_1[], C_2[] ∈ C[] → (C_1[]C_2[]) ∈ C[]
● C_1[] ∈ C[] → (λx. C_1[]) ∈ C[]

上下文中的空白用一个[]表示。引入上下文的概念是为了方便以后的讨论。
例如
C_1[] = (λx. []x) M

C_1[λy.y] = (λx. (λy.y) x) M

换句话说,C_1[N] 就是把 C_1[] 中的 [] 出现的地方都填入 N。这种填入替换和前面说的 M[x:=N] 不同,M[x:=N] 要把M中所有自由出现的x替换成N, 并且N本身可能也会适当地改变。但是C_1[N]的替换则是把所有的[]都换成N,且N不做任何改变。
另外需要注意的是,FV[M]中的变元在 C[M] 中可能会变成约束变元。

lambda演算的公理化系统

形式理论λβ由以下的公理和规则组成。这7条规则是lambda演算中最重要的部分,体现了演算的逻辑性和机械性。我们可以把lambda的函数看作是一 个有机体,这些规则就类似于有机体中分子与分子结合的方式,有机体通过这些组合方式可以无限的繁衍,并且无论如何繁衍,最终都还是这种有机体。因此,对于 一个可计算的问题,无论它有多么困难,理论上都有一个足够长的函数能够把它表示出来。

公理:

(ρ) M = M

(β) (λx. M) N = M[x:=N]

规则:

M = N
(σ) ---------------
N = M


M = N, N = L
(τ) ---------------
M = L


M = N
(μ) ---------------
ZM = ZN


M = N
(ν) ---------------
MZ = NZ


M = N
(ξ) ---------------
λx.M = λx.N


公式 M = N 在λβ中可记为λβ|- M = N,有时简记为 M = N,
这时称M可β转换于N。

上面的公理和规则的名称来源于 Curry[1958]。

β变换

β替换有两个基本原则:
1。M[x:=N] 只能把M中自由出现的x替换为N;
2。原来在N中自由出现的变元,在M[x:=N] 的结果中也应该保持自由。

下面将分别对这两个原则进行描述。

1。M[x:=N] 只能把M中自由出现的x替换为N

上述公理系统中的β公理其实就是λ演算中的作用(Application)过程。从计算机程序设计的角度来看,任何一个λ项都可以看作是一段子程序,λx. M 就表示该子程序的输入参数是x,而 (λx. M) N 则表示把N作为参数x带入到子程序M中进行计算。M本身的语法形式描述了M的计算过程,因而只需要把M中所有“自由出现”的x替换成N即可得到计算结果。注意,这里一直强调要替换“自由出现”的x,下面我们举一个例子来说明这一点。

假设我们已经在λ演算中定义了加减乘除运算,设
L ≡ λx. (y + x)
M ≡ L (x * x)

(λx. M) t ≡ (λx. L(x*x) ) t
≡ (λx. (λx. (y + x)) (x*x) ) t
= (λx. (y + x))(t*t)
= y + t*t

注意其中第一个=号处不能把(λx. (y + x))中的x替换成t,因为(λx. (y + x))中的x是约束的,如果把(λx. (y + x))中的x也换成t的话,将得到

(λx. M) t ≡ (λx. (λx. (y + x)) (x*x) ) t
= (λx. (y + t)) (t*t)
= y + t

在计算第二个等号的时候约束变元x在 (y + t)中没有出现,所以实际上并没有做任何替换,只是简单地浪费掉一个参数(t*t),最后得到结果y+t。但这个替换的结果并非我们所希望的。

从程序设计的角度来看,λx. M 相当于一段子程序,"λx."说明了子程序M的输入参数是x,M中除了x以外的其他自由变量都相当于子程序中的全局变量。

因此在λ演算中规定 M[x:=N] 只能把M中“自由出现”的x换成N。

2。原来在N中自由出现的变元,在M[x:=N] 的结果中也应该保持自由(俺不懂PASCAL,所以这里完全照抄了)

请看下面的例子:

L ≡ y + x
M ≡λy. L ≡ λy. (y + x)

(λx.M)y = M[x:=y] ≠ λy. y + y

我们还是用PASCAL语言为例来说明这点。

M就相当于以下的函数(在下面例子中我们扩展一下PASCAL的语法,让其函数返回值也可以为一个函数):
program Example_2;

var
y, z: integer;

function M(x: integer): function; // M的返回值也是一个function
function L(y: integer): integer; // L是M的子函数
begin
L := y + x;
end;
begin
M := L;
end;

begin
readln(y, z);
writeln( M(y)(z) );
end.

上面例子中M的返回值也是一个function,因此M(y)得到的是一个函数,M(y)(z)得到的才是一个函数值。

上述程序的正确输出应该是z+y。在调用了M(y)以后,函数M返回的是一个函数L,且因为L是M的子函数,L中的x就是M的参数x,他的值应该等于y,不过L本身的参数名字也叫做y,如果不加区分就会搞混淆。一种错误的计算过程如下:先计算M(y),得到一个函数
function L(y: integer): integer;
begin
L := y + y;
end;
然后计算L(z),得到z+z。这个结果是错误的,这就是对局部变元和全局变元不加区分的结果。

当然如果让计算机来执行这个程序它是不会搞错的,因为编译器采用了一个叫做“名字空间”的方法,每个变元名前面都会加上名字空间。具体而言,在编译器看来,函数M应该是下面这个样子的

function M(M_x : integer): function;
function M_L(M_L_y: integer): integer;
begin
M_L := M_L_y + M_x ;
end;
begin
M := M_L;
end;

计算机计算M(y)(z)的时候,先计算M(y),得到一个函数

function M_L(M_L_y: integer):integer;
begin
L := M_L_y + y
end;

然后计算 M_L(z),得到 z + y。这个结果是正确的。

其实编译器所采用的这个方法就是把函数中所有出现过的变元名,函数名都加上一个前缀,这个前缀是这个函数本身的名字空间。主程序的名字空间就是空字符串, 主程序的子函数M的名字空间就是"M_",M的子函数L的名字空间是L的父函数M的名字空间加上L自己的名字,即"M_L_"。这样就永远不会出现局部变 元和全局变元同名的问题了。

不过作为程序员来说,为了让程序更便于理解,需要自己来手工地给函数L的参数改名。例如可以把M改为下面的形式

function M(x: integer): function;
function L(y1: integer): integer; // 把L的参数命名为y1
begin
L := y1 + x;
end;
begin
M := L;
end;

这样在计算M(y)(z)的时候就不会搞错了。


下面我们回到λ演算中来。在λ演算中也会有这种问题。


L ≡ y + x
M ≡λy. L ≡ λy. (y + x)


(λx.M)y = M[x:=y] ≡ (λy. (y + x))[x:=y] ≠ λy. y + y

这就和刚才的计算过程一样,如果不做任何处理地直接将(λy. (y + x))中的x换为y,则会导致原来自由的y在替换后变为约束的,这就产生了错误。

原来自由出现的变元在β替换以后变成了约束的,这种现象就叫做“variable capture”。

解决“variable capture”的方法也和程序设计中一样,只要把M中的约束变元y改一个名字就可以了。这其实是基于这样一种思想:把函数中的参数或局部变元名字改变, 函数的功能仍然保持不变。但在λ-演算,我们需要把这种思想形式化地定义出来。在Curry的著作以及其他一些λ-演算的著作中,公理
(α) λx.M = λy. M[x:=y] 这里y不出现于M中

被加入形式理论λβ中。这条α公理就是为了处理变元改名而引入的。