由于语言的某些句法和语义特征,Scala中的函数式编程可能很难掌握。 特别是,当您熟悉主库时,一些语言工具和方法来实现您计划的内容就很明显了-但是在学习之初(尤其是您自己学习)并不容易识别它们。
因此,我认为在Scala中分享一些功能编程技巧将很有用。 示例和名称对应于猫,但是由于一般的理论基础,scalaz中的语法应相似。

9)扩展方法构造函数
让我们从最基本的工具开始-可以将实例转换为Option,Either等的任何类型的扩展方法,尤其是:
.some
和Option
的相应none
构造方法;.asRight
和.asLeft
Either
;.valid
, .invalid
, .validNel
, .invalidNel
用于Validated
使用它们的两个主要优点:
- 它更加紧凑和易于理解(因为保存了方法调用的顺序)。
- 与构造函数选项不同,这些方法的返回类型扩展为超类型,即:
import cats.implicits._ Some("a")
尽管多年来类型推断已得到改善,并且这种行为有助于程序员保持镇定的可能情况有所减少,但是今天在Scala中仍然可能由于过度专业化的输入而导致编译错误。 通常,与
Either
一起工作时会产生将头撞到桌子上的渴望(请参见《
带猫的Scala》第4.4.2章)。
关于该主题的
.asRight
件事:
.asRight
和
.asLeft
还有一个类型参数。 例如,
"1".asRight[Int]
是
Either[Int, String]
。 如果未提供此参数,则编译器将尝试输出它并获取
Nothing
。 但是,与每次构造两个参数或不提供两个参数相比,它更方便。
8)五十种阴影*>
在任何
Apply
方法(即
Applicative
,
Monad
等)中定义的*>运算符仅表示“处理初始计算并将结果替换为第二个参数中指定的结果”。 用代码的语言(例如
Monad
):
fa.flatMap(_ => fb)
为什么对不起眼的操作使用模糊的符号运算符? 开始使用ApplicativeError和/或MonadError,您会发现该操作将保留整个工作流程的错误效果。 以
Either
为例:
import cats.implicits._ val success1 = "a".asRight[Int] val success2 = "b".asRight[Int] val failure = 400.asLeft[String] success1 *> success2
如您所见,即使发生错误,计算仍会短路。 *>将帮助您处理
Monix
,
IO
等
Monix
延迟计算。
有一个对称运算,<*。 因此,在前面的示例中:
success1 <* success2
最后,如果符号的使用对您而言是陌生的,则不必诉诸它。 *>只是
productR
的别名,而* <是
productL
的别名。
注意事项
在个人对话中,亚当·沃斯基(Adam Warski)(感谢亚当!)正确地指出,除了*>(
productR
)之外,还有
FlatMapSyntax
>>。 >>的定义方式与
fa.flatMap(_ => fb)
,但有两个细微差别:
- 它是独立于
productR
定义的,因此,如果由于某种原因此方法的协定发生更改(从理论上讲,可以在不违反单子法则的情况下对其进行更改,但我不确定MonadError
),则您将不会受苦; - 更重要的是>>具有第二个操作数,该操作数由按名称调用,即
fb: => F[B]
。 如果执行可能导致堆栈爆炸的计算,则语义上的差异就变得至关重要。
基于此,我开始更频繁地使用*>。 一种或另一种方式,不要忘记上面列出的因素。
7)扬起帆!
许多人花时间将
lift
概念付诸实践。 但是当您成功时,您会发现他无处不在。
就像函数式编程中飞速发展的许多术语一样,
lift
来自
类别理论 。 我将尝试解释:进行一项操作,更改其类型的签名,使其与抽象类型F直接相关。
在Cats中,最简单的示例是
Functor :
def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f)
这意味着:更改此功能,使其作用于给定类型的函子F。
lift函数通常与给定类型的嵌套构造函数同义。 因此,
EitherT.liftF
本质上是
EitherT.right.
来自Scaladoc的示例 :
import cats.data.EitherT import cats.implicits._ EitherT.liftF("a".some)
蛋糕上的樱桃:Scala标准库中到处都有
lift
。 最受欢迎(也许在日常工作中最有用)的示例是
PartialFunction
:
val intMatcher: PartialFunction[Int, String] = { case 1 => "jak się masz!" } val liftedIntMatcher: Int => Option[String] = intMatcher.lift liftedIntMatcher(1)
现在我们可以继续处理更紧迫的问题。
6)mapN
mapN
是用于元组的有用的辅助函数。 再次,这不是新奇的东西,而是好的旧运算符的替代品。 他是尖叫。
这是在两个元素的元组的情况下mapN的样子:
本质上,它允许我们映射来自任何F的元组内的值,这些F是半群(乘积)和函子(映射)。 因此:
import cats.implicits._ ("a".some, "b".some).mapN(_ ++ _)
顺便说一句,不要忘记,对于猫来说,您会得到元组的map和
leftmap
:
("a".some, List("b","c").mapN(_ ++ _))
另一个有用的
.mapN
函数是实例化案例类:
case class Mead(name: String, honeyRatio: Double, agingYears: Double) ("półtorak".some, 0.5.some, 3d.some).mapN(Mead) //Some(Mead(półtorak,0.5,3.0))
当然,您更愿意为此使用for循环运算符,但是mapN在简单情况下避免使用单声道转换器。
import cats.effect.IO import cats.implicits._
方法具有相似的结果,但后者无需使用单相变压器。
5)嵌套
Nested
本质上是monad变压器的广义双重形式。 顾名思义,它允许您在某些条件下执行附件操作。 这是
.map(_.map( :
import cats.implicits._ import cats.data.Nested val someValue: Option[Either[Int, String]] = "a".asRight.some Nested(someValue).map(_ * 3).value
除了
Functor
,
Nested
泛化了
Applicative
,
ApplicativeError
和
Traverse
。 其他信息和示例在
此处 。
4).recover / .recoverWith / .handleError / .handleErrorWith / .valueOr
Scala中的函数式编程与处理错误影响有很大关系。
ApplicativeError
和
MonadError
有一些有用的方法,可能对您找出四个主要方法之间的细微差别很有用。 因此,使用
ApplicativeError F[A]:
handleError
根据指定的函数将调用点处的所有错误转换为A。recover
操作的方式类似,但是接受部分功能,因此可以将您选择的错误转换为A。handleErrorWith
与handleErrorWith
相似,但是其结果应类似于F[A]
,这意味着它可以帮助您转换错误。recoverWith
作用类似于recover,但也需要F[A]
。
如您所见,可以将
handleErrorWith
限制为
handleErrorWith
和
recoverWith
,它们涵盖了所有可能的功能。 但是,每种方法都有其优点,并且以其自己的方式很方便。
通常,我建议您熟悉
ApplicativeError API,它是Cats中最丰富的API之一,并且继承自MonadError-这意味着
cats.effect.IO
,
monix.Task
支持它。
还有另一个用于
Either/EitherT
,
Validated
和
.valueOr
-
.valueOr
。 本质上,它与
Option
.getOrElse
,但对于包含“左侧”内容的类通用。
import cats.implicits._ val failure = 400.asLeft[String] failure.valueOr(code => s"Got error code $code")
3)胡同猫
alley-cats是两种情况的便捷解决方案:
- 不遵守其法律的图块类实例为100%;
- 异常的辅助性输入错误,可以正确使用。
从历史上看,
Try
的monad实例在该项目中最受欢迎,因为就致命错误而言,
Try
不能满足所有的monadic律。 现在,他真正地被介绍给猫。
尽管如此,我还是建议您熟悉
此模块 ,它对您似乎很有用。
2)负责任地对待进口
您必须从文档,书籍或其他地方知道猫使用特定的导入层次结构:
cats.x
用于基本(内核)类型;
cats.data
用于验证类型,monad转换器等数据类型;
cats.syntax.x._支持扩展方法,以便您可以调用sth.asRight,sth.pure等。
cats.instances.x.
_将各种类型类的实现直接导入到各个具体类型的隐式范围中,这样,在调用sth.pure时,不会发生“找不到隐式”错误。
当然,您注意到了
cats.implicits._
的导入,该导入了隐式范围内类型类的所有语法和所有实例。
原则上,在使用Cats开发时,您应该从FAQ的某些导入序列开始,即:
import cats._ import cats.data._ import cats.implicits._
如果您更好地了解图书馆,则可以将其与自己的品味相结合。 遵循一个简单的规则:
cats.syntax.x
提供与x相关的扩展语法;cats.instances.x
提供了实例类。
例如,如果您需要
.asRight
(这是
Either
的扩展方法),
Either
执行以下操作:
import cats.syntax.either._ "a".asRight[Int]
另一方面,要获取
Option.pure
您必须导入
cats.syntax.monad
和 cats.instances.option
:
import cats.syntax.applicative._ import cats.instances.option._ "a".pure[Option]
通过手动优化导入,您将限制Scala文件中的隐式作用域,从而减少编译时间。
但是,请:如果不满足以下条件,请不要这样做:
- 您已经很好地掌握了Cats
- 您的团队拥有同一级别的图书馆
怎么了 因为:
这是因为
cats.implicits
和
cats.instances.option
都是
cats.instances.option
的扩展。 实际上,我们将其隐式作用域导入两次,而不是使编译器混淆。
而且,隐式层次结构中没有魔术-这是类型扩展的明确序列。 您只需要参考
cats.implicits
的定义并检查类型层次结构即可。
在大约10到20分钟的时间里,您可以进行足够的研究来避免出现此类问题-相信我,这项投资肯定会有所回报。
1)不要忘了猫更新!
您可能会认为您的FP库是永恒的,但实际上
scalaz
和
scalaz
积极更新。 以猫为例。 以下是最新更改:
因此,在处理项目时,请不要忘记检查库的版本,阅读新版本的注释并及时进行更新。