查看“︁Haskell/更进一步”︁的源代码
←
Haskell/更进一步
跳转到导航
跳转到搜索
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
{{Haskell微型目录|chapter=Haskell基础}} == Haskell文件 == 到现在,我们已经多次使用了GHC解释器。它的确是一个有用的调试工具。但是接下来的课程如果我们直接输入所有表达式是麻烦的。所以现在我们开始写第一个Haskell源文件。 <!--Up to now, we've made heavy use of the GHC interpreter. The interpreter is indeed a useful tool for trying things out quickly and for debugging your code. But we're getting to the point where typing everything directly into the interpreter isn't very practical. So now, we'll be writing our first Haskell source files.--> 在你最喜爱的编辑器中打开一个文件<tt>Varfun.hs</tt>(hs是Haskell的简写)然后把下面的定义粘贴进去。Haskell使用缩进和空格来决定函数(及其它表达式)的开始和结束,所以确保没有前置的空格并且缩进是正确的,否则GHC将报错。 <!--Open up a file <tt>Varfun.hs</tt> in your favourite text editor (the hs stands for Haskell) and paste the following definition in. Haskell uses indentations and spaces to decide where functions (and other things) begin and end, so make sure there are no leading spaces and that indentations are correct, otherwise GHC will report parse errors.--> area r = pi * r^2 (为避免你的疑虑,<code>pi</code>事实上已经被Haskell定义,不需要在这里包含它了)。现在转到你保存文件的目录,打开ghci,然后使用 :load(或者简写为 :l): <!--(In case you're wondering, <code>pi</code> is actually predefined in Haskell, no need to include it here). Now change into the directory where you saved your file, open up ghci, and use :load (or :l for short):--> Prelude> :load Varfun.hs Compiling Main ( Varfun.hs, interpreted ) Ok, modules loaded: Main. *Main> 如果ghci给出一个错误,"Could not find module 'Varfun.hs'"(“找不到模块'Varfun.hs'”),那么使用:cd来改变当前目录到包含Varfun.hs的目录: <!--If ghci gives an error, "Could not find module 'Varfun.hs'", then use :cd to change the current directory to the directory containing Varfun.hs:--> Prelude> :cd c:\myDirectory Prelude> :load Varfun.hs Compiling Main ( Varfun.hs, interpreted ) Ok, modules loaded: Main. *Main> 现在你可以执行文件中的函数: <!--Now you can execute the bindings found in that file:--> *Main> area 5 78.53981633974483 如果你修改了文件,只需使用:reload(简写为 :r)来重新载入文件。 <!--If you make changes to the file, just use :reload (:r for short) to reload the file.--> {{正文注解|GHC也可以用作编译器。就是说你可以用GHC来把你的Haskell文件转换为一个独立的可执行程序。详情见其文档。<!--GHC can also be used as a compiler. That is, you could use GHC to convert your Haskell files into a program that can then be run without running the interpreter. See the documentation for details.-->}} 你将注意到直接在ghci输入语句与从文件载入有一些不同。这些不同现在看来可能非常随意,但它们事实上是十分明智的范围的结果,其余的,我们保证将晚点解释。 <!--You'll note that there are a couple of differences between how we do things when we type them directly into ghci, and how we do them when we load them from files. The differences may seem awfully arbitrary for now, but they're actually quite sensible consequences of the scope, which, rest assured, we will explain later.--> === 没有let<!--No let--> === 初学者要注意,源文件里不要像这样写 <!--For starters, you no longer say something like--> let x = 3 let y = 2 let area r = pi * r ^ 2 而是要像这样写 <!--Instead, you say things like--> x = 3 y = 2 area r = pi * r ^ 2 关键字<code>let</code>事实上在Haskell中用的很多,但这里不强求。在这一章讨论'''<code>let</code>绑定'''的用法后,我们将看得更远。 <!--The keyword <code>let</code> is actually something you use a lot in Haskell, but not exactly in this context. We'll see further on in this chapter when we discuss the use of '''<code>let</code> bindings'''.--> === 不能定义同一个量两次<!--You can't define the same thing twice--> === 先前,解释器高兴地允许我们像这样写 <!--Previously, the interpreter cheerfully allowed us to write something like this--> Prelude> let r = 5 Prelude> r 5 Prelude> let r = 2 Prelude> r 2 另一方面,在源文件里像这样写不对 <!--On the other hand, writing something like this in a source file does not work--> --这不对<!--this does not work--> r = 5 r = 2 像我们先前提及的那样,变量不能改变,并且当你写一个源文件时甚至更关键。这里有一个漂亮的暗示。它意味着: <!--As we mentioned above, variables do not change, and this is even more the case when you're working in a source file. This has one very nice implication. It means that:--> === 顺序不重要<!--Order does not matter--> === 你声明变量的顺序不重要。例如,下面的代码片段可以得到完全同样的结果: <!--The order in which you declare things does not matter. For example, the following fragments of code do exactly the same thing:--> {|border="1" |- ||<pre> y = x * 2 x = 3</pre> ||<pre> x = 3 y = x * 2 </pre> |} 这是Haskell和其它函数式编程语言的一个独特的特性。变量不可改变的事实意味着我们随意选择以任何顺序写代码(但是这也是我们不能声明一个量一次以上的原因--否则那将是模棱两可的的)。 <!--This is a unique feature of Haskell and other functional programming languages. The fact that variables never change means that we can opt to write things in any order that we want (but this is also why you can't declare something more than once... it would be ambiguous otherwise).--> {{练习|把你在[[../变量和函数|上一章]]写的作业保存在一个Haskell文件中。在GHCi中载入这个文件并用一些参数测试里面的函数}} <!--{{练习|Save the functions you had written in the [[../Variables and functions|previous module]]'s exercises into a Haskell file. Load the file in GHCi and test the functions on a few parameters}}--> == 关于函数的更多特性<!--More about functions--> == 当我们开始使用源文件工作而不只是在解释器中敲一些代码时,定义多个子函数会让工作变得简单。 让我们开始见识Haskell函数的威力吧。 <!--Working with actual source code files instead of typing things into the interpreter makes things convenient to define much more substantial functions than those we've seen up to now. Let's flex some Haskell muscle here and examine the kinds of things we can do with our functions.--> === 条件表达式<!--Conditional expressions--> === ==== if / then / else ==== Haskell支持标准的条件表达式。我们可以定义一个参数小于<math>0</math>时返回<math>-1</math>、参数等于<math>0</math>时返回<math>0</math>、参数大于<math>0</math>时返回<math>1</math>的函数。事实上,这个函数已经存在(被称为符号(signum)函数),但是让我们定义一个我们自己的,把它叫做<code>mySignum</code>。 <!--Haskell supports standard conditional expressions. For instance, we could define a function that returns <math>-1</math> if its argument is less than <math>0</math>; <math>0</math> if its argument ''is'' <math>0</math>; and <math>1</math> if its argument is greater than <math>0</math> (this is called the signum function). Actually, such a function already exists, but let's define one of our own, what we'll call <code>mySignum</code>.--> <pre> mySignum x = if x < 0 then -1 else if x > 0 then 1 else 0 </pre> 我们可以这样像这样测试它: <!--You can experiment with this as:--> {{Haskell例子|1=|2=<pre> *Main> mySignum 5 1 *Main> mySignum 0 0 *Main> mySignum (5-10) -1 *Main> mySignum (-1) -1 </pre>}} 注意最后一个测试“-1”两边的括弧是必需的;如果没有,系统将认为你正试图将值“mySignum”减去“1”,而这是错误的。 <!--Note that the parenthesis around "-1" in the last example are required; if missing, the system will think you are trying to subtract the value "1" from the value "mySignum," which is ill-typed.--> Haskell中的结构与大多数其它编程语言非常相似;无论如何,你必须同时有一个<tt>'''then'''</tt>''和''一个<tt>'''else'''</tt>子句。在执行条件语句<math>x < 0</math>后如果它的值为<code>True</code>,接着会执行<tt>'''then'''</tt>部分;在执行条件语句<math>x < 0</math>后如果它的值为<code>False</code>,接着会执行<tt>'''else'''</tt>部分。 <!--The if/then/else construct in Haskell is very similar to that of most other programming languages; however, you must have both a <tt>'''then'''</tt> ''and'' an <tt>'''else'''</tt> clause. It evaluates the condition (in this case <math>x < 0</math> and, if this evaluates to <code>True</code>, it evaluates the <tt>'''then'''</tt> condition; if the condition evaluated to <code>False</code>, it evaluates the <tt>'''else'''</tt> condition).--> 你可以把程序修改到文件里然后重新装载到GHCI中,除了可以用命令<tt>:l Varfun.hs</tt> 重新装载外,你还可以通过更快捷更简单的方法<tt>:reload</tt> 或 <tt>:r</tt> 把当前已经载入的文件重新载入。 <!--You can test this program by editing the file and loading it back into your interpreter. Instead of typing <tt>:l Varfun.hs</tt> again, you can simply type <tt>:reload</tt> or just <tt>:r</tt> to reload the current file. This is usually much faster.--> ==== case ==== Haskell跟很多语言一样提供了 <tt>'''case'''</tt> 结构用于组合多个条件语句。(<tt>'''case'''</tt>其实有更强大的功能 -- 详情可以参考 [[../模式匹配/]]). <!--Haskell, like many other languages, also supports <tt>'''case'''</tt> constructions. These are used when there are multiple values that you want to check against (case expressions are actually quite a bit more powerful than this -- see the [[../Pattern matching/]] chapter for all of the details).--> <!--Suppose we wanted to define a function that had a value of <math>1</math> if its argument were <math>0</math>; a value of <math>5</math> if its argument were <math>1</math>; a value of <math>2</math> if its argument were <math>2</math>; and a value of <math>-1</math> in all other instances. Writing this function using <tt>'''if'''</tt> statements would be long and very unreadable; so we write it using a <tt>'''case'''</tt> statement as follows (we call this function <code>f</code>):--> 假设我们要定义一个这样的函数,如果它的参数为<math>0</math>它会输出<math>1</math>;如果它的参数为<math>1</math>它会输出<math>5</math>;如果它的参数为<math>2</math>它会输出<math>2</math>;如果它的参数为其它,它会输出<math>-1</math>。如果使用<tt>'''if'''</tt> 语句我们会得到一个可读性很差的函数。但我们可以用<tt>'''case'''</tt>语句写出如下形式可读性更强的函数: <pre> f x = case x of 0 -> 1 1 -> 5 2 -> 2 _ -> (-1) </pre> <!--In this program, we're defining <code>f</code> to take an argument <code>x</code> and then inspecting the value of <code>x</code>. If it matches <math>0</math>, the value of <code>f</code> is <math>1</math>. If it matches <math>1</math>, the value of <code>f</code> is <math>5</math>. If it matches <math>2</math>, then the value of <code>f</code> is <math>2</math>; and if it hasn't matched anything by that point, the value of <code>f</code> is <math>-1</math> (the underscore can be thought of as a "wildcard" -- it will match anything).--> 在这个程序中,我们定义了函数<code>f</code>它有一个参数<code>x</code>,它检查<code>x</code>的值。如果它的值符合条件<math>0</math>那么<code>f</code>的值为<math>1</math>,如果它的值符合条件<math>1</math>那么<code>f</code>的值为<math>5</math>,如此类推。如果<code>x</code>不符合之前任何列出的值那么<code>f</code>的值为<math>-1</math>(<code>"_"</code>为“通配符”表示任何值都可以符合这个条件) <!--The indentation here is important. Haskell uses a system called "layout" to structure its code (the programming language Python uses a similar system). The layout system allows you to write code without the explicit semicolons and braces that other languages like C and Java require.--> 注意在这里缩进是非常重要的。Haskell 使用一个叫“layout"的布局系统对程序的代码进行维护(Python 语言也使用一个相似的系统)。这个布局系统允许我们可以不需要像C,Java语言那样加分号跟花括号来对代码段进行分割。 <!--{{警告|1= Because whitespace matters in Haskell, you need to be careful about whether you are using tabs or spaces. If you can configure your editor to never use tabs, that's probably better. If not, make sure your tabs are always 8 spaces long, or you're likely to run in to problems. }}--> {{警告|1= 因为空白在Haskell的代码中是如此重要,无论你是使用tabs还是spaces都必须十分注意。如果你可以把你的编辑器配置成不使用tabs,可能会更好。如果不能,在你遇到不能解析的错误时,请确认你编辑其中的tabs总是相等于8个spaces的长度。 }} === 缩进<!--Indentation--> === <!--The general rule for layout is that an open-brace is inserted after the keywords <tt>'''where'''</tt>, <tt>'''let'''</tt>, <tt>'''do'''</tt> and <tt>'''of'''</tt>, and the column position at which the next command appears is remembered. From then on, a semicolon is inserted before every new line that is indented the same amount. If a following line is indented less, a close-brace is inserted. This may sound complicated, but if you follow the general rule of indenting after each of those keywords, you'll never have to remember it (see the [[../Indentation/]] chapter for a more complete discussion of layout).--> 更多了解请到 [[../缩进/]]。 <!--Some people prefer not to use layout and write the braces and semicolons explicitly. This is perfectly acceptable. In this style, the above function might look like:--> 有人不喜欢使用使用缩进的布局方法,而使用分号跟花括号的方法。如果使用这种方法,以上的程序可以写成以下的形式: <pre> f x = case x of { 0 -> 1 ; 1 -> 5 ; 2 -> 2 ; _ -> -1 } </pre> <!--Of course, if you write the braces and semicolons explicitly, you're free to structure the code as you wish. The following is also equally valid:--> 当然, 如果你使用分号跟花括号的布局方法, 你可以更随意地编写你的代码。以下的方式是完全可以的。 <pre> f x = case x of { 0 -> 1 ; 1 -> 5 ; 2 -> 2 ; _ -> -1 } </pre> <!--However, structuring your code like this only serves to make it unreadable (in this case).--> 但是用这种方法有时候代码可读性是很差的。(譬如在以上情况下) === 为不同参数定义一个函数<!--Defining one function for different parameters--> === <!--Functions can also be defined piece-wise, meaning that you can write one version of your function for certain parameters and then another version for other parameters. For instance, the above function <code>f</code> could also be written as:--> 函数也可以通过分段定义的方法进行定义,也就是说你可以为不同个参数定义同一个函数的不同版本。例如,以上的函数<code>f</code>可以写成一下方式 <pre> f 0 = 1 f 1 = 5 f 2 = 2 f _ = -1 </pre> <!--Just like in the <code>case</code> case above, order is important. If we had put the last line first, it would have matched every argument, and <code>f</code> would return <code>-1</code>, regardless of its argument (most compilers will warn you about this, though, saying something about overlapping patterns). If we had not included this last line, <code>f</code> would produce an error if anything other than 0, 1 or 2 were applied to it (most compilers will warn you about this, too, saying something about incomplete patterns). This style of piece-wise definition is very popular and will be used quite frequently throughout this tutorial. These two definitions of <code>f</code> are actually equivalent -- this piece-wise version is translated into the case expression.--> 就跟以上的<code>case</code>语句一样,函数的执行是与定义的顺序有关的。如果我们把最后的一行移到最前面,那么无论参数是什么,函数<code>f</code>的值都会是<code>-1</code>,无论是0 ,1 ,2 (大部分编译器会对这种参数定义重合发出警告)。如果我们不使用<code>f _ = -1</code>,当函数遇到 0 ,1 ,2 以外的参数时会抛出一个错误(大部分编译器也会会发出警告)。这种函数分段定义的方法是非常常用的,而且会经常在这个教程里面使用。以上的方法跟<code>case</code>语句实质上是等价的 —— 函数分段定义将被翻译成<code>case</code>语句 === 函数合成<!--Function composition--> === <!--More complicated functions can be built from simpler functions using ''function composition''. Function composition is simply taking the result of the application of one function and using that as an argument for another. We've already seen this back in the [[../Getting set up/]] chapter, when we wrote <code>5*4+3</code>. In this, we were evaluating <math>5*4</math> and then applying <math>+3</math> to the result. Wtioe can do the same thing with our <code>square</code> and <code>f</code> funcns:--> 复杂的函数可以通过简单的函数相互合成进行构建。函数合成就是把一个函数的结果作为另一个函数的参数。其实我们曾经在[[../起步/]]那一章见过,<code>5*4+3</code>就是两个函数合成。在这个例子中<math>5*4</math>先执行,然后执行的结果作为 <math>+3</math> 参数,最后得出结果。我们也可以把这种方法应用到<code>square</code> 与 <code>f</code>: <pre> square x = x^2 </pre> {{Haskell例子|1=|2=<pre> *Main> square (f 1) 25 *Main> square (f 2) 4 *Main> f (square 1) 5 *Main> f (square 2) -1 </pre>}} <!--The result of each of these function applications is fairly straightforward. The parentheses around the inner function are necessary; otherwise, in the first line, the interpreter would think that you were trying to get the value of ''<code>square f</code>'', which has no meaning. Function application like this is fairly standard in most programming languages. There is another, more mathematical way to express function composition: the (<code>.</code>) enclosed period function. This (<code>.</code>) function is modeled after the (<math>\circ</math>) operator in mathematics.--> 每一个函数的结果都是显而易见的。在例子的第一句中括号是非常必要的;不然,解释器认为你要尝试得到''<code>square f</code>''的值,但这显然没有意义。函数的这种合成方式普遍存在于其它编程语言中。在Haskell中有另外一种更数学化的表达方法:(<code>.</code>) 点函数。点函数源于数学中的(<math>\circ</math>)符号。 <!--{{正文注解|1= In mathematics we write <math>f \circ g</math> to mean "f following g." In Haskell , we write <code>f . g</code> also to mean "f following g." The meaning of <math>f \circ g</math> is simply that <math>(f \circ g)(x) = f(g(x))</math>. That is, applying the function <math>f \circ g</math> to the value <math>x</math> is the same as applying <math>g</math> to <math>x</math>, taking the result, and then applying <math>f</math> to that. }}--> {{正文注解|1= 在数学里我们用表达式 <math>f \circ g</math> 表达 "f following g." 在Haskell , 代码<code>f . g</code> 也表达为 "f following g." 其中<math>f \circ g</math> 等同于 <math>(f \circ g)(x) = f(g(x))</math>。 }} <!--The (<code>.</code>) function (called the function composition function), takes two functions and makes them into one. For instance, if we write <code>(square . f)</code>, this means that it creates a new function that takes an argument, applies <code>f</code> to that argument and then applies <code>square</code> to the result. Conversely, <code>(f . square)</code> means that a new function is created that takes an argument, applies <code>square</code> to that argument and then applies <code>f</code> to the result. We can see this by testing it as before:--> (<code>.</code>)函数(函数的合成函数),将两个函数合称为一个函数。例如,<code>(square . f)</code>表示一个有一个参数的函数,先把这个参数代入函数<code>f</code>,然后再把这个结果代入函数<code>square</code>得到最后结果。相反地,<code>(f . square)</code>表示一个有一个参数的函数,先把这个参数代入函数<code>square</code>,然后再把这个结果代入函数<code>f</code>得到最后结果。我们可以通过一下例子中的方法进行测试: {{Haskell例子|1=|2=<pre> *Main> (square . f) 1 25 *Main> (square . f) 2 4 *Main> (f . square) 1 5 *Main> (f . square) 2 -1 </pre>}} <!--Here, we must enclose the function composition in parentheses; otherwise, the Haskell compiler will think we're trying to compose <code>square</code> with the value <code>f 1</code> in the first line, which makes no sense since <code>f 1</code> isn't even a function.--> 在这里,我们必须用括号把函数合成语句括起来;不然,如<code>(square . f) 1</code>Haskell的编译器会认为我们尝试把<code>square</code>与<code>f 1</code>的结果合成起来,但是这是没有意义的因为<code>f 1</code>甚至不是一个函数。 <!--It would probably be wise to take a little time-out to look at some of the functions that are defined in the Prelude. Undoubtedly, at some point, you will accidentally rewrite some already-existing function (I've done it more times than I can count), but if we can keep this to a minimum, that would save a lot of time.--> 现在我们可以稍稍停下来看看在Prelude中已经定义的函数,这是十分明智的。因为很有可能把已经在Prelude中定义了的函数,自己却不经意再定义一遍(我已经不记得我这样做了多少遍了),这样浪费掉了很多时间。 === let绑定<!--Let Bindings--> === 我们经常在函数中声明局部变量。 如果你记得中学数学课的内容,这里有一个例子, 一元二次方程<math>ax^2+bx+c=0</math>可以用下列等式求解 <math>x = \frac {-b \pm \sqrt{b^2-4ac}} {2a}</math> 我们将它转换为函数来得到<math>x</math>:的两个值 <pre> roots a b c = ((-b + sqrt(b*b - 4*a*c)) / (2*a), (-b - sqrt(b*b - 4*a*c)) / (2*a)) </pre> 请注意我们的定义中有一些冗余,不如数学定义优美。在函数的定义中我们重复了<code>sqrt(b*b - 4*a*c)</code>。 用Haskell的局部绑定(local binding)可以解决这个问题。也就是说我们可以在函数中定义只能在本函数中使用的值。 我们来创建<code>sqrt(b*b-4*a*c)</code>的局部绑定<code>disc</code>,并在函数中替换<code>sqrt(b*b - 4*a*c)</code> 我们使用了<tt>'''let'''</tt>/<tt>'''in'''</tt>来声明<code>disc</code>: <pre> roots a b c = let disc = sqrt (b*b - 4*a*c) in ((-b + disc) / (2*a), (-b - disc) / (2*a)) </pre> 在let语句中可以同时声明多个值,只需注意让它们有相同的缩进就可以: <pre> roots a b c = let disc = sqrt (b*b - 4*a*c) twice_a = 2*a in ((-b + disc) / twice_a, (-b - disc) / twice_a) </pre> {{Haskell导航|chapter=Haskell基础}} {{自动分类}}
该页面使用的模板:
Template:Haskell例子
(
查看源代码
)
Template:Haskell导航
(
查看源代码
)
Template:Haskell微型目录
(
查看源代码
)
Template:正文注解
(
查看源代码
)
Template:练习
(
查看源代码
)
Template:自动分类
(
查看源代码
)
Template:警告
(
查看源代码
)
返回
Haskell/更进一步
。
导航菜单
个人工具
登录
命名空间
页面
讨论
不转换
查看
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
工具
链入页面
相关更改
页面信息