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.Orderingscala.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 已为 IntDoubleFloat 等标准类型提供了 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 提供了更多实用操作符,让代码更简洁:

✅ 使用 somenone 构造 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 仓库


原始标题:Comprehensive Guide to Null Safety in Kotlin

« 上一篇: Kotlin中的泛型