你想成为一个 函数编程者么(第一部分)

@sakila1012 2018-01-22 09:13:49发表于 sakila1012/blog

你想成为一个 函数编程者么(第一部分)

Taking that first step to understanding Functional Programming concepts is the most important and sometimes the most difficult step. But it doesn’t have to be. Not with the right perspective.
谈论的第一步是理解功能编程概念是最重要的,有时最困难的一步。但它不是必要的。没有正确的视野。

Learning to Drive

学习驾驶

When we first learned to drive, we struggled. It sure looked easy when we saw other people doing it. But it turned out to be harder than we thought.
当我们第一次学习驾驶,我们惊慌失措。当我们看到别的人驾驶时,似乎很容易。但是结果是比我们想象的难得多。

We practiced in our parent’s car and we really didn’t venture out on the highway until we had mastered the streets in our own neighborhood.
我们在父母的车上练习,真的不敢在高速公路上冒险直到我们在我们自己的临近的街道熟练了。

But through repeated practice and some panicky moments that our parents would like to forget, we learned to drive and we finally got our license.
通过重复的练习和我们会忘记的一些恐慌的时刻,我们学会了驾驶,最终我们拿到了驾照。

With our license in hand, we’d take the car out any chance we could. With each trip, we got better and better and our confidence went up. Then came the day when we had to drive someone else’s car or our car finally gave up the ghost and we had to buy a new one.
驾照在手,我们可以随意的驾车出去。每一次开车,我们的车技越来越好,自信心也不断上升。我们不得不驾驶别人车的那一天会来临,或者最终我们的将会报废,我们不得不买一辆新车。

What was it like that first time behind the wheel of a different car? Was it like the very first time behind the wheel? Not even close. The first time, it was all so foreign. We’ve been in a car before that, but only as a passenger. This time we were in the driver seat. The one with all the controls.
第一次站在**不同车的后面是什么感觉?它是像第一次**站在车轮后面?甚至还没有接近。第一次,忘记的一干二净。我们曾经坐在车内,但仅仅是一个乘客。这次我们在驾驶位上。我们控制着车。

But when we drove our second car, we simply asked ourselves a few simple questions like, where does the key go, where are the lights, how do you use the turn signals and how do you adjust the side mirrors.
但是当我们驾驶第二辆汽车时,我们仅仅询问了一些问题,比如车钥匙在哪,灯光在哪如何使用转向灯和如何调整后视镜。

After that, it was pretty smooth sailing. But why was this time so easy compared to the first time?
后面,就是相当顺利的驾驶。但是相对于第一次,这一次为什么这么容易呢?

That’s because the new car was pretty much like the old car. It had all the same basic things that a car needs and they were pretty much in the same place.
那是因为新车很像那辆旧车。一辆车所需要的基本要求是一样的,几乎都在同一个地方。

A few things were implemented differently and maybe it had a few additional features, but we didn’t use them the first time we drove or even the second. Eventually, we learned all the new features. At least the ones we cared about.
有些东西的实现是不同,也许它有一些额外的功能,但我们没有在第一次我们开车使用他们,甚至第二次。最终,我们学会所有新的功能。至少是我们关心的。

Well, learning programming languages is sort of like this. The first is the hardest. But once you have one under your belt, subsequent ones are easier.
好吧,学习编程语言跟学车有很多相似之处。开头难,但是一旦处在安全区域下,后面就很容易了。

When you first start a second language, you ask questions like, “How do I create a module? How do you search an array? What are the parameters of the substring function?”
当你第一次开始学习第二门语言时,你可能会问类似的问题,“如何创建一个模块?你如何搜索数组?substring 函数的参数是什么?”

You’re confident that you can learn to drive this new language because it reminds you of your old language with maybe a few new things to hopefully make your life easier.
你对你可以学习一门新的语言很自信,因为它让你想起你的旧语言,也许有一些新的东西,让你的生活更容易。

Your First Spaceship

你第一个宇宙飞船

Whether you’ve been driving one car your whole life or dozens of cars, imagine that you’re about to get behind the wheel of a spaceship.
不管你是一辈子开着一辆车还是几十辆车,想象一下你就要坐在宇宙飞船的后面了。

If you were going to fly a spaceship, you wouldn’t expect your driving ability on the road to help you much. You’d be starting over from square zero. (We are programmers after all. We count starting at zero.)
当你将要驾驶一架飞船,你不用期望你的驾驶能力在航行过程中帮助你很多。你将从零开始。(我们毕竟是程序员。需要从零计数。

You would begin your training with the expectation that things are very different in space and that flying this contraption is very different than driving on the ground.
你可能会带着期望开始训练,但是在太空中是非常困难的,而且驾驶这玩意儿比在地面行驶非常不同

Physics hasn’t changed. Just the way you navigate within that same Universe.
物理没有改变。就像你在同一宇宙中航行一样。

And it’s the same with learning Functional Programming. You should expect that things will be very different. And that much of what you know about programming will not translate.
而且这跟学习函数编程一样。你应该期望编程不一样。而且你知道的很多关于编程将**不会_**翻译.

Programming is thinking and Functional Programming will teach you to think very differently. So much so, that you’ll probably never go back to the old way of thinking.
编程是思考,函数式编程将教你如何与众不同地思考。所以,你可能永远不会回到旧的思维方式。

Forget Everything You Know

忘记你知道的一切

People love saying this phrase, but it’s sort of true. Learning functional programming is like starting from scratch. Not completely, but effectively. There are lots of similar concepts but it’s best if you just expect that you have to relearn everything.
人们喜欢说这个短语,但这是真的。学习函数式编程就像从头开始。不是完全的,而是有效的。有很多类似的概念,但是最好的是你必须重新开始学习

With the right perspective you’ll have the right expectations and with the right expectations you won’t quit when things get hard.
有了正确的观点,你就会有正确的期望,有了正确的期望,当事情变得困难时,你就不会放弃。

There are all kinds of things that you’re used to doing as a programmer that you cannot do any more with Functional Programming.
作为程序员,你已经习惯了用函数编程来做的事情。

Just like in your car, you used to backup to get out of the driveway. But in a spaceship, there is no reverse. Now you may think, “WHAT? NO REVERSE?! HOW THE HELL AM I SUPPOSED TO DRIVE WITHOUT REVERSE?!”
就像在你的车里一样,你曾经备份过离开车道。但是在飞船上,没有退路。现在你可能会想,“什么,没有退路?!我怎么不能开倒车呢?”

Well, it turns out that you don’t need reverse in a spaceship because of its ability to maneuver in three dimensional space. Once you understand this, you’ll never miss reverse again. In fact, someday, you’ll think back at how limiting the car really was.
事实上,你在宇宙飞船里不需要倒车,因为它在三维空间里有机动的能力。一旦你理解了这一点,你就再也不会错过逆转了。事实上,总有一天,你会想起汽车是如何被限制的。

Learning Functional Programming takes a while. So be patient.
学习函数式编程需要一段时间。所以耐心点。

So let’s exit the cold world of Imperative Programming and take a gentle dip into the hot springs of Functional Programming.
因此,让我们退出命令式编程的冷世界,并仔细考虑函数编程的温泉。

What follows in this multi-part article are Functional Programming Concepts that will help you before you dive into your first Functional Language. Or if you’ve already taken the plunge, this will help round out your understanding.
这篇多部分文章后面的内容是函数编程概念,这些概念在进入第一个函数式语言之前会对你有所帮助。或者,如果你已经采取了行动,这将有助于提高你的理解力。

Please don’t rush. Take your time reading from this point forward and take the time to understand the coding examples. You may even want to stop reading after each section to let the ideas sink in. Then return later to finish.
请不要急。从这一点开始阅读,并花时间去理解编码示例。你可能甚至想在每一节之后停止阅读,让思想下沉。然后返回完成。

The most important thing is that you understand.
最重要的事情是你**理解**。

Purity

纯粹

When Functional Programmers talk of Purity, they are referring to Pure Functions.
当函数编程人员谈到纯度时,它们指的是纯函数。

Pure Functions are very simple functions. They only operate on their input parameters.
纯函数是非常简单的函数。它们只对输入参数进行操作。

Here’s an example in Javascript of a Pure Function:
下面是纯函数JavaScript中的一个示例:

var z = 10;
function add(x, y) {
    return x + y;
}

Notice that the add function does NOT touch the z variable. It doesn’t read from z and it doesn’t write to z. It only reads x and y, its inputs, and returns the result of adding them together.
注意: add 函数没有触发变量 z。并不从变量 z 开始读代码,也不写变量 z。仅仅读它的输入 xy,然后一块返回他们求和的结果。

That’s a pure function. If the add function did access z, it would no longer be pure.
这是一个存函数。如果函数 add 确实访问了 z,该函数将不再是纯函数了。

Here’s another function to consider:
下面是另外一个函数:

function justTen() {
    return 10;
}

If the function, justTen, is pure, then it can only return a constant. Why?
如果函数 justTen 是一个纯函数,那么它仅仅返回一个常量。为什么?

Because we haven’t given it any inputs. And since, to be pure, it cannot access anything other than its own inputs, the only thing it can return is a constant.
因为我们没有给出任何输出。而且,作为纯函数,它不能访问其他只有自己的输入,返回的只有一个常量。

Since pure functions that take no parameters do no work, they aren’t very useful. It would be better if justTen was defined as a constant.

Most useful Pure Functions must take at least one parameter.
大多数有用的纯函数必须至少有一个参数。

Consider this function:
考虑这个函数:

function addNoReturn(x, y) {
    var z = x + y
}

Notice how this function doesn’t return anything. It adds x and y and puts it into a variable z but doesn’t return it.
注意,这个函数没有任何返回。它将 xy 赋给变量 z,但是没有返回 z

It’s a pure function since it only deals with its inputs. It does add, but since it doesn’t return the results, it’s useless.
这是一个纯函数,因为它只处理它的输入。它的确求和了,但是没有返回结果,这样是无效的。

All useful Pure Functions must return something.
所有有用的的纯函数必须又返回。

Let’s consider the first add function again:
让我们再次看看第一个函数 add

function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); _// prints 3_
console.log(add(1, 2)); _// still prints 3_
console.log(add(1, 2)); _// WILL ALWAYS print 3_

Notice that add(1, 2) is always 3. Not a huge surprise but only because the function is pure. If the add function used some outside value, then you could never predict its behavior.
注意:add(1,2) 总是 3。不是很大的惊喜,只是因为这个函数是纯函数的。

Pure Functions will always produce the same output given the same inputs.
纯函数总是在给予同样的输入得到同样的输出。

Since Pure Functions cannot change any external variables, all of the following functions are impure:
因为纯函数不能改变额外的变量,所有下面的函数都不是纯函数:

writeFile(fileName);
updateDatabaseTable(sqlCmd);
sendAjaxRequest(ajaxRequest);
openSocket(ipAddress);

All of these function have what are called Side Effects. When you call them, they change files and database tables, send data to a server or call the OS to get a socket. They do more than just operate on their inputs and return outputs. Therefore, you can never predict what these functions will return.

Pure functions have no side effects.
纯函数没有副作用。

In Imperative Programming Languages such as Javascript, Java, and C#, Side Effects are everywhere. This makes debugging very difficult because a variable can be changed anywhere in your program. So when you have a bug because a variable is changed to the wrong value at the wrong time, where do you look? Everywhere? That’s not good.
在指令式编程语言,比如 JavaScript,Java,和 C#,均有副作用。这使得调试非常困难,因为在你的项目中,变量可以在**任何地方**改变。所以当你有一个错误时,因为变量在错误的时间被改变为错误的值,你在哪里发现的,处处?这样不好。

At this point, you’re probably thinking, “HOW THE HELL DO I DO ANYTHING WITH ONLY PURE FUNCTIONS?!”
在这点,你可能会想,“使用仅仅使用纯函数,我能做什么?!”

In Functional Programming, you don’t just write Pure Functions.
在函数式编程中,你不用编写纯函数。

Functional Languages cannot eliminate Side Effects, they can only confine them. Since programs have to interface to the real world, some parts of every program must be impure. The goal is to minimize the amount of impure code and segregate it from the rest of our program.
函数式编程不能根除副作用,它们仅仅能限制这些影响。由于程序必须有与现实世界接口,所以每个程序的某些部分必须是不纯的。我们的目标是最小化不纯代码的数量,并将其与程序的其他部分隔离开来。

Immutability

不变性

Do you remember when you first saw the following bit of code:
你还记得你第一次看到下面一段代码的时候吗:

var x = 1;
x = x + 1;

And whoever was teaching you told you to forget what you learned in math class? In math, x can never be equal to x + 1.
无论是谁教你,你都要忘记你在数学课上学的东西?在数学上,x 永远不会等于**x+1**。

But in Imperative Programming, it means, take the current value of x add 1 to it and put that result back into x.
但是在命令式编程中,当前 **x**的值加 1,然后将这个值赋给返回的 x

Well, in functional programming, x = x + 1 is illegal. So you have to remember what you forgot in math… Sort of.
事实上,在函数式编程中,x=x+1 是非法的。因此,你必须记住你在数学上忘记的。

There are no variables in Functional Programming.
在函数式编程中没有变量。

Stored values are still called variables because of history but they are constants, i.e. once x takes on a value, it’s that value for life.
由于历史,存储值仍然被称为变量,但它们是常量,也就是,一旦 **x**有一个值,那么它将在声明周周期中一直保持这个值。

Don’t worry, x is usually a local variable so its life is usually short. But while it’s alive, it can never change.
不用担心,x 通常用于局部变量,因此它的生命周期很短,尽管它在生命中,仍不能改变。

Here’s an example of constant variables in Elm, a Pure Functional Programming Language for Web Development:
这里有一个在 Elm 中关于常量变量,一个针对 Web 开发的纯函数编程语言:

addOneToSum y z =
    let
        x = 1
    in
        x + y + z

If you’re not familiar with ML-Style syntax, let me explain. addOneToSum is a function that takes 2 parameters, y and z.
如果你不熟悉 ML风格的语法,让我来给你解释。

Inside the let block, x is bound to the value of 1, i.e. it’s equal to 1 for the rest of its life. Its life is over when the function exits or more accurately when the let block is evaluated.

Inside the in block, the calculation can include values defined in the let block, viz. x. The result of the calculation x + y + z is returned or more accurately, 1 + y + z is returned since x = 1.

Once again, I can hear you ask “HOW THE HELL AM I SUPPOSED TO DO ANYTHING WITHOUT VARIABLES?!”

Let’s think about when we want to modify variables. There are 2 general cases that come to mind: multi-valued changes (e.g. changing a single value of an object or record) and single-valued changes (e.g. loop counters).

Functional Programming deals with changes to values in a record by making a copy of the record with the values changed. It does this efficiently without having to copy all parts of the record by using data structures that makes this possible.

Functional programming solves the single-valued change in exactly the same way, by making a copy of it.

Oh, yes and by not having loops.

“WHAT NO VARIABLES AND NOW NO LOOPS?! I HATE YOU!!!”

Hold on. It’s not like we can’t do loops (no pun intended), it’s just that there are no specific loop constructs like for, while, do, repeat, etc.

Functional Programming uses recursion to do looping.

Here are two ways you can do loops in Javascript:

// simple loop construct
var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); _// prints 55_

// without loop construct or variables (recursion)
function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); _// prints 55_

Notice how recursion, the functional approach, accomplishes the same as the for loop by calling itself with a new start (start + 1) and a new accumulator (acc + start). It doesn’t modify the old values. Instead it uses new values calculated from the old.

Unfortunately, this is hard to see in Javascript even if you spend a little time studying it, for two reasons. One, the syntax of Javascript is noisy and two, you’re probably not used to thinking recursively.

In Elm, it’s easier to read and, therefore, understand:

sumRange start end acc =
    if start > end then
        acc
    else
        sumRange (start + 1) end (acc + start)

Here’s how it runs:

sumRange 1 10 0 =      -- sumRange (1 + 1)  10 (0 + 1)
sumRange 2 10 1 =      -- sumRange (2 + 1)  10 (1 + 2)
sumRange 3 10 3 =      -- sumRange (3 + 1)  10 (3 + 3)
sumRange 4 10 6 =      -- sumRange (4 + 1)  10 (6 + 4)
sumRange 5 10 10 =     -- sumRange (5 + 1)  10 (10 + 5)
sumRange 6 10 15 =     -- sumRange (6 + 1)  10 (15 + 6)
sumRange 7 10 21 =     -- sumRange (7 + 1)  10 (21 + 7)
sumRange 8 10 28 =     -- sumRange (8 + 1)  10 (28 + 8)
sumRange 9 10 36 =     -- sumRange (9 + 1)  10 (36 + 9)
sumRange 10 10 45 =    -- sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 =    -- 11 > 10 => 55
55

You’re probably thinking that for loops are easier to understand. While that’s debatable and more likely an issue of familiarity, non-recursive loops require Mutability, which is bad.

I haven’t entirely explained the benefits of Immutability here but check out the Global Mutable State section in Why Programmers Need Limits to learn more.

One obvious benefit is that if you have access to a value in your program, you only have read access, which means that no one else can change that value. Even you. So no accidental mutations.

Also, if your program is multi-threaded, then no other thread can pull the rug out from under you. That value is constant and if another thread wants to change it, it’ll have create a new value from the old one.

Back in the mid 90s, I wrote a Game Engine for Creature Crunch and the biggest source of bugs was multithreading issues. I wish I knew about immutability back then. But back then I was more worried about the difference between a 2x or 4x speed CD-ROM drives on game performance.

Immutability creates simpler and safer code.
不变性创建简单安全的代码。

My Brain!!!!

我的大脑!!!!

Enough for now.
对于现在而言是足够的。

In subsequent parts of this article, I’ll talk about Higher-order Functions, Functional Composition, Currying and more.
在本文的后续部分,我将讨论高阶函数,函数组成,柯里化等。

Up Next: Part 2

If you liked this, click the? below so other people will see this here on Medium.
如果你喜欢,点击这?下面,其他人会在 Medium 上看到这个

If you want to join a community of web developers learning and helping each other to develop web apps using Functional Programming in Elm please check out my Facebook Group, Learn Elm Programming https://www.facebook.com/groups/learnelm/
如果你想加入 web 开发者学习社区,请在 ELM 函数编程使用相互帮助开发Web应用程序,请检查我的脸谱网组,学习 ELM 的编程 https://www.facebook.com/groups/learnelm/

My Twitter: @cscalfani

One clap, two clap, three clap, forty?
一拍,两拍,三拍,四十拍?

By clapping more or less, you can signal to us which stories really stand out.
通过或多或少的掌声,你可以告诉我们哪些故事真的很突出。