相关链接:
https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-5-c70adc9cf56a
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.
- 采用第一步来理解功能编程概念是最重要的,有时也是最困难的步骤。 但它不一定是。 没有正确的观点。
Referential Transparency
image.png
Referential Transparency is a fancy term to describe that a pure function can safely be replaced by its expression. An example will help illustrate this.
- 参考透明度是一个奇特的术语,用于描述纯函数可以安全地用其表达式替换。 一个例子将有助于说明这一点。
In Algebra when you had the following formula:
- 在代数中,当你有以下公式时:
y = x + 10
And were told: 并被告知:
x = 3
You could substituted x back into the equation to get:
- 您可以将x替换为等式以获得:
y = 3 + 10
Notice that the equation is still valid. We can do the same kind of substitution with pure functions.
- 请注意,等式仍然有效。 我们可以用纯函数做同样的替换。
Here’s a function in Elm that puts single quotes around the supplied string:
- 这是Elm中的一个函数,它在提供的字符串周围放置单引号:
quote str =
"'" ++ str ++ "'"
And here’s some code that uses it:
- 以下是一些使用它的代码:
findError key =
"Unable to find " ++ (quote key)
Here findError builds an error message when a search for key is unsuccessful.
- 当搜索密钥失败时,findError会生成错误消息。
Since the quote function is pure, we can simply replace the function call in findError with the body of the quote function (which is just an expression):
- 由于quote函数是纯函数,我们可以简单地将findError中的函数调用替换为quote函数的主体(这只是一个表达式):
findError key =
"Unable to find " ++ ("'" ++ str ++ "'")
This is what I call Reverse Refactoring (which makes more sense to me), a process that can be used by programmers or programs (e.g. compilers and test programs) to reason about code.
- 这就是我所说的反向重构(这对我来说更有意义),程序员或程序(例如编译器和测试程序)可以使用这个过程来推理代码。
This can be especially helpful when reasoning about recursive functions.
- 在推理递归函数时,这可能特别有用。
Execution Order
- 执行顺序
Most programs are single-threaded, i.e. one and only one piece of code is being executed at a time. Even if you have a multithreaded program, most of the threads are blocked waiting for I/O to complete, e.g. file, network, etc.
- 大多数程序是单线程的,即一次只执行一段代码。 即使您有多线程程序,大多数线程也会被阻塞,等待I / O完成,例如 文件,网络等
This is one reason why we naturally think in terms of ordered steps when we write code:
- 这就是为什么我们在编写代码时自然地考虑有序步骤的原因之一:
1. Get out the bread
2. Put 2 slices into the toaster
3. Select darkness
4. Push down the lever
5. Wait for toast to pop up
6. Remove toast
7. Get out the butter
8. Get a butter knife
9. Butter toast
1.拿出面包
2.将2片放入烤面包机
3.选择黑暗
4.按下控制杆
5.等待吐司弹出
6.去除吐司
7.拿出黄油
8.拿一把黄油刀
9.黄油吐司
In this example, there are two independent operations: getting butter and toasting bread. They only become interdependent at step 9.
- 在这个例子中,有两个独立的操作:获取黄油和烤面包。 它们仅在步骤9中变得相互依赖。
We could do steps 7 and 8 concurrently with steps 1 through 6 since they are independent from one another.
-我们可以与步骤1到6同时执行步骤7和8,因为它们彼此独立。
But the minute we do this, things get complicated:
- 但是,一旦我们这样做,事情变得复杂:
Thread 1
--------
1. Get out the bread
2. Put 2 slices into the toaster
3. Select darkness
4. Push down the lever
5. Wait for toast to pop up
6. Remove toast
Thread 2
--------
1. Get out the butter
2. Get a butter knife
3. Wait for Thread 1 to complete
4. Butter toast
What happens to Thread 2 if Thread 1 fails? What is the mechanism to coordinate both threads? Who owns the toast: Thread 1, Thread 2 or both?
- 如果线程1失败,线程2会发生什么? 协调两个线程的机制是什么? 谁拥有吐司:线程1,线程2或两者?
It’s easier to not think about these complexities and leave our program single threaded.
- 不考虑这些复杂性并让我们的程序保持单线程更容易。
But when it’s worth squeezing out every possible efficiency of our program, then we must take on the monumental effort to write multithreading software.
- 但是,如果值得挤出我们程序的所有可能的效率,那么我们必须承担编写多线程软件的巨大努力。
However, there are 2 main problems with multithreading. First, multithreaded programs are difficult to write, read, reason about, test and debug.
- 但是,多线程存在两个主要问题。 首先,多线程程序很难编写,读取,推理,测试和调试。
Second, some languages, e.g. Javascript, don’t support multithreading and those that do, support it badly.
- 第二,一些语言,例如 Javascript,不支持多线程和那些支持多线程的,支持它很糟糕。
But what if order didn’t matter and everything was executed in parallel?
- 但是如果顺序无关紧要并且所有内容都是并行执行的呢?
While this sounds crazy, it’s not as chaotic as it sounds. Let’s look at some Elm code to illustrate this:
- 虽然这听起来很疯狂,但并不像听起来那么混乱。 让我们看一些Elm代码来说明这一点:
buildMessage message value =
let
upperMessage =
String.toUpper message
quotedValue =
"'" ++ value ++ "'"
in
upperMessage ++ ": " ++ quotedValue
Here buildMessage takes message and value then produces an uppercased message, a colon and value in single quotes.
- 这里buildMessage接收消息和值然后产生一个大写的消息,冒号和单引号值。
Notice how upperMessage and quotedValue are independent. How do we know this?
- 请注意upperMessage和quotedValue是如何独立的。 我们怎么知道呢?
There are 2 things that must be true for independence. First, they must be pure functions. This is important because they must not be affected by the execution of the other.
- 独立必须有两件事情。 首先,它们必须是纯粹的功能。 这很重要,因为它们不得受另一个执行的影响。
If they were not pure, then we could never know that they’re independent. In that case, we’d have to rely on the order that they were called in the program to determine their execution order. This is how all Imperative Languages work.
- 如果他们不纯洁,那么我们永远不会知道他们是独立的。 在这种情况下,我们必须依赖于在程序中调用它们的顺序来确定它们的执行顺序。 这就是所有面向过程语言的工作方式。
The second thing that must be true for independence is that the output of one function is not used as the input of the other. If this was the case, then we’d have to wait for one to finish before starting the second.
- 对于独立而言必须要做的第二件事是一个函数的输出不用作另一个函数的输入。 如果是这种情况,那么在开始第二次之前我们必须等待一次完成。
In this case, upperMessage and quotedValue are both pure and neither requires the output of the other.
- 在这种情况下,upperMessage和quotedValue都是纯粹的,既不需要另一个的输出。
Therefore, these 2 functions can be executed in ANY ORDER.
- 因此,这两个函数可以在任何顺序执行。
The compiler can make this determination without any help from the programmer. This is only possible in a Pure Functional Language because it’s very difficult, if not impossible, to determine the ramifications of side-effects.
- 编译器可以在没有程序员帮助的情况下进行此确定。 这只能在纯函数式语言中实现,因为确定副作用的后果是非常困难的,如果不是不可能的话。
The order of execution in a Pure Functional Language can be determined by the compiler.
- 纯函数式语言中的执行顺序可由编译器确定。
This is extremely advantageous considering that CPUs are not getting faster. Instead, manufactures are adding more and more cores. This means that code can execute in parallel at the hardware level.
- 考虑到CPU没有变得更快,这是非常有利的。 相反,制造商正在增加越来越多的核心。 这意味着代码可以在硬件级别并行执行。
Unfortunately, with Imperative Languages, we cannot take full advantage of these cores except at a very coarse level. But to do so requires drastically changing the architecture of our programs.
- 不幸的是,使用面向过程语言,我们无法充分利用这些核心,除非是非常粗略的。 但要做到这一点,就需要彻底改变我们程序的架构。
With Pure Functional Languages, we have the potential to take advantage of the CPU cores at a fine grained level automatically without changing a single line of code.
- 使用Pure Functional Languages,我们有可能在不改变单行代码的情况下自动利用细粒度级别的CPU核心。
Type Annotations
- 类型注释
In Statically Typed Languages, types are defined inline. Here’s some Java code to illustrate:
- 在静态类型语言中,类型是内联定义的。 这里有一些Java代码来说明:
public static String quote(String str) {
return "'" + str + "'";
}
Notice how the typing is inline with the function definition. It gets even worse when you have generics:
- 注意键入是如何与函数定义内联的。 当你有泛型时它变得更糟:
private final Map<Integer, String> getPerson(Map<String, String> people, Integer personId) {
// ...
}
I’ve bolded the types which makes them stand out but they still interfere with the function definition. You have to read it carefully to find the names of the variables.
- 我加粗了使它们脱颖而出的类型,但它们仍然干扰了函数定义。 您必须仔细阅读它以找到变量的名称。
With Dynamically Typed Languages, this is not a problem. In Javascript, we can write code like:
- 使用动态类型语言,这不是问题。 在Javascript中,我们可以编写如下代码:
var getPerson = function(people, personId) {
// ...
};
This is so much easier to read without all of that nasty type information getting in the way. The only problem is that we give up the safety of typing. We could easily pass in these parameters backwards, i.e. a Number for people and an Object for personId.
- 如果没有所有令人讨厌的类型信息阻碍,这将更容易阅读。 唯一的问题是我们放弃打字的安全性。 我们可以轻松地向后传递这些参数,即人的数字和personId的对象。
We wouldn’t find out until the program executed, which could be months after we put it into production. This would not be the case in Java since it wouldn’t compile.
- 在程序执行之前我们不会发现,这可能是我们投入生产后的几个月。 这不是Java中的情况,因为它不会编译。
But what if we could have the best of both worlds. The syntactical simplicity of Javascript with the safety of Java.
- 但是如果我们能够拥有两全其美的话呢? Javascript的语法简洁性与Java的安全性。
It turns out that we can. Here’s a function in Elm with Type Annotations:
- 事实证明,我们可以。 这是Elm中带有类型注释的函数:
add : Int -> Int -> Int
add x y =
x + y
Notice how the type information is on a separate line. This separation makes a world of difference.
- 请注意类型信息如何在单独的行上。 这种分离使世界变得与众不同。
Now you may think that the type annotation has a typo. I know I did when I first saw it. I thought that the first -> should be a comma. But there’s no typo.
- 现在您可能认为类型注释有拼写错误。 我知道我第一次见到它时就做了。 我认为第一个 - >应该是一个逗号。 但没有错字。
When you see it with the implied parentheses it makes a bit more sense:
- 当你用隐含的括号看到它时,它会更有意义:
add : Int -> (Int -> Int)
This says that add is a function that takes a single parameter of type Int and returns a function that takes a single parameter Int and returns an Int.
- 这表示add是一个函数,它接受Int类型的单个参数,并返回一个接受单个参数Int并返回Int的函数。
Here’s another type annotation with the implied parentheses shown:
- 这是另一种带有隐含括号的类型注释:
doSomething : String -> (Int -> (String -> String))
doSomething prefix value suffix =
prefix ++ (toString value) ++ suffix
This says that doSomething is a function that takes a single parameter of type String and returns a function that takes a single parameter of type Int and returns a function that takes a single parameter of type String and returns a String.
- 这表示doSomething是一个函数,它接受String类型的单个参数,并返回一个函数,该函数接受Int类型的单个参数,并返回一个函数,该函数接受String类型的单个参数并返回一个String。
Notice how everything takes a single parameter. That’s because every function is curried in Elm.
- 注意一切都只需要一个参数。 那是因为每个功能都是在Elm中进行的。
Since parentheses are always implied to the right, they are not necessary. So we can simply write:
- 由于括号总是隐含在右边,所以它们不是必需的。 所以我们可以简单地写:
doSomething : String -> Int -> String -> String
Parentheses are necessary when we pass functions as parameters. Without them, the type annotation would be ambiguous. For example:
- 当我们将函数作为参数传递时,括号是必需的。 没有它们,类型注释将是模糊的。 例如:
takes2Params : Int -> Int -> String
takes2Params num1 num2 =
-- do something
is very different from: 与以下内容截然不同:
takes1Param : (Int -> Int) -> String
takes1Param f =
-- do something
takes2Param is a function that requires 2 parameters, an Int and another Int. Whereas, takes1Param requires 1 parameters a function that takes an Int and another Int.
- takes2Param是一个需要2个参数的函数,一个Int和另一个Int。 而take1Param需要1个参数,这个函数采用Int和另一个Int。
Here’s the type annotation for map:
- 这是地图的类型注释:
map : (a -> b) -> List a -> List b
map f list =
// ...
Here parentheses are required because f is of type (a -> b), i.e. a function that takes a single parameter of type a and returns something of type b.
- 这里括号是必需的,因为f是(a - > b)类型,即一个采用类型a的单个参数并返回类型b的函数的函数。
Here type a is any type. When a type is uppercased, it’s an explicit type, e.g. String. When a type is lowercased, it can be any type. Here a can be String but it could also be Int.
- 这里键入a是任何类型。 当一个类型是大写的时,它是一个显式类型,例如 串。 当类型为小写时,它可以是任何类型。 这里可以是String但它也可以是Int。
If you see (a -> a) then that says that the input type and the output type MUST be the same. It doesn’t matter what they are but they must match.
- 如果你看到(a - > a)那么说输入类型和输出类型必须相同。 它们是什么并不重要,但它们必须匹配。
But in the case of map, we have (a -> b). That means that it CAN return a different type but it COULD also return the same type.
- 但就地图而言,我们有(a - > b)。 这意味着它可以返回不同的类型,但它也可以返回相同的类型。
But once the type for a is determined, a must be that type for the whole signature. For example, if a is Int and b is String then the signature is equivalent to:
- 但是一旦确定了a的类型,就必须是整个签名的类型。 例如,如果a为Int且b为String,则签名等效于:
(Int -> String) -> List Int -> List String
Here all of the a’s have been replaced with Int and all of the b’s have been replaced with String.
- 这里所有的a都被Int替换,所有的b都被String替换掉了。
The List Int type means that a list contains Ints and List String means that a list contains Strings. If you’ve used generics in Java or other languages then this concept should be familiar.
- List Int类型表示列表包含Ints,List String表示列表包含字符串。 如果您使用过Java或其他语言的泛型,那么这个概念应该是熟悉的。
Enough for now.
- 够了。
In the final part of this article, I’ll talk about how you can use what you’ve learned in your day-to-day job, i.e. Functional Javascript and Elm.
- 在本文的最后部分,我将讨论如何使用您在日常工作中学到的知识,即Functional Javascript和Elm。
网友评论