2012年11月11日星期日

4 脚本(Scripts)和显性函数

4 脚本(Scripts)和显性函数

这是对<Learning J>(by Roger Stokes)的尝试翻译。

Roger Stokes的版权声明如下:
Copyright © Roger Stokes 2012. This material may be freely reproduced, provided that this copyright notice is also reproduced.
感谢作者的无私奉献,请您转载时也注明以上版权,也请同时注明转载地址http://corwindong.blogspot.com,我将不胜感激。

----------------------------------------------------------------------------------

“脚本”就是连续的几行J代码,这些代码完成某个计算,而且可以重复使用。本章的主题是:脚本、脚本定义的函数,以及脚本文件。

4.1 文本

先看一下对变量txt的赋值:
   txt =: 0 : 0
What is called a "script" is
a sequence of lines of J.
)
 
上式中的 0 : 0 表示“如下”,也就是说,0 : 0 是一个动词,它把接下来后面的所有行
作为自己的参数(直到遇到一个以单独")"开头的行为止),同时也作为自己的结果。
 
txt的值就是接下来那两行,这两行整体作为一个字符串。该字符串包含换行符(LF),
这使得字符串分为几行显示。txt有确定的长度,秩为1,也就是说,txt是一个列表,
包含两个换行符。
   txt
What is called a "script" is
a sequence of lines of J.

$ txt# $ txt+/ txt = LF
5512


我们说txt是一个“文本”变量,就是—— 一个有着零个或多个换行符的字符串。

4.2 过程脚本

我们来看一个按步骤执行的计算过程。作为一个简单的示例,华氏摄氏温度转换可以用两步计算完成。假设T表示华氏温度:
   T =: 212
第一步是将T减去32。得到结果t,
   t =: T - 32
第二步将t乘以5%9得到摄氏温度。
   t * 5 % 9
100


假如我们要以不同的T值进行上述计算,我们可以将上述两行过程记录成脚本,脚本就可以重复使用。脚本由那几行J代码组成,并存储在文本变量中:
   script =: 0 : 0
t =: T - 32
t * 5 % 9
) 
这样的脚本可以通过内置的J动词—— 0 !: 111 ——来执行,我们称该动词为do,
   do =: 0 !: 111
   
       do  script
 
然后,我们会在屏幕上看到脚本中的那些行:
   t =: T - 32
   t * 5 % 9
100
 
我们可以以不同的T值来再次运行脚本:
   T =: 32
   do script
   t =: T - 32
   t * 5 % 9
0
  

4.3 显式定义函数

可以通过脚本定义函数。这里有一个例子,华氏摄氏温度转换:

   Celsius =: 3 : 0
t =: y - 32
t * 5 % 9
)
   
Celsius 32 2121 + Celsius 32 212
0 1001 101

接下来让我详细解释上述定义。

4.3.1 头部

函数以表达式 3 : 0 开头,表达式的含义为:“一个动词,定义如下”。(对照一下,0 : 0 表示“一个字符串,定义如下”。)

3 : 0 中的冒号是一个连词,它的左参数(3)表示“动词”,右参数(0)表示“下面的行”。
更多的细节请参考第12章。一个以这种方法定义的函数被称为“显式定义”,或者“显式”。

4.3.2 含义

表达式 (Celsius 32 212) 将动词Celsius应用到参数32 212上,计算过程可以通过下面的模型描述:

   y =: 32 212
   t =: y - 32
   t * 5 % 9
0 100 
如上所示,除了第一行,计算按照脚本的定义进行。

4.3.3 参数变量

参数(32 212)通过变量y提供给脚本。一元函数中“参数变量”命名为y。(二元函数中,下面我们将看到,左参数命名为x,右参数命名为y)

4.3.4 局部变量

再看一下Celsius的定义:
   Celsius =: 3 : 0
t =: y - 32
t * 5 % 9
)
可以看到它包含了对变量t的赋值。该变量只在Celsius执行过程中使用。不幸的是对t的赋值干扰了Celsius外部其他任何也叫做t的变量的值例如:
   t =: 'hello' 
   
   Celsius 212
100
   
   t
180

可以看到变量t的原始值('hello')在Celsius执行的过程中被改变了。为了避免这种事情,我们可以把Celsius内部的t声明为严格的私有变量,和其他也叫做t的变量区分开来。
为了达到这种效果,可以使用特殊形式的赋值—— =.(等号 点)。新版本的定义变为:

   Celsius =: 3 : 0
t =. y - 32
t * 5 % 9
)
我们说Celsius中的t是局部变量或者说t局部化于Celsius。作为对比,定义于函数外部的变量叫做全局变量。现在可以看到在Celsius内对t的赋值不会影响任何全局变量t:
   t =: 'hello'
    
   Celsius 212
100
   
   t
hello

参数变量y也是局部变量。因此(Celsius 32 212)的执行过程可以用下述模型更准确的描述:
   y =. 32 212
   t  =. y - 32
   t * 5 % 9
0 100

4.3.5 二元动词

Celsius是一个一元动词,以 3 : 0 开头,定义使用单个参数y。相应的,二元动词以 4 : 0 开头。左右参数命名为x和y。例如:两个数字的“正差”是大的数字减去小的数字:
   posdiff =: 4 : 0
larger  =. x >. y
smaller =. x <. y
larger - smaller 
)
3 posdiff 44 posdiff 3
11


4.3.6 单行脚本

一个单行脚本可以写成一个字符串,将该字符串作为冒号连词的右参数。
   PosDiff =: 4 : '(x >. y) - (x <. y)'
   4 PosDiff 3
   

4.3.7 控制结构 

在目前我们看到的以脚本方式定义的函数中,程序从第一行的表达式开始执行,然后接着下一行,再下一行,直到最后。

这种笔直的执行路径不是唯一的可能路径。下一步要执行哪个表达式是可以选择的。

例如:这里有一个函数用来计算给定长宽高的长方体的体积。假如该函数先测试它的参数是不是一个长度为3的列表(长、宽和高)。如果是,那么计算体积,否则返回字符串'ERROR'。
   volume =: 3 : 0
if.   3 = # y
do.   * / y
else. 'ERROR'
end.
)
 
测试一下:
volume 2 3 4volume 2 3
24ERROR

if.end. 的所有东西构成一个“控制结构”。其中if. do. else.end. 叫做
“控制词”。第12章可以看到更多的控制结构。
 

4.4 隐式函数与显式函数比较

我们已经看到两种不同的定义函数方式。显式定义,本章介绍的,之所以称为显式是因为它显式地涉及参数变量。如上面的volume函数,变量y就是显式参数。

相应地,我们在前面章节看到的定义函数方式称为“隐式”,因为不涉及参数变量。例如:对比一下“正差”函数的显式和隐式定义:
   epd =: 4 : '(x >. y) - (x <. y)'
   
   tpd =: >. - <.
很多隐式定义的函数可以显式定义,反之亦然。哪种方式更好取决于我们定义函数时觉得哪种最自然。我们要选择是将问题分解为作为脚本的步骤序列呢?还是一些小函数的组合?

隐式定义比较紧凑。因此隐式函数利于系统分析和转换。事实上,J系统能够为很大范围内的隐式函数自动生成类似“inverses”和“derivatives”这样的转换。

4.5 函数作为值

函数也是值,一个值可以通过键入表达式显示出来。这里的表达式可以是一个简单的名字。下面是一些隐式和显式函数的值:
   - & 32
+-+-+--+
|-|&|32|
+-+-+--+
   
   epd
+-+-+-------------------+
|4|:|(x >. y) - (x <. y)|
+-+-+-------------------+
   
   Celsius
+-+-+-----------+
|3|:|t =. y - 32|
| | |t * 5 % 9  |
+-+-+-----------+

每个函数的值显示为盒子结构。这是默认显示方式,我们也可以选择其他的显式方式:参考第27章。现在我只讨论“线性显示”,该显示方式将函数显示为一个字符序列,该序列可以用来再次输入产生函数。我们可以让系统以“线性显示”的方式显示函数,通过输入:
   (9!:3) 5
然后我们再看函数的显示:
   epd
4 : '(x >. y) - (x <. y)'

在后面的章节中,将经常以“线性显示”来展示函数的值。  

4.6 脚本文件 

我们已经看到了脚本(J代码行)被用来定义单个变量:文本变量或者函数。然而,一个J语言文件可以保存很多定义。 这样的文件被称为脚本文件,它的用途在于通过读取该文件可以执行它里面的所有定义。

下面是一个例子。使用你的文本编辑器,创建一个文件,包含下面两行代码:
                 squareroot =: %:
                 z =: 1 , (2+2) , (4+5)
J脚本文件通常以 .ijs 为后缀,假设创建的文件(Windows系统)的路径名为:c:\temp\myscript.ijs
然后在J会话中,为了方便,我们定义一个变量F来保存文件名:
   F =: 'c:\temp\myscript.ijs' 
接下来就可以通过下面的命令来执行这个脚本:
       0!:1 < F
然后我们将在屏幕上看到脚本文件中的行:
   squareroot =: %:
   z =: 1 ,(2+2), (4+5)

现在我们可以使用脚本文件中的定义了:
   z
1 4 9
   
   squareroot z
1 2 3 
 
J会话中常见的活动基本上是:编辑脚本文件,载入(重载入)脚本文件中的定义,在键盘上发起计算。能从一个J会话传递到另一个J会话的只有脚本文件。J系统的状态,或者内存,在会话结束时统统消失;所有会话中输入的定义也随之消失。因此在J会话结束前,要确保脚本文件已经更新,也就是确保脚本文件包含了所有你想要保存的定义。

在会话开始时,J系统会自动载入一个特定的脚本文件,叫做“profile”。(更多细节参考第26章)。该配置文件可以编辑,因此是一个记录你自己常用定义的好地方。现在到了第4章的结尾,同时也是第一部分的结尾。下来的章节将更深入和细致地讨论我们在第一部分遇到的这些主题。

没有评论:

发表评论