2012年11月22日星期四

IO对Haskell Heap求值

这是对Edward Z. YangHaskell Heap系列的翻译。
感谢Edward的无私奉献,允许我对这些文章进行翻译。
Edward以Creative Commons Attribution-ShareAlike 3.0 Unported License对文章进行了授权,所以译文也以此License发布。 



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

                IO对Haskell Heap求值    

原文地址为:http://blog.ezyang.com/2011/04/io-evaluates-the-haskell-heap/  

第一次看到这个系列吗?那么请从头开始看。

在今天的文章中,我们关注于——这个之前被忽略的,植根于Haskell heap周围来打开礼物的人。归根结底,Haskell heap上的礼物不会自发地打开自己。
必须有人来打开第一个礼物。
如果Haskell heap不和外面的世界交互,那么也就没有礼物需要被打开:因此IO函数就是那些会打开礼物的家伙。它们将打开哪些礼物?这个对于很多函数来说可不是那么明显的,所以我们将聚焦于一个打开礼物行为特别明显函数:evaluate。它告诉你去...
打开一个礼物。

如果你得到一个基本值,那么就结束了。但是,当然,你可能得到了一个礼物卡(constructor):
你会打开剩下的礼物吗?尽管你心中非常不满,但是答案是。evaluate只让你打开一个礼物。如果礼物已经打开了,那么没什么事再需要你做了。

进一步提示:如果你想对更多的东西求值,就做一个礼物,该礼物里有能为你打开那些东西的幽灵!当论及lazy IO时,这种做法一个常用的例子是:evaluate (length xs),如果你不太明白的话,不要担心:我实际上还没说明我们如何制作礼物!
即使我们仅仅正在打开一个礼物,很多事情也会发生,就像上一篇文章中提及的。它会执行一些IO...

当求值进行时,我们能直窥求值的窗口是(译注:让鬼魂喊一声):当我们正常运行程序时,我们看不到正在打开的礼物;但是如果我们让鬼魂在它被打扰时也大声喊一下,我们就能拿回这个信息。实际上,这就是Debug.Trace做的事!

还有别的方法可以看到evaluation如何进行的(译注:作者没说别的方法是什么)。礼物可能会爆炸(译注:这里话题转变的很突然啊):这种礼物是会爆炸的诱饵礼物,也被称为“bottom”(译注:bottom指符号⊥,在Haskell中, ⊥是一个占位符,表明计算不会成功。比如无穷循环,或者调用error,或者特殊值undefined)。
爆炸可能是由于undefined或者error "Foobar"引起的。
爆了。

2012年11月16日星期五

Haskell Heap上的求值

这是对Edward Z. YangHaskell Heap系列的翻译。
感谢Edward的无私奉献,允许我对这些文章进行翻译。
Edward以Creative Commons Attribution-ShareAlike 3.0 Unported License对文章进行了授权,所以译文也以此License发布。 


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

                Haskell Heap上的求值 

原文地址为:http://blog.ezyang.com/2011/04/evaluation-on-the-haskell-heap/

第一次看到这个系列吗?那么请从头开始看。
圣诞礼物中的幽灵
在今天这篇文章中,我们简要探究一下当你打开Haskell heap中闹鬼的礼物时会发生的许多事情。除了常量和那些已经被求值的事物,Haskell heap中的所有事物基本上都是闹鬼的。因此真正的问题是幽灵在礼物上搞了什么鬼。

最简单的情形是,什么也没干!
这和礼物卡不同(译注:这指的是x=y,你打开了礼物盒子x,里面的幽灵告诉你礼物在盒子y,所以说与礼物卡不同),你必须打开下一个礼物(Haskell不允许你对一个thunk求值后,又不按照间接指示继续前进...)(译注:注意看上下的漫画,当你打开一个礼物时,幽灵告诉你礼物不在这里,在另一个地方。你要按照这个指示去下一个地方拿礼物,也就是按照指示间接而非直接地拿到礼物。如果你打开礼物/thunk后,不按照找指示做,那么Haskell是不会允许你打开/求值的。)
更常见的是,幽灵很lazy,当他被唤醒时,不得不打开别的礼物来确定你第一次拿到的礼物里面有什么。(译注:同样注意看下面的漫画,这里的幽灵指的是你打开的第二个礼物里的幽灵。)


简单的基本运算需要打开所有涉及的礼物。

但是幽灵也可能无缘无故地打开了另一个礼物...(译注:其实原因就是seq)


或者执行一些IO ...


注意,他打开的任何礼物都有可能唤醒更多的幽灵:



最终真成了幽灵狂欢节,这都是为了打开一个礼物!


打开一个礼物(thunk)可能会造成这样一个级联效应,如果你习惯了heap上的所有对象都已经被打开(求值)的话,那么这就是造成对惰性求值的计时会让你感到惊讶的原因。所以了解真相的关键是:搞清楚幽灵何时决定它需要打开一个礼物(strictness分析),以及你的礼物是否已经被打开了(amortized分析)。

上一篇:The Haskell Heap 
下一篇:IO对Haskell Heap求值

2012年11月15日星期四

5 构建阵列(Arrays)

5 构建阵列(Arrays)

这是对<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,我将不胜感激。

----------------------------------------------------------------------------------
本章的主题是构建阵列。首先考察如何从列表构建阵列,然后学习一些将阵列拼接在一起构成更大阵列的方法。

5.1 通过给列表塑形来构建阵列

5.1.1 回顾

回忆一下第2章中“元素”的含义。数字列表中的元素就是数字本身。表格的元素就是表格的每行。三维阵列的元素就是阵列的每个平面。
再回想一下,x $ y 生成一个以x为形状,y的元素为元素的阵列。也就是说,阵列的维度有列表x给出。例如:
2 2 $ 0 1 2 32 3 $ 'ABCDEF'
0 1
2 3
ABC
DEF


如果y的元素数比维度表要求的少,那么将会循环使用y直到元素数满足要求。例如:
2 3 $ 'ABCD'2 2 $ 13 3 $ 1 0 0 0
ABC
DAB
1 1
1 1
1 0 0
0 1 0
0 0 1


动词“塑形”,也就是二元模式 $,有相应的一元模式——“求形”,一元模式的$返回阵列的维数列表(也就是阵列的形状)。比如:
A =: 2 3 $ 'ABCDEF'$ Aa =: 'pqr'$ a
ABC
DEF
2 3pqr3


对于任何阵列A,它的维度表($ A)是一个一维列表(阵列的形状)。因此$ $ A是一个只包含一个元素(阵列的秩),$ $ $ A是一个值包含数字1的列表。
A$ A$ $ A$ $ $ A
ABC
DEF
2 321



5.1.2 空阵列

阵列的每个维数都可以为0。一个长度为0的空列表可以通过以0作为维数表来构建,任何值都可以作为空列表的值。
E =: 0 $ 99$ E
0

如果E为空,那么E没有任何元素,在它后面追加一个元素后,结果将得到含有一个元素的列表。
E$ Ew =: E ,98$ w
0981


类似的,如果ET是一个0行3列的空列表,当增加一行时,结果将得到1行3列的表格。
ET =: 0 3 $ 'x'$ ET$ ET , 'pqr'
0 31 3



5.1.3 构造一个标量

假如我们要构造一个标量。标量没有维度,它的维度表为空。我们可以通过以空列表作为 $ 的左参数来构造标量:
S =: (0$0) $ 17$ S$ $ S
170



5.1.4 广义塑形

我们知道,(x $ y)生成一个以x为形,y的元素为元素的阵列。那就是说,在广义上讲,(x$y)的形状不仅仅由x决定,而是由x和y的元素的形状共同决定。如果y是一个表格,那么y的元素就是行(一个列表)。在下面的例子中,Y的元素的形状是Y的行长度,也就是4。
X =: 2Y =: 3 4 $ 'A'Z =: X $ Y$ Z
2AAAA
AAAA
AAAA
AAAA
AAAA
2 4
下一节我们将学习如何从连接已有阵列来构造新阵列。

5.2 追加,或者首尾相连

回忆一下,阵列可以看作是元素的列表,所以一张表格的元素就是它的行。动词 ,(逗号)叫做“追加”。表达式 (x, y) 是一个由x和y的元素组成的列表,y的元素和x的元素首尾相接。
   B =: 2 3 $ 'UVWXYZ'
   b =:   3 $ 'uvw'
   
aba , bABA , B
pqruvwpqruvwABC
DEF
UVW
XYZ
ABC
DEF
UVW
XYZ


在上例的 (A, B) 中,A的元素是长度为3的列表,B的元素也是一样。因此A和B的元素兼容,也就是说,有着相同的秩和长度。如果它们不兼容会怎么样?在这种情况下“追加”动词将自动尝试延长某个参数去适应另一个,通过使它们有相同的秩,填充长度,需要时重复使用标量等方式。下面的例子将展示这些方法。

5.2.1 使秩相同

假如我们要把一行追加到表格后面。例如,要把3个字符的列表b(上面那个b)追加到2x3的表格A(上面的A)后面,形成一个新行。

AbA , b
ABC
DEF
uvwABC
DEF
uvw


注意,我们想要在有两个元素的A后面追加有一个元素的b,但是b不是只有一个元素。我们通过将b重塑形成一个1x3的表格来达到目的,就是说,通过提升b的秩来完成。但是,这不是必须的,因为,正如我们看到的,动词“追加”自动地提升了低秩的参数,使之成为一个单元素阵列——通过提供首维度1 来完成。

AbA , (1 3 $ b)A , bb , A
ABC
DEF
uvwABC
DEF
uvw
ABC
DEF
uvw
uvw
ABC
DEF


5.2.2 填充长度

当一个参数的元素比另一个参数少时,将会用填充来增加长度。字符阵列用空字符填充,数值阵列用0填充。

AA , 'XY'(2 3 $ 1) , 9 9
ABC
DEF
ABC
DEF
XY
1 1 1
1 1 1
9 9 0


5.2.3 重复标量

动词“追加”的标量参数需要时会被重复使用以匹配另一个参数。下面的例子中,注意标量 '*' 是如何重复,而向量 (1 $ '*') 是如何填充的。
AA , '*'A , 1 $ '*'
ABC
DEF
ABC
DEF
***
ABC
DEF
*


5.3 缝合,或者并排连接

二元动词 ,.(逗号 点)叫做“缝合”。表达式(x ,. y)中,x中的每个元素和y中相应的元素相连,形成结果中的一个元素。

aba ,. bABA ,. B
pqruvwpu
qv
rw
ABC
DEF
UVW
XYZ
ABCUVW
DEFXYZ


5.4 叠层,或者面对面连接

动词 ,:(逗号 冒号)称为“叠层”。(x ,: y)的结果将永远是一个有两个元素的阵列,第一个元素是x,第二个是y。
aba ,: b
pqruvwpqr
uvw


如果x和y都是表格,我们可以想象结果是一个表格叠在另一个上面,形成一个3维阵列,第一维的长度为2.
ABA ,: B$ A ,: B
ABC
DEF
UVW
XYZ
ABC
DEF

UVW
XYZ
2 2 3

5.5 拼接

动词 ;(分号)称为“拼接”。它可以方便地构建盒子列表。

'good' ; 'morning'5 ; 12 ; 1995
+----+-------+
|good|morning|
+----+-------+
+-+--+----+
|5|12|1995|
+-+--+----+

注意示例 5;12;1995 展示了 (x;y) 并不仅仅总是(< x), (< y)。既然“拼接”用来构建盒子列表,当它的右参数已经是盒子列表时,它能分辨出来。如果我们定义一个动词来生成(< x) , (<y):

   foo =: 4 : '(< x) , (< y)'
 
我们可以比较一下二者的区别:
 
1 ; 2 ; 31 foo 2 foo 3
+-+-+-+
|1|2|3|
+-+-+-+
+-+-----+
|1|+-+-+|
| ||2|3||
| |+-+-+|
+-+-----+




5.6 解构阵列

我们已经看到了四个二元动词:“追加”(,),“缝合”(,.),“叠层”(,:)和“拼接”(;)。它们每一个都有一元模式,下面我们来看看。

5.6.1 推平

一元 ; 叫做“推平”。它将参数的元素拆盒后组装成列表。

B =: 2 2 $ 1;2;3;4; B$ ; B
+-+-+
|1|2|
+-+-+
|3|4|
+-+-+
1 2 3 44


5.6.2 散开

一元 , 叫做“散开”。它将参数的元素组装成列表。

B, B$ , B
+-+-+
|1|2|
+-+-+
|3|4|
+-+-+
+-+-+-+-+
|1|2|3|4|
+-+-+-+-+
4

5.6.3 散开元素

一元模式 ,. 叫做“散开元素”。它将参数的每个元素散开后组装成表格。

k =: 2 2 3 $ i. 12,. k
0  1  2
3  4  5

6  7  8
9 10 11
0 1 2 3  4  5
6 7 8 9 10 11


“散开元素”在通过列表构造单列表格时很有用。

b,. b
uvwu
v
w


5.6.4 元素化

一元 ,: 称为“元素化”。它将任何阵列构造成一个单元素阵列,通过提供首维度1来完成。

A,: A$ ,: A
ABC
DEF
ABC
DEF
1 2 3

5.7 阵列:大和小

我们已经看到过,一个阵列可以通过动词$来构造。

   3 2 $ 1 2 3 4 5 6
1 2
3 4
5 6


对于小的阵列——阵列的内容可以在一行列出来——也有替代$的方法,这些方法可以不需要提供维度。

> 1 2 ; 3 4 ; 5 61 2 , 3 4 ,: 5 6
1 2
3 4
5 6
1 2
3 4
5 6

构建大型表格,下面有一个方便的方法。首先,有一个“实用”动词(一个对我们的目标有帮助的动词,但是我们现在不需要研究它的定义)。
 
   ArrayMaker =: ". ;. _2

ArrayMaker的目的是通过脚本来逐行地构造数值表格。

   table =: ArrayMaker 0 : 0
1 2 3
4 5 6
7 8 9
)
table$ table
1 2 3
4 5 6
7 8 9
3 3

(ArrayMaker的工作原理,请参考第17章)。盒子阵列也可以用同样的方式从脚本输入:

   X =:  ArrayMaker  0 : 0
'hello' ; 1 2 3 ; 8
'Waldo' ; 4 5 6 ; 9
)
X$ X
+-----+-----+-+
|hello|1 2 3|8|
+-----+-----+-+
|Waldo|4 5 6|9|
+-----+-----+-+
2 3


第5章就到这里。

说明:
  1. Array在APL系语言中,个人感觉翻译成阵列数组更贴切一些。

The Haskell Heap

这是对Edward Z. YangHaskell Heap系列的翻译。
感谢Edward的无私奉献,允许我对这些文章进行翻译。
Edward以Creative Commons Attribution-ShareAlike 3.0 Unported License对文章进行了授权,所以译文也以此License发布。 

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

                  The Haskell Heap

原文地址为:http://blog.ezyang.com/2011/04/the-haskell-heap/

Haskell heap是一个相当古怪的地方。它和传统的、严格求值的语言的heap可不一样...
...传统的heap就是一个杂货堆(原始的旧数据)!

在Haskell heap中,每个项都被漂亮地包装在盒子中:Haskell heap是礼物堆(thunks)。
如果你确实想知道礼物里面是什么,你得打开它(对它求值)。
礼物往往有名字,有时当你打开礼物时,你会得到一张礼物卡(data constructor)。这些礼物卡有两个特征:它们有名字(比如Just礼物卡,或者Right礼物卡);它们可以告诉你你剩下的礼物在哪里。可能不止一个礼物哦(比如tuple礼物卡),如果你是个幸运的家伙!
但是就像买了礼物卡放在那里不用一样(礼物卡公司就是这么赚钱的!),你也不是必须要拿回那些礼物。

Haskell heap上的礼物可是相当淘气的。一些礼物当你打开时会爆炸;而另一些礼物闹鬼哦,这些鬼魂被打扰时会偷偷打开别的礼物的。




理解你打开礼物时会发生什么 是理解Haskell程序时间和空间行为的关键。


在这个系列中,Edward将进军网络漫画,以此来展示惰性求值语言中关于“求值”的一些关键的操作上的概念。希望你喜欢!

下一篇是:Haskell Heap上的求值

技术注解
从技术上来讲,这个系列应该叫“The GHC Heap”。但是,我将尽力最大程度地避免GHC主义,简单地为在操作上探究任一种惰性语言提供一种隐喻。一开始,这个系列叫做“Bomberman Teaches Lazy Evaluation”,但是当我尝试以炸弹作为隐喻,来类比错误的或者无法终止的thunks时,我发现自己想要个更好的隐喻——它要特别能体现惰性求值的一些关键点:它要能体现已求值和未求值的区别,以及礼物一旦打开,就向所有人开放这个事实。使用术语“boxed”带有一些暗示性:实际上,boxed或者lifted values在GHC中恰好是那些可以无穷的值,而unboxed values和那些你在C heap中看到的更类似一些。然而,像Java这样的语言也使用术语“boxed”用来表示看起来像objects的primitive values。为了不至于混淆,从现在起我们将不再使用术语“boxed”(实际上,我们也不会讨论unboxed types)。



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章的结尾,同时也是第一部分的结尾。下来的章节将更深入和细致地讨论我们在第一部分遇到的这些主题。