-
Notifications
You must be signed in to change notification settings - Fork 5
/
modules.html
444 lines (368 loc) · 23.3 KB
/
modules.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="keywords" content="模块,属性,编译,导入,导出,宏,语法,
Erlang, modules, attributes, Compile, export, import, function, macro, syntax" />
<meta name="description" content="Module syntax for Erlang: how to write basic functions, export and import them with module attributes, compile Erlang code for the VM, write macros, etc." />
<meta name="google-site-verification" content="mi1UCmFD_2pMLt2jsYHzi_0b6Go9xja8TGllOSoQPVU" />
<link rel="stylesheet" type="text/css" href="static/css/screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shCore.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shThemeLYSE2.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/print.css" media="print" />
<link href="rss" type="application/rss+xml" rel="alternate" title="LYSE news" />
<link rel="icon" type="image/png" href="favicon.ico" />
<link rel="apple-touch-icon" href="static/img/touch-icon-iphone.png" />
<link rel="apple-touch-icon" sizes="72x72" href="static/img/touch-icon-ipad.png" />
<link rel="apple-touch-icon" sizes="114x114" href="static/img/touch-icon-iphone4.png" />
<title>Modules | Learn You Some Erlang for Great Good!</title>
</head>
<body>
<div id="wrapper">
<div id="header">
<h1>Learn you some Erlang</h1>
<span>for great good!</span>
</div> <!-- header -->
<div id="menu">
<ul>
<li><a href="content.html" title="Home">Home</a></li>
<li><a href="faq.html" title="Frequently Asked Questions">FAQ</a></li>
<li><a href="rss" title="Latest News">RSS</a></li>
<li><a href="static/erlang/learn-you-some-erlang.zip" title="Source Code">Code</a></li>
</ul>
</div><!-- menu -->
<div id="content">
<div class="noscript"><noscript>Hey there, it appears your Javascript is disabled. That's fine, the site works without it. However, you might prefer reading it with syntax highlighting, which requires Javascript!</noscript></div>
<h2>Modules</h2>
<h3><a class="section" name="what-are-modules">什么是模块</a></h3>
<img class="left" src="static/img/modules.png" width="168" height="148" alt="A box with functions written on it" title="And yes, I do believe functions are squiggly little colorfoul lines stuffed in a cardboard box." />
<p>使用交互式的shell被认为是动态语言非常重要的一部分。通常对于测试各种代码是非常有用的。Erlang中绝大部分的基础数据类型都不需要编写一个文件来使用。
你可以丢掉键盘,出去打一天球,但是如果你在这停下来,你将是个非常差的Erlang程序员。代码需保存起来,以备将来使用!</p>
<p>这就是模块的作用。模块是将很多函数放到同一个命名空间,存储到一个文件中。
另外,所有的函数都需要在模块中定义。你已经使用了模块,但是你没有察觉它。
前面章节提到过BIF,像 <code>hd</code>和<code>tl</code>,实际上它们属于<code>erlang</code>这个模块,
同样所有的数学,逻辑和布尔操作符都属于这个模块。
和其它的函数不同的是,当你使用Erlang的时候,属于<code>erlang</code>这个模块会被自动引入。
剩下的函数,你就需要用 <code>Module:Function(Arguments)</code>这种形式来调用了。
</p>
<p>你可以自己尝试下:</p>
<pre class="brush:eshell">
1> erlang:element(2, {a,b,c}).
b
2> element(2, {a,b,c}).
b
3> lists:seq(1,4).
[1,2,3,4]
4> seq(1,4).
** exception error: undefined shell command seq/2
</pre>
<p>List模块中的<code>seq</code>函数并没有被自动引入,但是<code>element</code>就被自动引入了。
’undefined shell command‘这个错误是因为shell在查找像<code>f()</code>这样的shell命令,并且没有找到该命令的时候产生的。
<code>erlang</code>模块中有些不常使用的函数,并不会自动引入。
</p>
<p>按道理来说,你应当将功能类似的函数放到一个模块中。
列表相关的操作都被放到了<code>lists</code>模块中,关于IO的操作(输出到终端或写文件)都被放到了<code>io</code>模块中。
你将遇到唯一一个不遵守前面提到的规则的模块是<code>erlang</code>模块,因为它包含了数学,转换,多进程和调整虚拟机相关的设置等。
它们除了都是内置函数就没有其它的共同点了。
你应当避免创建一个像<code>erlang</code>模块的模块并且要专注于清晰的逻辑分层。
</p>
<h3><a class="section" name="module-declaration">模块声明</a></h3>
<img class="right" src="static/img/declaration.png" width="95" height="139" alt="A scroll with small text on it" title="I HEREBY DECLARE THIS MODULE AS INDEPENDENT FROM THE OTHERS"/>
<p>当编写一个模块的时候,你能声明两种东西: <em>函数</em>和<em>属性</em>.
属性是模块用来描述它自己的元数据,其中包括模块的名字,什么函数是对外可见的和代码编写者等。
这种元数据是非常有用的,它能告诉编译器该如何编译同时它可以让人们从编译过的代码中得到有用信息而不需要去看代码。
</p>
<p>
有大量的模块属性在全世界的Erlang代码中使用,事实上,你可以根据你的喜好定义你自己的属性。
(There is a large variety of module attributes currently used in Erlang code across the world;
as a matter of fact, you can even declare your own attributes for whatever you please.)
这里有很多预先定义好的属性,在代码中,它们比别的属性更加常用。所有这些模块属性都使用这种写法<code>-Name(Attribute).</code>。
为了让你的模块能编译,它们当中唯一必须的是:</p>
<dl>
<dt>-module(Name).</dt>
<dd>
这始终是一个文件的第一个属性(和语句),因为一个很好的理由:
它是这个模块的名字,并且这个<var>Name</var>是一个<a class="chapter" href="starting-out-for-real.html#atoms" title="atom definition">原子</a>。
这个就是你用来从别的模块调用函数的名字。
这种调用的写法是<code>M:F(A)</code>,其中<var>M</var>是模块名,<var>F</var>是函数,<var>A</var>是参数。
</dd>
</dl>
<p>是时候让我们写一些代码了!我们第一个模块将会非常简单且无用。
打开文本编辑器输入下面这些,然后把它们保存到<code>useless.erl</code>中:</p>
<pre class="brush:erl">
-module(useless).
</pre>
<p>
这一行文本是一个有效的模块。
真的!当然没有函数它很没用。让我们先决定什么函数从我们的‘useless’模块中导出。
为了做这事情,我们将用另一个属性:
</p>
<dl>
<dt>-export([Function1/Arity, Function2/Arity, ..., FunctionN/Arity]).</dt>
<dd>
这是用来定义该模块什么函数可以由外界被调用。
它需要一个有元数的函数列表。(It takes a list of functions with their respective arity.)
函数的元数是一个整数,代表有几个参数将传入函数。
这个是非常重要的信息,因为在同一个模块中,我们可以定义同名但元数不同的函数。
函数<code>add(X,Y)</code>和<code>add(X,Y,Z)</code>被认为是不同的,分别用<code>add/2</code>和<code>add/3</code>
来表示。
</dd>
</dl>
<div class="note">
<p><strong>注意:</strong>
导出的函数代表模块的接口。定义一个只导出必要函数的接口是非常重要的。(It is important to define an interface revealing strictly what is necessary for it to be used and nothing more.)
这样做,可以让你随意优化其它的[隐藏的]实现细节,而不至于因为优化导致其它依赖你代码的模块不工作。
</p>
</div>
<p>我们无用的模块将导出第一个有用的函数叫‘add’,它将接受2个参数。
<code>-export</code>属性将被添加到模块的声名中:</p>
<pre class="brush:erl">
-export([add/2]).
</pre>
<p>接着,让我写这个函数:</p>
<pre class="brush:erl">
add(A,B) ->
A + B.
</pre>
<p>函数的语法是<code>Name(Args) -> Body.</code>,其中<var>Name</var>是一个原子,
<var>Body</var>是用逗号分割的多个Erlang表达式。函数以句号结束。Erlang并不使用‘return’关键字。
‘Rerturn’是没用的!取而代之,函数的最后一个逻辑表达式所返回的值将会返回给调用者。
</p>
<p>
添加下面的函数(为什么,因为每个教程都会有一个‘Hello world’的例子!即便在第四章!),
并且不要忘记将它添加到<code>-export</code>属性中。
</p>
<pre class="brush:erl">
%% Shows greetings.
%% io:format/1 is the standard function used to output text.
hello() ->
io:format("Hello, world!~n").
</pre>
<p>我们从这个函数可以看到,注释单行是用<code>%</code>符号开头(使用<code>%%</code>完全是风格问题)。
<code>hello/0</code>函数同样展示了怎么从外部模块调用模块内的函数。在这个场景,就像注释里面写的,
<code>io:format/1</code>是一个标准的输出到文本的函数。
</p>
<p>最后一个添加到模块中的函数,使用了<code>add/2</code>和<code>hello/0</code>:</p>
<pre class="brush:erl">
greet_and_add_two(X) ->
hello(),
add(X,2).
</pre>
<img class="left" src="static/img/imports.png" width="145" height="134" alt="A box being put in another one" title="Yes, I'm reusing drawings on that one" />
<p>
不要忘记在导出列表中添加<code>greet_and_add_two/1</code>。
因为是在同一个模块中声名的函数,所以对<code>hello/0</code>和<code>add/2</code>的调用,并不需要在前面添加模块的名称。
</p>
<p>
你是受否想让调用<code>io:format/1</code>像调用<code>add/2</code>或其它这个某块中定义的函数一样,
你可以在文件开头添加以下模块属性: <code>-import(io, [format/1]).</code>。
然后你就可以直接调用<code>format("Hello, World!~n").</code>。
更常见的,<code>-import</code>属性遵循以下原则:</p>
<pre class="brush:erl">
-import(Module, [Function1/Arity, ..., FunctionN/Arity]).
</pre>
<p>
程序员在写代码的时候,导入函数并不比程序员的快捷方式强。
(Importing a function is not much more than a shortcut for programmers when writing their code.)
Erlang程序员非常不鼓励使用<code>-import</code>属性,因为有些人发现它降低了代码的可读性。
在<code>io:format/2</code>和<code>io_lib:format/2</code>同时存在的情况下。
为了找出使用了那个函数,就要到文件头去看那个模块被引入。
所以,保留模块名被认为是更好的做法。通常,你会看到唯一引入的函数是从列表模块:
列表模块中的函数比别的模块中的函数使用频率更高。
</p>
<p>你的<code><a class="source" href="static/erlang/useless.erl" title="final implementation">useless</a></code>模块将会看起来像下面的文件:</p>
<pre class="brush:erl">
-module(useless).
-export([add/2, hello/0, greet_and_add_two/1]).
add(A,B) ->
A + B.
%% Shows greetings.
%% io:format/1 is the standard function used to output text.
hello() ->
io:format("Hello, world!~n").
greet_and_add_two(X) ->
hello(),
add(X,2).
</pre>
<p>We are done with the "useless" module.
我们完成了"useless"模块。你现在可以将它保存到<code>useless.erl</code>里。
‘.erl’是标准的Erlang代码扩展名,文件名应该定义在<code>-module</code>属性中的模块名,加上‘.erl’扩展名。
</p>
<p>在展示如何编译模块,并最终尝试所有激动人心的函数前,我们将看到如何定义和使用宏。
Erlang的宏和C的‘#define’语句非常相似,主要用于定义短函数和常量。
它们是用文本表示的非常简单的表达式,在代码被编译到VM前它将被替换掉。
这类宏主要用来避免潜伏在你模块中那些魔法数值。
宏定义以<code>-define(MACRO, some_value).</code>形式定义为模块属性,并且在模块中的任何函数内用<code>?MACRO</code>来引用。
‘函数’宏可以这样来写<code>-define(sub(X,Y), X-Y).</code>,并且可以通过 <code>?sub(23,47)</code>来使用,
在编译的时候编译器会替换为<code>23-47</code>。有些人会使用更复杂的宏,但是基础的语法是一样的。</p>
<h3><a class="section" name="compiling-the-code">编译代码</a></h3>
<p>
Erlang代码都是被编译成字节码供虚拟机使用。
你可以在很多地方调用编译器:在命令行中可以用<code>$ <a class="docs" href="http://erlang.org/doc/man/erlc.html" title="erlc manual">erlc</a> flags file.erl</code>,
在shell或者模块中可以用<code>compile:file(FileName)</code>,在shell中可以用<code>c()</code>等等。</p>
<p>是时候尝试编译我们的useless模块。打开Erlang的shell,输入:</p>
<pre class="brush:eshell">
1> cd("/path/to/where/you/saved/the-module/").
"Path Name to the directory you are in"
ok
</pre>
<p>
默认情况下,shell只寻找同目录下的文件,这个目录一般是标准库的目录和Erlang启动的文件夹:
Erlang的shell定义了一个<code>cd/1</code>命令,用来告诉Erlang进行目录切换,这样浏览我们的文件就不那么麻烦了。
Windows用户记得要用正斜杠。当这些都完成了,执行下面的命令:</p>
<pre class="brush:eshell">
2> c(useless).
{ok,useless}
</pre>
<p>如果你看到别的信息,确认是否在正确的文件夹下,文件名是否正确,并且你的<a class="source" href="static/erlang/useless.erl" title="check against THIS!">模块</a>中没有错误。
一旦你成功编译代码,你会看文件夹下除了<code>useless.erl</code>还新增加了<code>useless.beam</code>文件。这个就是被编译过的模块。
让我们尝试我们第一个函数:</p>
<pre class="brush:eshell">
3> useless:add(7,2).
9
4> useless:hello().
Hello, world!
ok
5> useless:greet_and_add_two(-3).
Hello, world!
-1
6> useless:not_a_real_function().
** exception error: undefined function useless:not_a_real_function/0
</pre>
<p>函数都如预期那样工作了:<code>add/2</code>将两个数相加,<code>hello/0</code>输出”Hello, world!“,<code>greet_and_add_two/1</code>同时做了前面两个函数的事情!
当然,你也许会问<code>hello/0</code>在完成输出后为什么会返回‘ok’。
这是因为Erlang的函数和表达式必须<strong>总是</strong>返回某个值,即便在其它语言中这不是必须的。
所以,<code>io:format/1</code>返回‘ok’表示一切正常,没有错误。</p>
<p>第6个表达式显示一个错误告诉我们某个函数并不存在。
如果我们忘记导出某个函数,当你要尝试调用该函数的时候,这个错误信息就会显示出来。
</p>
<div class="note">
<p><strong>注意:</strong>
如果曾经你想知道‘.beam’的含义,‘.beam’实际上代表<em>Bogdan/Björn's Erlang Abstract Machine</em>,Erlang虚拟的名字。
其它的Erlang虚拟机也存在过,并且这已经不再被使用已经成为历史了:
JAM(Joe's Abstract Machine),一个受到Prolog's <a class="external" href="http://en.wikipedia.org/wiki/Warren_Abstract_Machine" title="Warren Abstract Machin">WAM</a>影响的早期虚拟机,
它尝试将Erlang编译成C,再编译成本地代码。
基准测试表明从这个实现中只有一点性能提升并且该想法已经被放弃了。</p>
</div>
<p>
Erlang有很多编译标志来控制模块的编译。
你可以从<a class="docs" href="http://erlang.org/doc/man/compile.html">Erlang文档</a>中找到这些编译标志。
常用的标志有:</p>
<dl>
<dt>-debug_info</dt>
<dd>Erlang的工具像调试器,代码覆盖和静态分析工具等将使用模块的调试信息来工作。</dd>
<dt>-{outdir,Dir}</dt>
<dd>默认情况下,Erlang的编译器将在当前目录中创建‘beam’文件。
这个标志将让你选择编译的文件放在何处。</dd>
<dt>-export_all</dt>
<dd>这个标志将忽略<code>-export</code>模块属性,并将导出所有的函数。
这个主要用于测试和开发新的代码,但是你不应当在生产代码中使用。
</dd>
<dt>-{d,Macro} or {d,Macro,Value}</dt>
<dd>在模块中定义一个宏,其中<var>Macro</var>原子。
这个主要用于单元测试,确保每个模块中的测试函数能正确的被创建和导出。(This is more frequently used when dealing when unit-testing, ensuring that a module
will only have its testing functions created and exported when they are explicitly wanted.)
默认情况下,如果不定义元组的第三个元素<var>Value</var>默认为‘true’。
</dd>
</dl>
<p>为了一些标志编译<code>useless</code>模块,我们可以像下面这样做:</p>
<pre class="brush:eshell">
7> compile:file(useless, [debug_info, export_all]).
{ok,useless}
8> c(useless, [debug_info, export_all]).
{ok,useless}
</pre>
<p>
你可以在模块内定义编译标志当作模块属性。
从表达式7和8可以获得相同的结果,我们可以将下面几行添加到模块中:</p>
<pre class="brush:erl">
-compile([debug_info, export_all]).
</pre>
<p>
然后只要编译,就能和你手工指定编译标志的结果一样。
现在,我们可以编写函数,编译和执行,是时候看我们能走多远了!
</p>
<div class="note">
<p><strong>注意:</strong>
另一个选项是将Erlang编译成本地代码。
本地代码<strong>不</strong>支持所有的平台,在所支持的平台上我们的程序会有一定的性能的提升(根据传闻,会有20%的性能提升)。
为了编译成本地代码,你需要使用<code>hipe</code>模块,并用下面的方式调用:
<code>hipe:c(Module,OptionsList).</code>,当然在shell中你使用<code>c(Module,[native]).</code>也可以得到相似的结果。
注意生成的.beam将会包含本地代码和字节码,并且本地代码是不可以跨平台的。</p>
</div>
<h3><a class="section" name="more-about-modules">更多关于模块的知识</a></h3>
<p>
在继续学习更多关于编写函数和有用代码的片段前,这里还有一些其它的信息将来可能会有用途,我将在这个讨论下。
</p>
<p>首先是关于模块的元信息。我在本章的开头就提过模块属性是描述模块自身的元信息。
当我们无法得到代码时,我们怎么找到这些元信息?
编译器会做的非常好:当编译一个模块,编译器会提取绝大部分模块属性(连同其它信息)一起被保存到<code>module_info/0</code>函数中。
你可以通过下面的方式看到<code>useless</code>模块的元信息:
</p>
<pre class="brush:eshell">
9> useless:module_info().
[{exports,[{add,2},
{hello,0},
{greet_and_add_two,1},
{module_info,0},
{module_info,1}]},
{imports,[]},
{attributes,[{vsn,[174839656007867314473085021121413256129]}]},
{compile,[{options,[]},
{version,"4.6.2"},
{time,{2009,9,9,22,15,50}},
{source,"/home/ferd/learn-you-some-erlang/useless.erl"}]}]
10> useless:module_info(attributes).
[{vsn,[174839656007867314473085021121413256129]}]
</pre>
<p>
上面的这个段代码告诉了我们一个额外的函数<code>module_info/1</code> ,该函数可以让你获取特殊的信息。
你可以看到导出函数,导入函数(上面的例子没有!),属性(你自定义的元信息也在这里)还有编译选项和其它信息。
当你决定在你的模块中添加<code>-author("An Erlang Champ").</code>,它将在和<code>vsn</code>同一个节显示。
在生产环境中,模块属性的用途非常有限,但是你这有一些小技巧帮助自己:
我为本书编写了一些注释函数在
<a class="source" href="static/erlang/tester.erl" title="Automatic testing module. See warnings/0.">测试脚本</a>中使用,
这些脚本会让单元测试更好一些;这个脚本检查模块属性,找出注释函数并显示它们的相关警告。</p>
<div class="note">
<p><strong>注意:</strong>
<code>vsn</code>是一个不包含注释且自动为不同的代码版本生成唯一码。
它被一些发布用具用于代码热替换(在不停止应用的情况下升级)。
如果你想,你也可以自己定义<code>vsn</code>:只要在模块中添加<code>-vsn(VersionNumber)</code>。
</p>
</div>
<img class="right explanation" src="static/img/circular-dependencies.png" width="237" height="255" alt="A small graph with three nodes: Mom, Dad and You. Mom and Dad are parents of You, and You is brother of Dad. Text under: 'If circular dependencies are digusting in real life, maybe they should be disgusting in your programs too'" title="Seems clear enough."/>
<p>另一点是关于通用的模块设计,能做到这样是最好的:避免循环依赖!
一个模块<var>A</var>应当避免调用一个调用模块<var>A</var>的模块<var>B</var>。
这种依赖通常最终使代码维护困难。实际上,即便不是循环依赖,依赖太多模块也是让维护变困难。
你所能想到的最后一件事情是,当你半夜醒来发现一个疯狂的软件工程师或计算机科学家想把你眼睛挖出来,就因为你写的差劲的代码。
(The last thing you want is to wake up in the middle of the night
only to find a maniac software engineer or computer scientist trying to gouge your eyes out
because of terrible code you have written.)
</p>
<p>同样的原因(维护者和你恐惧的眼神),想相同功能的函数放在一起,被认为是非常好的实践。
启动和停止应用程序或在数据库内创建和删除记录就是这样例子。
(For similar reasons (maintenance and fear for your eyes), it is usually considered a good practice to regroup functions that have similar roles close together.
Starting and stopping an application or creating and deleting a record in some database are examples of such a scenario.)
</p>
<p>好了, 我们已经听了足够多的理论。让我们更多探索一点Erlang,好吗?</p>
<ul class="navigation">
<li><a href="starting-out-for-real.html" title="Previous chapter">< Previous</a></li>
<li><a href="contents.html" title="Index">Index</a></li>
<li><a href="syntax-in-functions.html" title="Next chapter">Next ></a></li>
</ul>
</div><!-- content -->
<div id="footer">
<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License Details"><img src="static/img/cc.png" width="88" height="31" alt="Creative Commons Attribution Non-Commercial No Derivative License" /></a>
<p>Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License</p>
</div> <!-- footer -->
</div> <!-- wrapper -->
<div id="grass" />
<script type="text/javascript" src="static/js/shCore.js"></script>
<script type="text/javascript" src="static/js/shBrushErlang2.js%3F11"></script>
<script type="text/javascript">
SyntaxHighlighter.defaults.gutter = false;
SyntaxHighlighter.all();
</script>
</body>
</html>