1. 简介
本文将带你了解 Scalaz —— 一个用于函数式编程的 Scala 库。它提供了纯函数式的数据结构以及丰富的 类型类(type classes) 支持。
2. 依赖配置
要开始使用 Scalaz,只需在你的 build.sbt
文件中添加如下依赖:
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.3.7"
3. 类型安全的相等性比较
Scala 原生支持使用双等号 ==
来比较两个值是否相等,但这种比较 缺乏类型安全性。例如比较不同类型的值时,会直接返回 false
,而不会报错。
✅ Scalaz 提供了三等号操作符 ===
,用于实现类型安全的比较。如果尝试比较不同类型,编译器会直接报错:
Type Mismatch.
Required: Int
Found: String
这能帮助我们提前发现潜在问题。同时,Scalaz 还提供了不等号操作符 =/=
替代 Scala 原生的 !=
:
assertTrue(15 === 15)
assertTrue(15 =/= 25)
4. Order 类型类
Order
是一个 类型类,为值的比较提供了一套丰富的操作符。相比 Scala 自带的 scala.math.Ordering
和 scala.math.Ordered
,它的优势在于:
- 提供了更丰富的操作符,如
<
、>
、>=
、<=
- 更重要的是,它是类型安全的
比如以下代码在 Scala 中是合法的,但在 Scalaz 中会编译失败:
3 < 5.6 // ❌ 类型不匹配
此外,对于第三方类(我们无法修改其源码),我们可以借助 隐式机制 来为其添加比较能力。
举个例子,我们有一个 Score
类:
case class Score(amt: Double)
我们可以通过定义一个隐式对象来支持 Score
的比较:
implicit object scoreOrdered extends Order[Score] {
override def order(x: Score, y: Score): Ordering =
x.amt compare y.amt match {
case 1 => Ordering.GT
case 0 => Ordering.EQ
case -1 => Ordering.LT
}
}
之后就可以使用如下操作:
assertTrue(Score(0.9) > Score(0.8))
assertTrue(Score(0.9) gt Score(0.8))
assertTrue(Score(0.9) gte Score(0.8))
还可以通过 ?|?
获取 scalaz.Ordering
类型的比较结果:
assertEquals(Ordering.GT, Score(0.9) ?|? Score(0.8))
assertEquals(Ordering.LT, Score(0.8) ?|? Score(0.9))
assertEquals(Ordering.EQ, Score(0.9) ?|? Score(0.9))
5. Show 类型类
Show
的作用是为对象提供类型安全的字符串表示。与 toString
不同,Show
可以通过隐式机制为第三方类提供定制化输出,尤其是那些 final
类型无法重写 toString
的场景。
✅ Scalaz 已为 Int
、Double
、Float
等标准类型提供了 Show
实例。调用 .shows
方法可直接获取字符串表示:
assertEquals("3", 3.shows)
assertEquals("3.4", 3.4.shows)
自定义 Show
实例如下:
implicit object threadShow extends Show[Thread] {
override def show(f: Thread): Cord =
Cord(s"Thread id = ${f.getId}, name = ${f.getName}")
}
6. Enum 类型类
Enum
是一种枚举类型,支持对变量值进行限定。Scalaz 提供了丰富的操作符来处理枚举,例如生成范围、获取前驱/后继等。
✅ 示例:生成字符范围并转换为列表
val enum = 'a' |-> 'g'
val enumAsList = enum.toList
val expectedResult = IList('a', 'b', 'c', 'd', 'e', 'f', 'g')
val expectedResultAsList = List('a', 'b', 'c', 'd', 'e', 'f', 'g')
assertEquals(expectedResult, enum)
assertEquals(expectedResultAsList, enumAsList)
✅ 获取前驱/后继
assertEquals('c', 'b'.succ)
assertEquals('a', 'b'.pred)
✅ 步进操作
assertEquals('e', 'b' -+- 3)
assertEquals('f', 'b' -+- 4)
assertEquals('b', 'e' --- 3)
assertEquals('a', 'c' --- 2)
✅ 获取最小/最大值(返回 Option
)
assertEquals(Some(-2147483648), Enum[Int].min)
assertEquals(Some(2147483647), Enum[Int].max)
我们也可以自定义枚举类型,例如优先级:
case class Priority(num: Int, name: String)
val HIGH = Priority(1, "HIGH")
val MEDIUM = Priority(2, "MEDIUM")
val LOW = Priority(3, "LOW")
implicit object PriorityEnum extends Enum[Priority] {
def order(p1: Priority, p2: Priority): Ordering =
(p1.num compare p2.num) match {
case -1 => Ordering.LT
case 0 => Ordering.EQ
case 1 => Ordering.GT
}
def succ(s: Priority): Priority = s match {
case LOW => MEDIUM
case MEDIUM => HIGH
case HIGH => LOW
}
def pred(s: Priority): Priority = s match {
case LOW => HIGH
case MEDIUM => LOW
case HIGH => MEDIUM
}
override def max = Some(HIGH)
override def min = Some(LOW)
}
使用示例:
// generate range
val expectedEnum = IList(Priority(1, "LOW"), Priority(2, "MEDIUM"), Priority(3, "HIGH"))
assertEquals(expectedEnum, LOW |-> HIGH)
//range to list
assertEquals(expectedEnum.toList, (LOW |-> HIGH).toList)
//pred and succ
assertEquals(HIGH, LOW.pred)
assertEquals(HIGH, MEDIUM.succ)
//step forward and back
assertEquals(MEDIUM, HIGH -+- 2)
assertEquals(LOW, LOW --- 3)
//min and max
assertEquals(Some(Priority(3, "HIGH")), Enum[Priority].max)
assertEquals(Some(Priority(1, "LOW")), Enum[Priority].min)
7. Option 操作增强
Scalaz 为 Option
提供了更多实用操作符,让代码更简洁:
✅ 使用 some
和 none
构造 Option
assertEquals(Some(12), some(12))
assertEquals(None, none[Int])
✅ 直接调用 .some
assertEquals(Some(13), 13.some)
assertEquals(Some("baeldung"), "baeldung".some)
✅ some/none
操作符(类似 getOrElse
)
val opt = some(12)
val value1 = opt some { a =>
a
} none 0
val value2 = opt
.some(_ * 2)
.none(0)
assertEquals(12, value1)
assertEquals(24, value2)
✅ 管道操作符 |
val opt = some(12)
val opt2 = none[Int]
assertEquals(12, opt | 0)
assertEquals(5, opt2 | 5)
✅ 一元操作符 ~
(提取值或返回默认零值)
assertEquals(25, ~some(25))
assertEquals(0, ~none[Int])
assertEquals("baeldung", ~some("baeldung"))
assertEquals("", ~none[String])
8. 字符串操作增强
Scalaz 提供了一些实用的字符串操作,例如:
✅ 复数形式转换(plural
)
assertEquals("apples", "apple".plural(2))
assertEquals("tries", "try".plural(2))
assertEquals("range rovers", "range rover".plural(2))
9. Boolean 操作增强
✅ fold
操作符:根据布尔值选择返回值
val t = true
val f = false
val expectedValueOnTrue = "it was true"
val expectedValueOnFalse = "it was false"
val actualValueOnTrue = t.fold[String](expectedValueOnTrue, expectedValueOnFalse)
val actualValueOnFalse = f.fold[String](expectedValueOnTrue, expectedValueOnFalse)
assertEquals(expectedValueOnTrue, actualValueOnTrue)
assertEquals(expectedValueOnFalse, actualValueOnFalse)
✅ option
操作符:根据布尔值返回 Option
val restrictedData = "Some restricted data"
val actualValueOnTrue = true option restrictedData
val actualValueOnFalse = false option restrictedData
assertEquals(Some(restrictedData), actualValueOnTrue)
assertEquals(None, actualValueOnFalse)
✅ 三元操作符 ? |
val t = true
val f = false
assertEquals("true", t ? "true" | "false")
assertEquals("false", f ? "true" | "false")
✅ ??
操作符:条件为真时返回值,否则返回零值
val t = true
val f = false
assertEquals("string value", t ?? "string value")
assertEquals("", f ?? "string value")
assertEquals(List(1, 2, 3), t ?? List(1, 2, 3))
assertEquals(List(), f ?? List(1, 2, 3))
assertEquals(5, t ?? 5)
assertEquals(0, f ?? 5)
✅ !?
是 ??
的反向操作
10. Map 操作增强
Scalaz 提供了比标准库更丰富的 Map
操作:
10.1. alter
✅ 高阶函数,用于修改 Map 中某个 key 的值:
val map = Map("a" -> 1, "b" -> 2)
val mapAfterAlter1 = map.alter("b") { maybeValue =>
maybeValue
.some(v => some(v * 10))
.none(some(0))
}
val mapAfterAlter2 = map.alter("c") { maybeValue =>
maybeValue
.some(v => some(v * 10))
.none(some(3))
}
assertEquals(Map("a" -> 1, "b" -> 20), mapAfterAlter1)
assertEquals(Map("a" -> 1, "b" -> 2, "c" -> 3), mapAfterAlter2)
10.2. intersectWith
✅ 求两个 Map 的交集,并对值进行合并:
val m1 = Map("a" -> 1, "b" -> 2)
val m2 = Map("b" -> 2, "c" -> 3)
val m3 = Map("a" -> 5, "b" -> 8)
assertEquals(Map("b" -> 4), m1.intersectWith(m2)(_ + _))
assertEquals(Map("b" -> 4), m1.intersectWith(m2)((v1, v2) => v1 * v2))
assertEquals(Map("a" -> -4, "b" -> -6), m1.intersectWith(m3)(_ - _))
10.3. mapKeys
✅ 对 Map 的 key 进行映射转换:
val m1 = Map("a" -> 1, "b" -> 2)
assertEquals(Map("A" -> 1, "B" -> 2), m1.mapKeys(_.toUpperCase))
10.4. unionWith
✅ 合并两个 Map,处理 key 冲突:
val m1 = Map("a" -> 1, "b" -> 2)
val m2 = Map("b" -> 2, "c" -> 3)
assertEquals(Map("a" -> 1, "b" -> 4, "c" -> 3), m1.unionWith(m2)(_ + _))
10.5. insertWith
✅ 插入键值对,处理冲突时合并值:
val m1 = Map("a" -> 1, "b" -> 2)
val insertResult1 = m1.insertWith("a", 99)(_ + _)
val insertResult2 = m1.insertWith("c", 99)(_ + _)
val expectedResult1 = Map("a" -> 100, "b" -> 2)
val expectedResult2 = Map("a" -> 1, "b" -> 2, "c" -> 99)
assertEquals(expectedResult1, insertResult1)
assertEquals(expectedResult2, insertResult2)
11. NonEmptyList
✅ NonEmptyList
保证列表不为空,避免运行时异常:
//wrap a value in a non-empty list
val nel1 = 1.wrapNel
assertEquals(NonEmptyList(1), nel1)
//standard apply
val nel2 = NonEmptyList(3, 4)
//cons approach
val nel3 = 2 <:: nel2
assertEquals(NonEmptyList(2, 3, 4), nel3)
//append
val nel4 = nel1 append nel3
assertEquals(NonEmptyList(1, 2, 3, 4), nel4)
12. Lens
✅ Lens 是一种用于处理嵌套不可变对象更新的函数式工具。通过 Lens,我们可以像操作顶层字段一样更新深层嵌套字段。
示例:用户对象更新
case class UserId(id: Long)
case class FullName(fname: String, lname: String)
case class User(id: UserId, name: FullName)
✅ 创建 Lens:
val userFullName = Lens.lensu[User, FullName](
(user, name) => user.copy(name = name),
_.name
)
val firstName = Lens.lensu[FullName, String](
(fullName, firstName) => fullName.copy(fname = firstName),
_.fname
)
✅ 链式操作:
val userFirstName = userFullName >=> firstName
val name = FullName(fname = "John", lname = "Doe")
val userId = UserId(10)
val user = User(userId, name)
val updatedName = FullName("Jane", "Doe")
val actual = userFirstName.set(user, "Jane")
assertEquals(User(userId, updatedName), actual)
13. 总结
本文介绍了 Scalaz 库的核心功能,包括类型安全比较、类型类、Option 增强、Map 操作、Lens 等。Scalaz 是函数式编程在 Scala 中的重要工具,值得深入学习。
源码可参考:GitHub 仓库