文档章节

[译] 印度朋友手把手教你学Scala(9):函数式编程简介

暗夜在火星
 暗夜在火星
发布于 2017/02/26 21:09
字数 3441
阅读 13
收藏 0
点赞 0
评论 0

 

/**
 * 谨献给我最爱的Yoyo
 *
 * 原文出处:https://madusudanan.com/blog/scala-tutorials-part-9-intro-to-functional-programming/
 * @author dogstar.huang <chanzonghuang@gmail.com> 2017-02-21
 */

由于它的优雅和性能特点,Scala的函数式编程范式在最近几年变得相当流行。

这是关于Scala系列教程的第九章。点击这里可查看整个系列。

在这系列里的前面8章里,我从来都没谈及过Scala函数式编程这一方面,那是因为如果你是这个范式的新手(这世界大多数是),那么伴随着完全陌生和困难的范式,学习这门语言和环境会有一点难学,那就是为什么我一开始没有谈及他们。既然我们已经覆盖了面向对象编程方面到达一定程度,理解起来就稍微更简单了。

目录

  • 看看在2017年的摩尔定律
  • 什么是编程范式?
  • 理解冯·诺依曼编程风格
  • 冯·诺依曼风格有什么问题
  • 归约(Map reduce)和不可变性
  • Scala里的纯函数
  • 高级抽象和避免一个字一个字的概念化程序

看看在2017年的摩尔定律

摩尔定律是一个关于电路中晶体管数量每两年翻一番的预测。就在最近,很多例如英特尔AMD和很多其他芯片制作商意识到,摩尔定律可以是增加核数而不是关注于单核时钟速度。

下面和图表展示了每核的性能,以及它是年复一年攀升的。
图片来源:维基百科

可以看到,每核的性能停滞在3Ghz左右,没有看到以前的增长。

这是另外一张图表,谈论了核的数量增长与时钟速度增长的关系。

数据来源于:Kunle Olukotun, Lance Hammond, Herb Sutter, Burton Smith, Chris Batten and Krste Asanovic

很明显,摩尔定律现在是依据核的数量增长,而不是单核时钟速度的增长。

这个硬件问题现在变成了一个软件问题,程序/操作系统必须稳定地利用多核。

这和函数式编程又有什么关系呢?函数式范式以尽可能最好的方式给予了程序解决这个问题的能力,以及许多其他的好处。继续看下去,挖掘更多信息。

什么是编程范式?

引用自维基百科

编程范型或编程范式(英语:Programming paradigm),(范即模范之意,范式即模式、方法),是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照方法学)。

这与设计模式不同,设计模式是高级抽象并且大部分独立于与语言。

编程范式建立在语言中,就像C不支持OOP里的类/对象,而Java不支持指针。

世界上有几百个编程语言,对于全部这些共同之处的是和语言相关某种的范式。

Java是面向对象的,C是过程式的,尽管你可以在Java里进行过程式编程,但它更关注于面向对象编程。

如果你对不同的范式不熟悉,我强烈推荐你看下在线课程:斯坦福 CS107 - 编程范式,它会给你一个非常棒的流行范式概览。

理解冯·诺依曼编程风格

命令式编程/面向对象编程基于一种叫冯·诺依曼架构的东西,它是一个描绘计算机各个部分的计算机架构,如下所示。
图片来源:维基百科

今天的编程风格与上面的架构强烈相关。

  • 程序变量 -> 计算机存储单元(寄存器)
  • 控制语句 -> 跳转指令
  • 赋值语句 -> 获取/存储指令
  • 表达式 -> 内存引用/算术指令

如果你曾经学习/编写过汇编,那么你会很熟悉上面的指令。在任何情况下,如果汇编/CPU/寄存器对你来说就像希腊和拉丁,那么或者复习一下计算机架构将有会很大的帮助。这些课程对架构进行了深度讲解,不管怎样应该能给你关于我们正在讨论的东西一个很好的概念。

用汇编或命令式语言编写的程序称为指令序列,他们表示被计算机逐步执行的指令。

这个概念在很大程度上塑造了C,C++,Java,C#等编程语言。

冯·诺依曼风格有什么问题

那么冯·诺依曼风格有什么问题呢?为什么我们需要一种新的范式?

这位于经典的调查页面:John Backus编程是否可以从冯·诺依曼风格解放出来

对于一个调查页面来说这有点长,但我强烈建议你阅读一下它。很多概念可能现在对你来说没有什么意义,但后面它肯定会变得有意义的。

理解冯·诺依曼风格的编码有什么问题,需要深入的函数式编程知识,以便我们可以对比他们。以接下来的部分,我们会来看几种函数式编程的构造/概念,这些能够帮助我们理解。

归约(Map reduce)和不可变性

当发生了大数据爆炸时,归约曾是它的核心。Hadoop和归约简介是一个开始学习Hadoop和Map Reduce很好的资源。

我们会来看一下非常简单的例子,是关于归约怎么在Apache Spark里工作的。语法并不是非常重要,重点是背后的概念。

以下是一段简单的、可以工作的代码片段,处理了著名的单词计数问题。

val textFile = sc.textFile("hdfs://...")  
val counts = textFile.flatMap(line => line.split(" "))  
                 .map(word => (word, 1))
                 .reduceByKey(_ + _)
counts.saveAsTextFile("hdfs://...")  

归约分为两个阶段,即Map阶段和Reduce阶段。

出于我们的理解,假设以下是要执行单词数的内容,例如要计算每个单词在给定数据集里出现的次数。

This is one of few movies that are truly timeless. And it's entertaining and moving, no matter how many times you view it.

The only other movie I have ever seen that effects me as strongly is To Kill a Mockingbird. Both movies leave me feeling cleaner for having watched them.

It is a simple film, yet it has an everlasting message.  

当你把上面的段落当作一个文本文件给到上面的代码时,它会给出集合中去重后的单词以及每个单词在指定集合里出现了多少次。例如单词movies在集合里出现了2次。

让我们分别来看下单词计数的每行代码。

val textFile = sc.textFile("hdfs://...")  

首先我们读取从来自HDFS的一个文本文件。这个文本文件包含了类似上面示例中给出的内容。

接着语法开始变得奇怪,我特定选择了代码里的Scala部分而不是Java的,因为它能更好地解释这些概念。

flatMpamap是Scala集合类库里的一部分。我不打算详细解释他们,因为他们两个都是很大的一个主题,还不如先关注他们做了什么这个简单的术语上。

通过下面的例子来进行理解,

It is a simple film, yet it has an everlasting message.  

步骤分解如下。

为了简单起见,假设输入的单词在被归约任务处理之前全部转换成了小写。

  • flatMap的输出会是分隔好的单词 -> it,is,a,simple ….
  • 然后map接收这些分隔好的单词并且把他们处理为一个键值对 -> (it,1),(is,1),(a,1) ….
  • 随后reduceByKey做了它名字的事情。它接收例如(it,1),下一个(it,1)这样的单词并且把它们合并成(it,2)。


图片来源:维基百科

整个过程最重要的部分是输入的数据一旦给定就不能再改变,即它是不可变的,如果Spark的worker配置正常的话可以轻易扩展到多核甚至多台机器。

不可变对象是线程安全的认为他们的内容不能被改变。线程安全直接与并行和多核/多机器性能/扩展性有关。如果锁太多,整个操作的速度就会慢下来,而如果没有锁就会导致错误的结果。

为了根除这点,我们需要不可变编程概念以便不需要线程安全。在Scala,不可变性意味着线程安全

在其他语言里它可能是相同的意义,也可能不同。在Scala,样本类是不可变对象/类的典型例子。除了创建的部分,他们还可以在多线程中传递而不会引起任何线程安全问题,因为这些数据是不可变的。

样本类这一章里,我们看到了在创建后改变样本类的值会导致编译时的异常,并且定义他们很容易。

这是为什么在Scala里喜爱不可变性而反对可变编程构造的原因之一。

Scala里的纯函数

函数式编程的核心是纯函数。乍一看,“方法不是和函数一样的吗?”这个问题让人困惑。结果是在Scala他们是不一样的。

注意:我们只是简单地接触函数的概念。在后面的文章里会更多地对它进行解释。

在函数式编程的世界里,纯函数是没有副作用的函数,即它只依赖于输入的参数并且不会修改其他地方的状态。

如果函数做了下面一件或多件事,它就被视为非纯函数。

  • 修改在当前函数/类或者其他类的变量
  • 打印到控制台或者其他地方
  • 存储数据到数据库

这里还有更多好处。转念一想,有人可能会想如果上面这些我都不能做,我还怎么写代码?

在函数式语言里,有用于处理I/O的monad,但monad是一个高级的东西,后面我们会讲到。但请记住,Scala是多范式的,根据手上的问题程序员可以选择不同 风格。

FP的目的不显了移除可变性,而是为了减少它们,使其具有种性能优点。

来看一个例子。

val x = math.sin(10)  

scala.math包的sin函数可以认为是一个纯函数。等一下,它会修改变量,对吗?实际上没有,它返回一个新的值并且没有改变传入的值。

object Runnable extends App {

  val init = 10

  val x = math.sin(init)

  println(x)
  // init没被改变
  println(init)


}

Java程序员可能会认为对于对象,它是按引用传递的,在这样情况下函数是不纯的。它是的,但函数只接收原始类型。在只有传值调用和传名调用(稍候我们会探讨)的Scala里这明显是不同的。

纯函数令人难以置信的有用,它们有以下这些好处。

  • 因为没有可变性调用,所以不需要线程安全。
  • 减少你代码的认知负荷。关于这点,更多请见后面的教程。
  • 既然不依赖任何额外的资源,它们提供了优秀的封装,即使用它的人不需要了解背后发生了什么。

高级抽象和避免一个字一个字的概念化程序

解决复杂性的关键是抽象。这是由John backus发表的文章提出的主要概念,并且不需要多线程和水平扩展。因为摩尔定律以及单核处理器性能看到了天花板,这点最近变得很有名。

可以从归约例子里取一些代码来作为抽象的一个很好的例子。

object Runnable extends App {

  val x = List(10,20,30,40)

  val y = x.map( i => i* 3)

  println(y)

}

这是一个遍历转换,即把列表中的每个元素乘以3并且返回一个新的列表。

传统的循环,有一点乏味。

import scala.collection.mutable.ListBuffer

object Runnable extends App {

  val x = List(10,20,30,40)

  val mutable = new ListBuffer[Int]

  for (e <- x) {
    mutable += (e * 3)
  }

  println(mutable.toList)

}

因为List是一个不可变的数据结构,我们需要引入一个额外的可变的称为ListBuffer的数据结构。这已经打破了我们前面讨论的不可变原则,而且函数式的版本更为清晰明了。

现在运行时可以接收这些代码块,并且与for循环相比,还可以进行大量的优化,这就是高级抽象的强大之处。

总结一下到目前为止我们学到的内容。

  • 我们明白了为什么函数式编程最近变得流行,和摩尔定律的意义。
  • 为什么不可变性对于线程安全和性能是重要的。
  • 纯函数的概念以及其优点。
  • 高级构造与其重要性。

这是进入函数式编程世界非常漫长但有益的旅途的开始。还有很多核心概念要涵盖,但这篇文章关注于为什么这是一件大事情。

直到下一次。^_^


------------------------

© 著作权归作者所有

共有 人打赏支持
暗夜在火星

暗夜在火星

粉丝 150
博文 150
码字总数 310930
作品 1
广州
程序员
Scala 技术周刊 | 第 24 期

这里有最新的 Scala 社区动态、技术博文。 微信搜索 「scalacool」关注我们,及时获取最新资讯。 深度阅读 Resolve me, Implicitly 依赖注入 Refined types, what are they good for? 让类型...

ScalaCool ⋅ 2017/10/23 ⋅ 0

如何不编程,采集网站评论信息?(视频教程)

如果你不想学编程,数据采集爬虫也是可以用的。 简介 最近的一次组会,我们请来了一位分享嘉宾——15级研究生庞琳同学,给我们科研团队分享网站评论数据的采集。 还记得去年这个时候,庞琳过...

王树义 ⋅ 04/23 ⋅ 0

Scala 学习纪要

2016-03-08 转眼一晃到了16年的38节,突然想放些scala的学习心得,翻着过去的博客突然发现了这一篇博客,竟然又是好久没有更新了。 不过对于这段时间scala的使用和学习,对scala及函数式编程...

大师兄悟空 ⋅ 2015/05/25 ⋅ 0

Cats(一):从函数式编程思维谈起

本文由 Yison 发表在 ScalaCool 团队博客。 Cats logo 如果你看到一个开源类库,logo 是四只猫 + 五个箭头,可能会略感不适 — 这是工程代码里可以使用的玩意儿吗? 没错,这是 TypeLevel 设...

ScalaCool ⋅ 2017/11/30 ⋅ 0

Scala 是个有趣的语言

[英文出处]:Why Scala Is Interesting [中文翻译]:外刊IT评论 经常读我的博客的人应该知道,我最喜欢的编程语言是Haskell。我喜欢函数式编程,Haskell是一个能把函数式编程推向极致的语言。...

鉴客 ⋅ 2010/08/18 ⋅ 1

Fanta/free-programming-books-zh_CN

免费的编程中文书籍索引 免费的编程中文书籍索引,欢迎投稿。 国外程序员在 stackoverflow 推荐的程序员必读书籍,中文版。 stackoverflow 上的程序员应该阅读的非编程类书籍有哪些? 中文版...

Fanta ⋅ 2016/11/14 ⋅ 0

[转]Scala是个有趣的语言

经常读我的博客的人应该知道,我最喜欢的编程语言是Haskell。我喜欢函数式编程,Haskell是一个能把函数式编程推向极致的语言。虽然如此,我仍然时刻关注着其它新兴的和即将产生的编程语言,特...

mj4738 ⋅ 2011/11/01 ⋅ 0

开源电子书

目录 语言无关类 操作系统 智能系统 分布式系统 编译原理 函数式概念 计算机图形学 WEB服务器 版本控制 编辑器 NoSQL PostgreSQL MySQL 管理和监控 项目相关 设计模式 Web 大数据 编程艺术 ...

zting科技 ⋅ 2017/12/11 ⋅ 0

为Play初学者准备的Scala基础知识

1 前言 本文的主要目的是为了让Play Framework的初学者快速了解Scala语言,算是一篇Play Framework的入门前传吧。 使用PlayFramework可以极大的提高开发效率,但是需要注意,PlayJava入门很简...

joymufeng ⋅ 2017/05/16 ⋅ 0

ZigBEE

手把手教你学Zigbee第一讲:http://www.tudou.com/programs/view/fQattIYSwBo/ 手把手教你学Zigbee第二讲:http://www.tudou.com/programs/view/7-dIvUgk2kg/ 手把手教你学Zigbee第三讲:htt...

GIFCOOL ⋅ 2016/09/02 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Redis 单线程 为何却需要事务处理并发问题

Redis是单线程处理,也就是命令会顺序执行。那么为什么会存在并发问题呢? 个人理解是,虽然redis是单线程,但是可以同时有多个客户端访问,每个客户端会有 一个线程。客户端访问之间存在竞争...

码代码的小司机 ⋅ 31分钟前 ⋅ 0

到底会改名吗?微软GVFS 改名之争

微软去年透露了 Git Virtual File System(GVFS)项目,GVFS 是 Git 版本控制系统的一个开源插件,允许 Git 处理 TB 规模的代码库,比如 270 GB 的 Windows 代码库。该项目公布之初就引发了争...

linux-tao ⋅ 41分钟前 ⋅ 0

笔试题之Java基础部分【简】【二】

1.静态变量和实例变量的区别 在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变...

anlve ⋅ 58分钟前 ⋅ 0

Lombok简单介绍及使用

官网 通过简单注解来精简代码达到消除冗长代码的目的 优点 提高编程效率 使代码更简洁 消除冗长代码 避免修改字段名字时忘记修改方法名 4.idea中安装lombnok pom.xml引入 <dependency> <grou...

to_ln ⋅ 今天 ⋅ 0

【转】JS浮点数运算Bug的解决办法

37.5*5.5=206.08 (JS算出来是这样的一个结果,我四舍五入取两位小数) 我先怀疑是四舍五入的问题,就直接用JS算了一个结果为:206.08499999999998 怎么会这样,两个只有一位小数的数字相乘,怎...

NickSoki ⋅ 今天 ⋅ 0

table eg

user_id user_name full_name 1 zhangsan 张三 2 lisi 李四 `` ™ [========] 2018-06-18 09:42:06 星期一½ gdsgagagagdsgasgagadsgdasgagsa...

qwfys ⋅ 今天 ⋅ 0

一个有趣的Java问题

先来看看源码: public class TestDemo { public static void main(String[] args) { Integer a = 10; Integer b = 20; swap(a, b); System.out......

linxyz ⋅ 今天 ⋅ 0

十五周二次课

十五周二次课 17.1mysql主从介绍 17.2准备工作 17.3配置主 17.4配置从 17.5测试主从同步 17.1mysql主从介绍 MySQL主从介绍 MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主...

河图再现 ⋅ 今天 ⋅ 0

docker安装snmp rrdtool环境

以Ubuntu16:04作为基础版本 docker pull ubuntu:16.04 启动一个容器 docker run -d -i -t --name flow_mete ubuntu:16.04 bash 进入容器 docker exec -it flow_mete bash cd ~ 安装基本软件 ......

messud4312 ⋅ 今天 ⋅ 0

OSChina 周一乱弹 —— 快别开心了,你还没有女友呢。

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享吴彤的单曲《好春光》 《好春光》- 吴彤 手机党少年们想听歌,请使劲儿戳(这里) @clouddyy :小萝莉街上乱跑,误把我认错成...

小小编辑 ⋅ 今天 ⋅ 9

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部