查看“︁Haskell/变量和函数”︁的源代码
←
Haskell/变量和函数
跳转到导航
跳转到搜索
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
''(本章的所有例子都可以写进Haskell源代码文件并利用GHC或Hugs加载进行求值运算。请注意不要输入开头的提示符,如果代码前面有提示符,可以在GHCi等环境中输入;如果没有,则写进文件再运行。)'' {{Haskell迷你目录|chapter=Haskell基础}} ==变量== 我们已经了解如何把GHCi当作计算器来使用,当然只是极其简短的例子。对于更长的计算以及Haskell程序而言,需要对運算的中間结果进行追踪。 中間结果可以存入''变量'',通过名字来访问。一个变量包含一个''值'',當使用變數時,變數內的值會被代入。举个例子,请考虑以下情况 ghci> 3.1416 * 5^2 78.53999999999999 在前一章节中,我们知道了如何做像加法和减法这样的算术运算。提问:半径是5厘米的圆的面积是多少?别担心,你并不是错看了几何课本。这个圆的面积是<math>\pi r^2</math>,其中<math>r</math>是半径(5厘米),<math>\pi</math>简单地说就是<math>3.14</math>。那么,让我们在GHCi中试一下吧: <!--Previously, we saw how to do simple arithmetic operations like addition and subtraction. Pop quiz: what is the area of a circle whose radius is 5 cm? No, don't worry, you haven't stumbled through the [[Geometry]] wikibook by mistake. The area of our circle is <math>\pi r^2</math> where <math>r</math> is our radius (5cm) and <math>\pi</math>, for the sake of simplicity, is <math>3.14</math>. So let's try this out in GHCi:--> ___ ___ _ / _ \ /\ /\/ __(_) / /_\// /_/ / / | | GHC Interactive, version 6.4.1, for Haskell 98. / /_\\/ __ / /___| | http://www.haskell.org/ghc/ \____/\/ /_/\____/|_| Type :? for help. Loading package base-1.0 ... linking ... done. Prelude> 我们要把pi(3.14)乘以半径的平方,那就这样 <!--So let's see, we want to multiply pi (3.14) times our a radius squared, so that would be--> Prelude> 3.14 * 5^2 78.5 太棒了!我们让这台奇妙而又伟大的计算机帮我们计算出了我们想要的结果。但我们不想pi仅仅保留2位小数。那么就让我们再试一次,这次pi变得有点长。 <!--Great! Well, now since we have these wonderful, powerful computers to help us calculate things, there really isn't any need to round pi down to 2 decimal places. Let's do the same thing again, but with a slightly longer value for pi--> Prelude> 3.14159265358979323846264338327950 * (5 ^ 2) 78.53981633974483 很好,这次我们来计算一下圆的周长(提示:<math>2 \pi r</math>)。<!--Much better, so now how about giving me the circumference of that circle (hint: <math>2 \pi r</math>)? --> Prelude> 2 * 3.14159265358979323846264338327950 * 5 31.41592653589793 那么半径是25的圆的面积呢? <!--Or how about the area of a different circle with radius 25 (hint: <math>\pi r^2</math>)?--> Prelude> 3.14159265358979323846264338327950 * (25 ^ 2) 1963.4954084936207 我们迟早会厌倦把上述代码一遍又一遍的输入(或拷贝粘贴)的。如我们所言,编程的目的就是摆脱类似于把pi的前20位输入一亿遍这样愚蠢、乏味的重复劳动。我们要做的就是让解释器记住pi的值: <!--What we're hoping here is that sooner or later, you are starting to get sick of typing (or copy-and-pasting) all this text into your interpreter (some of you might even have noticed the up-arrow and Emacs-style key bindings to zip around the command line). Well, the whole point of programming, we would argue, is to avoid doing stupid, boring, repetitious work like typing the first 20 digits of pi in a million times. What we really need is a means of remembering the value of pi: But doesn't GHCi already define pi? --> Prelude> let pi = 3.14159265358979323846264338327950 {{正文注解|如果这条命令没用的话,你可能使用的是hugs而不是GHCi,hugs的语法有一点不同。<!--If this command does not work, you are probably using hugs instead of GHCi, which expects a slightly different syntax.-->}} 这里你就告诉了Haskell:“让pi等于3.14159……”。这里引入了一个新的'''变量'''<code>pi</code>,它被定义为一个数字3.14159265358979323846264338327950。这样就变得非常方便了,我们只要再次输入pi就可以了: <!--Here you are literally telling Haskell to: "let pi be equal to 3.14159...". This introduces the new '''variable''' <code>pi</code>, which is now defined as being the number 3.14159265358979323846264338327950. This will be very handy because it means that we can call that value back up by just typing pi again:--> Prelude> pi 3.141592653589793 别在意那些丢失的小数位,他们仅仅是不显示罢了。所有这些小数位都会在将来的计算中被使用到的。 <!--Don't worry about all those missing digits; they're just skipped when displaying the value. All the digits will be used in any future calculations.--> 有了变量,事情就变得轻松了。半径是5厘米的圆的面积是多少呢?半径是25厘米的呢? <!--Having variables takes some of the tedium out of things. What is the area of a circle having a radius of 5 cm? How about a radius of 25cm?--> Prelude> pi * 5^2 78.53981633974483 Prelude> pi * 25^2 1963.4954084936207 {{正文注解|我们这里所说的“变量”("variables"),在其它函数式编程的书籍中经常被叫做是“符号”("symbols")。那是因为在其它的语言中,也就是命令式语言中,变量被赋予了完全不同的使用方式:跟踪状态(keeping track of state)。在Haskell中的变量不是这样的:变量保存了一个值,然后再也不可以修改它了。}} == 类型 == 按照上面的示例,你可能会想把半径保存在一个变量里。让我们看看会发生什么吧! <!--Following the previous example, you might be tempted to try storing a value for that radius. Let's see what happens:--> <pre> Prelude> let r = 25 Prelude> 2 * pi * r <interactive>:1:9: Couldn't match `Double' against `Integer' Expected type: Double Inferred type: Integer In the second argument of `(*)', namely `r' In the definition of `it': it = (2 * pi) * r </pre> 怎么回事!这里,你遇到了程序设计上的一个概念:'''类型'''(types)。类型是许多程序设计语言都有的一个特性,它被设计用来在你自己发现之前,尽早的捕获程序设计上的错误。我们将在之后的[[../类型基础|类型基础]]详细讨论型别,现在让我们把它想像成插头和插座。例如,计算机背后的不同功能的插头被设计成不同的形状和大小,这样你就不必担心插错插头了。类型也有着类似的目的,但是在这个特殊的例子里,类型好像不怎么有用啊。 <!--Whoops! You've just run into a programming concept known as '''types'''. Types are a feature of many programming languages which are designed to catch some of your programming errors early on so that you find out about them before it's too late. We'll discuss types in more detail later on in the [[../Type basics|Type basics chapter]], but for now it's useful to think in terms of plugs and connectors. For example, many of the plugs on the back of your computer are designed to have different shapes and sizes for a purpose. This is partly so that you don't inadvertently plug the wrong bits of your computer together and blow something up. Types serve a similar purpose, but in this particular example, well, types aren't so helpful.--> 这里的诡异之处在于,像<code>25</code>这样的数字会被解释成<code>Double</code>(双精度浮点型)或<code>Integer</code>(整型),或者可能是其它类型。由于缺少必要的信息,Haskell“猜测”它的类型是<code>Integer</code>(它不能和一个<code>Double</code>型别的数字相乘)。为了解决这个问题,我们只需简单的强调一下,把它作为<code>Double</code>就可以了。 <!--The tricky bit here is that numbers like <code>25</code> can either be interpreted as being <code>Double</code> or <code>Integer</code> (among other types)... but for lack of other information, Haskell has "guessed" that its type must be <code>Integer</code> (which cannot be multiplied with a <code>Double</code>). To work around this, we simply insist that it is to be treated as a <code>Double</code>--> <pre> Prelude> let r = 25 :: Double Prelude> 2 * pi * r 157.07963267948966 </pre> 请注意,Haskell的这里“猜测”只会发生在当它缺乏足够的信息来推断出型别的时候。下面我们会看到,在大多数情况下,Haskell是能够根据上下文的信息来作出推断的。也就是说,是否把一个数字作为<code>Integer</code>,或是其它型别。 <!--Note that Haskell only has this "guessing" behaviour in contexts where it does not have enough information to infer the type of something. As we will see below, most of the time, the surrounding context gives us all of the information that is needed to determine, say, if a number is to be treated as an <code>Integer</code> or not.--> {{正文注解|1= 事实上在这个难题背后有一点点微妙。它涉及一个叫作'''单一同态限定'''(monomorphism restriction)的语言特性。事实上现在你不需要知道这个,所以如果只想快一点你可以跳过这条提示。除了指定<code>Double</code>类型,你也可以给它一个'''多态'''(polymorphic)类型,像<code>Num a => a</code>,意思是"属于<code>Num</code>类的任意类型<code>a</code>"。示例代码如下,它们运行起来像先前的代码一样: <pre> Prelude> let r = 25 :: Num a => a Prelude> 2 * pi * r 157.07963267948966</pre> Haskell可以从''理论上''系统地指定这样的多态类型,而不是做缺省的存在潜在错误的猜测,比如整数。但在现实世界,这可能导致数值被不必要地复制或重复计算。要避免这个潜在的陷阱,Haskell的设计者赞同更谨慎的“单一同态限定”。这意味着如果可以从上下文推断出来,或者你明确指定了一个,那么数值只能有一个多态类型。否则编译器会强制选择一个默认的单一同态(也就是非多态)类型。无论如何,这个特性是有争议的。甚至可以使用GHC参数(-fno-monomorphism-restriction)来禁用它,但是这带来了一些低效率的风险。除此之外,多数情况下它就像明确指定类型一样容易。 }} <!-- {{body note|1= There is actually a little bit more subtlety behind this problem. It involves a language feature known as the '''monomorphism restriction'''. You don't actually need to know about this for now, so you can skip over this note if you just want to keep a brisk pace. Instead of specifying the type <code>Double</code>, you also have given it a '''polymorphic''' type, like <code>Num a => a</code>, which means "any type <code>a</code> which belongs in the class <code>Num</code>". The corresponding code looks like this and works just as seamlessly as before: <pre> Prelude> let r = 25 :: Num a => a Prelude> 2 * pi * r 157.07963267948966</pre> Haskell could <em>in theory</em> assign such polymorphic types systematically, instead of defaulting to some potentially incorrect guess, like Integer. But in the real world, this could lead to values being needlessly duplicated or recomputed. To avoid this potential trap, the designers of the Haskell language opted for a more prudent "monomorphism restriction". It means that values may only have a polymorphic type if it can be inferred from the context, or if you explicitly give it one. Otherwise, the compiler is forced to choose a default monomorphic (i.e. non-polymorphic) type. This feature is somewhat controversial. It can even be disabled with the GHC flag (-fno-monomorphism-restriction), but it comes with some risk for inefficiency. Besides, in most cases, it is just as easy to specify the type explicitly. }} --> == 变量中的变量 == 变量不仅可以保存像3.14这样的数值,还可以保存任何Haskell表达式。那么,为了方便的表达半径是5的圆的面积,我们可以写如下代码: <!--Variables can contain much more than just simple values such as 3.14. Indeed, they can contain any Haskell expression whatsoever. So, if we wanted to keep around, say the area of a circle with radius of 5, we could write something like this:--> Prelude> let area = pi * 5^2 多么有意思啊,我们在一个变量里面保存了一个复杂的Haskell程序块(一个包含变量的算术表达式)。 <!--What's interesting about this is that we've stored a complicated chunk of Haskell (an arithmetic expression containing a variable) into yet another variable. --> 我们可以在变量里面保存任意的Haskell代码。那么,让我们继续吧! <!--We can use variables to store any arbitrary Haskell code, so let's use this to get our acts together.--> Prelude> let r = 25.0 Prelude> let area2 = pi * r ^ 2 Prelude> area2 1963.4954084936207 到现在为止,一直都还不错。 Prelude> let r = 2.0 Prelude> area2 1963.4954084936207 {{边注|side=right|变量不可以被修改}} 等一下,怎么不对?为什么我们改了半径,面积还是原来的结果?原因就是''Haskell里的变量是不可以被修改的''<ref>依据读者以前的编程经验:变量不可以改变?我只能得到常量?震惊!恐怖!不……相信我们,我们希望在这本书的剩下部分给你说明,不改变一个单一的变量对你大有帮助!事实上,这些不能改变的变量可以使生活更加方便,因为它可以使程序变得更加可以预测。</ref>。在你第二次定义r的时候,实际上,操作的是''另一个''r。这种事情在现实生活中也经常碰到。名字叫John的人有多少呢?很多人名字都会是John。当你向朋友们提起“John”的时候,你朋友会根据当时的情况知道到底是哪一个John。在程序设计上也有类似于“当时的情况”这样的概念:'''[[Wikipedia:Scope (programming)|作用域]]'''。我们并不在这里解释作用域(至少现在不)。Haskell作用域的诡异之处在于可以定义两个不同的r,而总能取用正确的那个。遗憾的是,作用域并没有解决当前的问题。我们要的是定义一个通用的<code>area</code>函数,来告诉我们圆的面积。我们现在能做的,只能把它再定义一遍: <!--Wait a second, why didn't this work? That is, why is it that we get the same value for area as we did back when r was 25? The reason this is the case is that ''variables in Haskell do not change''<ref>For readers with prior programming experience: Variables don't change? I only get constants? Shock! Horror! No... trust us, as we hope to show you in the rest of this book, you can go a ''very'' long way without changing a single variable! In fact, this non-changing of variables makes life easier because it makes programs so much more predictable.</ref>. What actually happens when you defined r the second time is that you are talking about a ''different'' r. This is something that happens in real life as well. How many people do you know that have the name John? What's interesting about people named John is that most of the time, you can talk about "John" to your friends, and depending on the context, your friends will know which John your are referring to. Programming has something similar to context, called '''[[Wikipedia:Scope (programming)|scope]]'''. We won't explain scope (at least not now), but Haskell's lexical scope is the magic that lets us define two different r and always get the right one back. Scope, however, does not solve the current problem. What we want to do is define a generic <code>area</code> function that always gives you the area of a circle. What we could do is just define it a second time:--> Prelude> let area3 = pi * r ^ 2 Prelude> area3 12.566370614359172 我们是程序员,程序员是讨厌重复劳动的。有更好的解决方法吗? <!--But we are programmers, and programmers ''loathe'' repetition. Is there a better way?--> == 函数 == 我们为了完成一个通用的求面积功能,其实就是定义一个'''函数'''。就如同定义一个变量那样,在Haskell中定义一个函数十分的简单。不同之处在于等号的左边有一些额外的东西。例如,下面我们定义一个pi,接着就是面积函数area: <!--What we are really trying to accomplish with our generic <code>area</code> is to define a '''function'''. Defining functions in Haskell is dead-simple. It is exactly like defining a variable, except with a little extra stuff on the left hand side. For instance, below is our definition of pi, followed by our definition of area:--> Prelude> let pi = 3.14159265358979323846264338327950 Prelude> let area r = pi * r ^ 2 要计算两个圆的面积,只要把不同的半径传入就可以了: <!--To calculate the area of our two circles, we simply pass it a different value:--> Prelude> area 5 78.53981633974483 Prelude> area 25 1963.4954084936207 函数的出现使得我们对代码的重用方面有了极大的飞跃。先等一下,让我们来剖析一下上面事物的本质。注意到<code>area r = ...</code>中的<code>r</code>了吗?这就是所谓的'''参数'''。参数用来表示一个函数的输入。当Haskell在执行这个函数的时候,参数的值是来自于外部的。在<code>area</code>这个例子中,当你调用<code>area 5</code>的时候,<code>r</code>是<code>5</code>,而调用<code>area 25</code>的时候,<code>r</code>是<code>25</code>. <!--Functions allow us to make a great leap forward in the reusability of our code. But let's slow down for a moment, or rather, back up to dissect things. See the <code>r</code> in our definition <code>area r = ...</code>? This is what we call a '''parameter'''. A parameter is what we use to provide input to the function. When Haskell is interpreting the function, the value of its parameter must come from the outside. In the case of <code>area</code>, the value of <code>r</code> is <code>5</code> when you say <code>area 5</code>, but it is <code>25</code> if you say <code>area 25</code>.--> {{练习|1= 在Haskell中输入如下代码:(先不要在解释器中输入) <!--Say I type something in like this (don't type it in yet):--> Prelude> let r = 0 Prelude> let area r = pi * r ^ 2 Prelude> area 5 # 你认为会发生什么? # 实际发生了什么为什么(提示:记得以前我们提过的“作用域”吗?))}} <!-- # What do you think should happen? Are we in for an unpleasant surprise? # What actually happens? Why? (Hint: remember what was said before about "scope")}}--> ===作用域和参数=== :'''警告:此章节包含上面练习的一些信息''' <!--:'''Warning: this section contains spoilers to the previous exercise'''--> 我们希望你完成上面那个小小的练习(也可说是一个实验)。那么,下面的代码并不会让你出乎意料: <!--We hope you have completed the very short exercise (I would say thought experiment) above. Fortunately, the following fragment of code does not contain any unpleasant surprises:--> Prelude> let r = 0 Prelude> let area r = pi * r ^ 2 Prelude> area 5 78.53981633974483 你可能认为会得到一个0,但结果出乎你的预料。原因就上我们上面所说的,一个命名参数的值是你调用这个函数时传入的。那就是我们的老朋友,作用域。<code>let r = 0</code>中的<code>r</code>和我们定义的函数<code>area</code>中的<code>r</code>,并不是同一个<code>r</code>。在<code>area</code>中的<code>r</code>覆盖了其它的<code>r</code>。你可以认为Haskell选取了一个最近的一个<code>r</code>。如果你有很多朋友都叫John,那么和你在一起的那个John就是你在说话中提到的那个Jonh。类似的,r的值是什么取决于作用域。 <!--An unpleasant surprise here would have been getting the value 0. This is just a consequence of what we wrote above, namely the value of a parameter is strictly what you pass in when you call the function. And ''that'' is directly a consequence of our old friend scope. Informally, the <code>r</code> in <code>let r = 0</code> is not the same <code>r</code> as the one inside our defined function <code>area</code> - the <code>r</code> inside <code>area</code> overrides the other <code>r</code>; you can think of it as Haskell picking the most specific version of <code>r</code> there is. If you have many friends all named John, you go with the one which just makes more sense and is specific to the context; similarly, what value of r we get depends on the scope.--> === 多重参数<!--Multiple parameters--> === 关于函数你也许应该知道另一项知识。那就是,函数可以接受一个以上的参数。例如,你想要计算一个矩形的面积。这很容易表达: <!-- Another thing you might want to know about functions is that they can accept more than one parameter. Say for instance, you want to calculate the area of a rectangle. This is quite simple to express: --> Prelude> let areaRect l w = l * w Prelude> areaRect 5 10 50 又或者你想要计算一个直角三角形的面积<math>\left(A = \frac{bh}{2}\right)</math>: <!-- Or say you want to calculate the area of a right angle triangle <math>\left(A = \frac{bh}{2}\right)</math>: --> Prelude> let areaTriangle b h = (b * h) / 2 Prelude> areaTriangle 3 9 13.5 参数传入的方式很直接:只需要根据和定义时一样的顺序传入参数就可以了。因此,<code>areaTriangle 3 9</code>会给出底为3而高为9的三角形面积,而<code>areaTriangle 9 3</code>将给出底为9而高为3的三角形面积。 <!-- Passing parameters in is pretty straightforward: you just give them in the same order that they are defined. So, whereas <code>areaTriangle 3 9</code> gives us the area of a triangle with base 3 and height 9, <code>areaTriangle 9 3</code> gives us the area with the base 9 and height 3. --> {{练习|1=写一个计算立方体体积的函数。立方体有长、宽、高。把它们乘起来就可以了。<!--Write a function to calculate the volume of a box. A box has width, height and depth. You have to multiply them all to get the volume.-->}} == 函数中的函数 == 我们可以在其他函数的内部调用函数以进一步删减重复的数量。一个简单的例子将用于展示怎样利用这些创建一个计算正方形面积的函数。我们都知道正方形是矩形的一个特殊情况(面积仍然是宽乘以长);然而,我们知道它的宽和长是相同的,因此为什么我们需要键入两次呢? <!--To further cut down the amount of repetition it is possible to call functions from within other functions. A simple example showing how this can be used is to create a function to compute the area of a Square. We can think of a square as a special case of a rectangle (the area is still the width multiplied by the length); however, we also know that the width and length are the same, so why should we need to type it in twice?--> Prelude> let areaRect l w = l * w Prelude> let areaSquare s = areaRect s s Prelude> areaSquare 5 25 {{练习|1= 编写一个计算圆柱体体积的函数. 圆柱体的体积是圆形底的面积 (你已经在这章编写了这个函数, 所以重新利用它) 乘以高. <!--Write a function to calculate the volume of a cylinder. The volume of a cylinder is the area of the base, which is a circle (you already programmed this function in this chapter, so reuse it) multiplied by the height.--> }} == 总结 == # 变量保存着值。其实,他们可以保存任意的Haskell表达式。 # 变量不可以改变。 # 函数可以帮助你编写可重复利用的代码。 # 函数可以接受一个以上的参数。 <!--# Variables store values. In fact, they store any arbitrary Haskell expression. # Variables do not change. # Functions help you write reusable code. # Functions can accept more than one parameter.--> == 注释 == <references/> {{Haskell导航|chapter=Haskell基础}} {{自动分类}}
该页面使用的模板:
Template:Haskell导航
(
查看源代码
)
Template:Haskell迷你目录
(
查看源代码
)
Template:正文注解
(
查看源代码
)
Template:练习
(
查看源代码
)
Template:自动分类
(
查看源代码
)
Template:边注
(
查看源代码
)
返回
Haskell/变量和函数
。
导航菜单
个人工具
登录
命名空间
页面
讨论
不转换
查看
阅读
查看源代码
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
工具
链入页面
相关更改
页面信息