软件架构风格 仓库风格

懒惰的评估。

看到一粒花在沙粒中的世界和天堂中的野花
一小时内将无限握在手中,永恒

–威廉·布莱克

几年前,我参加了有关C#的培训课程。 我记得在理解两件事时遇到了麻烦。 其中之一就是LINQ,部分原因是我不太了解语法。 我已经沉迷于SQL多年了,这种语言虽然相似但又不太一样,这让我感到困惑。 另外,我还没有学习编程的功能风格。 现在我拥有了,这对我来说更加有意义。

另一件事是yield关键字。 但是,再次了解功能样式会更有意义,而且实际上非常简单。 .NET文档提供了此示例用法:

public class PowersOf2
{static void Main(){foreach (var i in Power(2, 8))Console.Write("{0} ", i);}public static IEnumerable<int> Power(int number, int exponent){var result = 1;for (var i = 0; i < exponent; i++){result = result * number;yield return result;}}
}

运行时将打印出: 2 4 8 16 32 64 128 256

发生的情况是, Power方法返回一个IEnumerable实例,并且foreach循环反复在其上调用MoveNext。 但是,Power方法不会显式创建IEnumerable的实例。 通过使用yield return语句,该方法从字面上变为一个迭代器,该迭代器根据需要计算其迭代值。 此处返回的值是第一个迭代的元素。 此时,控制返回到foreach循环,然后其主体执行一次。 然后,它在迭代器上再次调用MoveNext,这使控件在yield return语句之后立即返回Power方法。 Power方法的内部状态从前保留,因此它的for循环再次迭代,并产生下一个迭代的元素。 因此,控制会在foreach循环和yield return语句之间反复跳转,直到最后for循环终止并且Power退出,而无需再次调用yield return。

正如文档所解释的那样, yield return的主要用例是在方法中为自定义集合类型实现迭代器,而无需创建新的IEnumerable或IEnumerator实现,从而避免了定义新类的麻烦。 它给出了另一个例子来说明这一点。

但是,如果迭代器方法永不退出怎么办?

假设我们删除了终止条件,那么包含yield return语句的循环将永远继续:

public static IEnumerable<int> Numbers()
{var number = 1;for (;;)yield return number++;
}

如果我们这样称呼,我们将打印出1 2 3 4 5 6 7 8 9 10

static void Main()
{foreach (var i in Numbers()){Console.Write("{0} ", i);if (i == 10)break;}
}

现在,我们不得不跳出foreach循环,因为显然Numbers方法永远不会正常退出。 那么,这有什么意义呢? 实际上,这是一个深刻而深刻的观点。 我们现在拥有的是一个IEnumerable ,它声称包含所有自然数,这是一个无限集。

当然这是不可能的!

的确如此,但事实证明,诺言比不能兑现的诺言更为重要。

如果您在第4部分中回想过,我们有这段代码会生成一个质数筛子,并返回一个谓词来测试数字是否为质数:

private Predicate<Integer> notInNonPrimesUpTo(int limit) {Set<Integer> sieve = IntStream.range(2, (limit / 2)).boxed().flatMap(n -> Stream.iterate(n * 2, nonPrime -> nonPrime += n).takeWhile(nonPrime -> nonPrime <= limit)).collect(Collectors.toSet());return candidate -> !sieve.contains(candidate);
}

我说Stream.iterate很有趣,我稍后会Stream.iterate 。 时机已到。 这是迭代器的另一个示例,该迭代器旨在迭代无限列表:

Stream.iterate(n * 2, nonPrime -> nonPrime += n)

它生成一个从(n * 2)并每次增加n的整数流。 请注意,没有上限。 终止条件(当超过limit值时)在此处:

.takeWhile(nonPrime -> nonPrime <= limit)

另外,如果您还记得上一篇文章,我们在Clojure中看到了range函数的几种用法。 在每种情况下,我都以这种方式使用它:

(range 1 10)

它的结果是一个从1到10的数字列表。但是我们也可以不带任何参数调用range

user=> (take 5 (range))
(0 1 2 3 4)

是的,没有参数的range是另一个据称是无限的整数列表。 但是,如果您仅在REPL中执行(range) ,则仅阻塞即可。 它正在等待采取某些措施。 与Java Stream.iterate和C# yield循环一样,仅在请求值时才生成值。 这就是为什么评估被认为是懒惰的。 一个“急切”评估的序列在创建时立即生成其所有值,就像(range 1 10)那样。 “延迟”评估的序列仅按需生成其值。 正是这种延缓的执行使伪造无限序列成为可能。 只要眨眨眼和一个会意的点头,假装就可以维持,前提是该程序从不真正要求兑现诺言。

循环索引。

正如您可能现在已经知道的那样,我和下一个人一样喜欢一个聪明的把戏,但是如果可以将其用于实际使用以使我的代码变得更好,我将最喜欢它。 懒惰的评估也是如此。

在本系列的前面,我断言采用函数式风格意味着您几乎不必再次编写循环。 映射和归约确实确实涵盖了很多用例,但是尽管如此,您仍然有时确实需要使用某种计数器来跟踪迭代。 现在,如果我使用C#或Java这样的语言进行编程,而这两种语言在本质上都是必不可少的,那么我就不会费心去以功能性样式进行操作。 我会改用传统的for循环:

public void IterateWithIndex()
{var numbers = new[] {"one", "two", "three", "four", "five"};for (var i = 0; i < numbers.Length; i++)Console.WriteLine("{0}: {1}", i, numbers[i]);
}

希望不必要地改变状态,但对我来说,简单仍然是一种较高的美德,在某些语言中,这仍然是解决问题的最简单方法。 在所有其他条件都相同的情况下,我将选择三行简单的代码来改变状态,这些代码会在完成相同工作的六行难以理解的功能代码上进行突变。

某些语言为您提供了一种开箱即用的简便方法,可以以功能样式迭代序列,还为您提供了迭代计数器。 Groovy提供了eachWithIndex方法,该方法无需管理即可完成:

[[symbol: 'I', value: 1],[symbol: 'V', value: 5],[symbol: 'X', value: 10],[symbol: 'L', value: 50],[symbol: 'C', value: 100],[symbol: 'D', value: 500],[symbol: 'M', value: 1000],
].eachWithIndex { numeral, i -> println("${i}: ${numeral.symbol} ${numeral.value}")
}

在这种情况下,这是一个不错的选择。 但是让主题回到懒惰的评估,如果您正在使用Clojure,可以使用(range)以有趣的方式解决问题:

(map (fn [i number] (format "%d: %s" i number))(range)'("one" "two" "three" "four" "five"))

回想一下,在多个序列上的map一直持续到最短的序列用完为止。 显然,懒惰求值的序列不会首先用完,因此它将继续递增计数,直到其他列表用尽。 因此,结果是:

("0: one" "1: two" "2: three" "3: four" "4: five")

也就是说,Clojure还提供了map-indexed功能,该功能与Groovy中的eachWithIndex相似:

(map-indexed (fn [i number] (format "%d: %s" i number))'("one" "two" "three" "four" "five"))

给我看一个示例,其中懒惰的评估确实有帮助。

我最近在现实世界中遇到的一种需要循环索引的情况是,我想编写参数化的日志消息。 为此,我选择了一种类似于C#中使用的模板格式,即:

The {0} has a problem. Cause of failure: {1}. Recommended solution: {2}.

那不是实际的日志消息之一,但是您知道了。 该程序使用Java编写,解决方案非常简单:

private String replaceParameters(String template, String... parameters) {var out = template;for (var i = 0; i < parameters.length; i++)out = out.replace(String.format("{%d}", i), parameters[i]);return out;
}

它是绝对必要的,它可以无耻地改变其内部状态。 如果我们尝试将其重写为更具功能性的样式,则可能看起来像这样:

private String replaceParameters(String template, String... parameters) {var i = new AtomicInteger(0);return Arrays.stream(parameters).reduce(template, (string, parameter) ->string.replace(String.format("{%d}", i.getAndIncrement()),parameter));
}

我不认为这是实用的。 而是,我认为这是为了自己的目的而使用功能样式,而不是因为它更好。 回想一下我所说的函数式编程的最佳位置吗? 不是吗 一方面,牺牲了原件的清晰度:为了帮助提高可读性,我不得不将其分成更多行。 而且它实际上并没有更多的功能-无论如何,它仍然会改变状态,因为AtomicInteger正在递增。

因此,在Java中,我认为传统循环是最好的解决方法,但是如果使用功能适当的语言却无法为我们提供选择,那又如何呢? 在Clojure中, map-indexed功能在这里无济于事。 相反,这是reduce的工作,并且没有reduce-indexed功能。 我们拥有的是zipmap ,有时在其他语言中称为“拉链”。 之所以这样命名是因为它的行为让人联想到拉链的作用:

user=> (zipmap [1 2 3] ["one" "two" "three"])
{1 "one", 2 "two", 3 "three"}

我们可以使用它使用reduce编写参数替换函数,如下所示:

(defn- replace-parameter [string [i parameter]](clojure.string/replace string (format "{%d}" i) parameter))(defn replace-parameters [template parameters](reduce replace-parameter template (zipmap (range) parameters)))

而且有效!

user=> (replace-parameters#_=>   "The {0} has a problem. Cause of failure: {1}. Recommended solution: {2}."#_=>   ["time machine" "out of plutonium" "find lightning bolt"])
"The time machine has a problem. Cause of failure: out of plutonium. Recommended solution: find lightning bolt."

懒惰自己。

我们在开箱即用的序列中玩了一些,所以让我们做一些练习,其中包括构建自己的一个。 您可能听说过Pascal的Triangle。 用数学术语来说,帕斯卡的三角形是二项式系数的三角形阵列,但是您真的不需要知道这意味着什么,因为它的构造非常简单。 首先,在顶点处放置1,然后在下面放置数字行,以偏移上面的数字的左右,从而建立一个三角形,以便每行中的数字都比上面的数字多。 每个数字等于其上方两个数字的和。 对于三角形边缘上的数字,不存在的数字视为零。 该图应该清楚:

11 11 2 11 3 3 11 4 6 4 1

编写一个程序来产生这个问题是一个有趣的问题。 如果您观察到可以通过复制前一行,将其中一个向左或向右移动,然后将偏移位数字相加来生成下一行,则变得更加简单。

1  4  6  4  1  0
+   0  1  4  6  4  1
=   1  5 10 10  5  1

用Clojure术语可以这样表示:

(defn next-row [previous](apply vector (map + (conj previous 0)(cons 0 previous))))

快速测试表明它有效:

#'user/next-row
user=> (next-row [1])
[1 1]
user=> (next-row [1 1])
[1 2 1]
user=> (next-row [1 2 1])
[1 3 3 1]

然后我们可以像下面这样使用iterateiterate地构建整个三角形:

(def triangle (iterate next-row [1]))

Clojure中, iterate通过重复应用所述先前迭代的结果到建立一个序列懒惰地next-row功能,从所述种子值[1]这是一个包含数字1的向量。

然后,您可以根据需要从triangle获取任意多行:

user=> (take 7 triangle)
([1] [1 1] [1 2 1] [1 3 3 1] [1 4 6 4 1] [1 5 10 10 5 1] [1 6 15 20 15 6 1])

甚至请求任意行:

user=> (nth triangle 10)
[1 10 45 120 210 252 210 120 45 10 1]

显然,尽管triangle似乎是一个序列,但是内存中不存在任何实际序列,除非您碰巧从结果中构建自己。 如果从头开始阅读C#中的yield return示例,您将在此处看到相同的行为。 似乎很矛盾,无限的收集需要更少的内存,但这是一个重要的性能提示。

重复一遍。

为了完成对惰性评估的探索,让我们玩得开心。 正如有人曾经不经意地观察到的那样,FizzBu​​zz的kata很受欢迎,因为它避免了在房间里没人能记住如何对数组进行二进制搜索的尴尬。 如果您是世界上少数不熟悉该游戏的人之一,那就太简单了:您可以对数字进行计数,同时用“ Fizz”替换所有三倍的整数,用“ Buzz”替换所有五倍的整数,而所有都是“ FizzBu​​zz”的倍数。

注意:通常说它起源于喝酒游戏,尽管沃里克规则略有不同,但实际上我确实在大学玩过这样的喝酒游戏。 在播放时,嗡嗡声是4而不是5,还有一个附加规则:当十进制数字包含数字3时,您还必须说“嘶嘶声”,而当包含4时,您必须说“嗡嗡声”。 因此,12代表“嘶嘶声”,13代表“嘶嘶声”,14代表“嗡嗡声”,15代表“嘶嘶声”。 这使其变得更加复杂,因此我们变得更加醉酒。

我们曾经在一个乐队社交活动中尝试过使用二进制数字进行演奏,但是男中音萨克斯演奏者以他是法律系学生而不是科学家为由反对。 因此,我们同意改为使用罗马数字,这样一来就杀死了两片kata。

如果存在规范的实现,则在Java中可能看起来像这样:

public class FizzBuzz {public static void main(String[] args) {for (var i = 0; i < 100; i++)System.out.println(fizzBuzz(i));}private static String fizzBuzz(int i) {if (i % 3 == 0 && i % 5 == 0)return "FizzBuzz";else if (i % 3 == 0)return "Fizz";else if (i % 5 == 0)return "Buzz";elsereturn String.valueOf(i);}
}

(但绝对不是这样 )。

因此,该解决方案将其视为算术问题,但也许不一定如此。 如果我们对其进行分析,则整个周期将以十五个周期连续重复,如下所示:

数量,数量,嘶嘶声,数量,嗡嗡声,嘶嘶声,数量,数量,嘶嘶声,嗡嗡声,数量,嘶嘶声,数量,数量,嘶嘶声

也许我们可以在程序中这样对待它。 Clojure具有称为cycle的功能,该功能会无限期地重复提供的模式:

user=> (take 10 (cycle [nil nil "Fizz"]))
(nil nil "Fizz" nil nil "Fizz" nil nil "Fizz" nil)

如果我们有一个映射到这三个惰性序列上的函数,那么我们可以从它们中生成一个惰性评估的FizzBu​​zz实现:

(def numbers (map inc (range)))               ; goes 1 2 3 4 5 6 7 8 9 10 etc.
(def fizzes (cycle [nil nil "Fizz"]))         ; goes nil nil fizz nil nil fizz etc.
(def buzzes (cycle [nil nil nil nil "Buzz"])) ; goes nil nil nil nil buzz nil nil nil nil buzz etc.

map inc是必需的,因为range从0开始,我们需要它从1开始,所以我们将序列中的每个值都增加1。现在让我们在编写函数时遇到一点麻烦:

(defn fizzbuzz [n fizz buzz](if (or fizz buzz)(str fizz buzz)(str n)))

那很简单; 如果fizz buzzbuzz是真实的(即不是nil),则将它们串联在一起-在串联字符串时,将nil视为空字符串-否则将n返回为字符串。 当我们在所有三个惰性序列上映射它时,我们得到FizzBu​​zz!

user=> (take 30 (map fizzbuzz numbers fizzes buzzes))
("1" "2" "Fizz" "4" "Buzz" "Fizz" "7" "8" "Fizz" "Buzz" "11" "Fizz" "13" "14" "FizzBuzz" "16" "17" "Fizz" "19" "Buzz" "Fizz" "22" "23" "Fizz" "Buzz" "26" "Fizz" "28" "29" "FizzBuzz")

我们还可以使用nth从序列中获取任何值(考虑到nth从零开始计数,而不是从1开始计数):

user=> (nth (map fizzbuzz numbers fizzes buzzes) 1004)
"FizzBuzz"

那不是很酷吗?

下次。

在下一篇文章中,我将结束对函数式编程实践方面的考察。 我将研究持久性数据结构,这是功能语言如何实现不可变数据结构,从而给人以可变性的印象,同时通过避免不必要的数据重复来最有效地利用内存。

整个系列:

  1. 介绍
  2. 第一步
  3. 一流的函数I:Lambda函数和映射
  4. 一流的功能II:过滤,缩小和更多
  5. 高阶函数I:函数组成和Monad
  6. 高阶函数II:咖喱
  7. 懒惰评估

翻译自: https://www.javacodegeeks.com/2018/11/functional-style-part-7.html

软件架构风格 仓库风格

软件架构风格 仓库风格_功能风格–第7部分相关推荐

  1. 软件架构风格 仓库风格_功能风格–第9部分

    软件架构风格 仓库风格 实用主义 在本系列中,我们进行了一次旋风之旅,浏览了我认为最重要的与函数式编程相关的主题,以及一些我认为很好的相关知识. 我们从基础开始,定义了我认为是FP的本质,并展示了如何 ...

  2. python图片风格迁移毕设_神经风格迁移是如何运作的概述及Python实现

    神经风格迁移是如何运作的概述及Python实现 作者:PHPYuan 时间:2019-03-26 03:40:37 深度学习可以捕获一个图像的内容并将其与另一个图像的风格相结合,这种技术称为神经风格迁 ...

  3. 图像迁移风格保存模型_图像风格迁移也有框架了:使用Python编写,与PyTorch完美兼容,外行也能用...

    原标题:图像风格迁移也有框架了:使用Python编写,与PyTorch完美兼容,外行也能用 选自Medium 作者:Philip Meier 机器之心编译 编辑:陈萍 易于使用的神经风格迁移框架 py ...

  4. python风格变换图片_图片风格转换--深度学习介绍

    前言 先举个机器学习的应用例子:图片的风格转换. 原图.jpg 处理后的图.jpg 机器学习 通过计算机强大的计算能力进行迭代运算.试错得到相关知识. 形象生动的描述请看:机器学习 深度学习 神经网络 ...

  5. 图像迁移风格保存模型_图像风格迁移

    样式迁移 如果你是一位摄影爱好者,也许接触过滤镜.它能改变照片的颜色样式,从而使风景照更加锐利或者令人像更加美白.但一个滤镜通常只能改变照片的某个方面.如果要照片达到理想中的样式,经常需要尝试大量不同 ...

  6. python字符串驼峰转换_驼峰风格字符串转换为下滑线风格字符串

    形如 productTypeId(驼峰风格),若要转换为 product_type_id(下划线风格),则可使用如下方法: // 将驼峰风格替换为下划线风格 public static String ...

  7. 目前流行的装修风格_当下最流行的十大装修风格,目前主流装修风格前十

    随着时代的发展,装修风格也是多种多样,但对于没有装修经验的业主来说,需要提前了解一下装修风格,了解的同时选择一个适合自己的装修风格,接下来,我们一起来了解一下,新房装修十大风格有哪些?现在房子装修风格 ...

  8. python风格变换图片_巧用python实现图片转换成素描和漫画格式

    [相关学习推荐:python视频教程] 本文实例为大家分享了python实现图片转换成素描和漫画格式的具体代码,供大家参考,具体内容如下 原图 图片转换后的成果 源码# -*- coding: utf ...

  9. 图像风格迁移_图像风格迁移—谷歌大脑团队任意图像风格化迁移论文详解

    点击蓝字关注我们 AI研习图书馆,发现不一样的世界 风格迁移 图像风格化迁移是一个很有意思的研究领域,它可以将一张图的风格迁移到另外一张图像上,由此还诞生了Prisma和Ostagram这样的商业化产 ...

最新文章

  1. 【转】modulenotfounderror: no module named ‘matplotlib._path‘问题的解决
  2. 转货币格式和 rgb转hex
  3. leetcode 138. Copy List with Random Pointer | 138. 复制带随机指针的链表(复杂链表的复制)
  4. CentOS7.4搭建FTP服务器(vsftp)
  5. Django集成celery实战小项目
  6. 作业2结对(升级版)
  7. 越混越差的十个原因,看看你有没有?
  8. 机器学习代码实战——保存和加载模型(Save and Load Model)
  9. Linux about MySQL
  10. 计算机四级网络工程师(计算机网络单选)- 知识点
  11. Win32的setlocale详解
  12. python中[::-1][1:2][1::2]的用法
  13. 用Java来解析torrent文件
  14. 【文献阅读】医学图像分割中的loss函数选择-Loss odyssey in medical image segmentation loss
  15. 关于固态硬盘冷数据掉速问题解决方案
  16. 云骞开源即时通讯软件
  17. CAN收发器 评估标准理解
  18. 【最新】iOS App上架AppStore 教程 (Part 二)
  19. Ubuntu子系统安装GPGPU-SIM(附相关文件)
  20. 基于MThings配置MODBUS数据中常见的几种时间概念介绍(超时时间、间隔时间、轮询时间)

热门文章

  1. 办公室布置(2013)
  2. 阿里云HaaS固件烧录至m5stack的步骤
  3. 个人网站中的小贴纸(待更新)
  4. Linux如何开启远程连接
  5. 2022年拼接显示屏市场前景分析及研究报告
  6. 2022年度废物报告
  7. java源码-String
  8. DASH机器人被邀请入驻苹果店,作为一家创业公司是如何做到的?
  9. Linux下的网络配置[ dhcp]
  10. [事务] 编程式事务和声明式事务的优缺点