您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页Scala 中 Type Class 实现的套路

Scala 中 Type Class 实现的套路

来源:二三娱乐
typeclass.png

Scala 中 Type Class 实现的套路

什么是Type Class

A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass,
that means that it supports and implements the behavior the typeclass describes. A lot of people
coming from OOP get confused by typeclasses because they think they are like classes in object
oriented languages. Well, they're not. You can think of them kind of as Java interfaces, only better.

TypeClassHaskell 语言里的概念,根据《Learn you a haskell》中的解释, TypeClass 是定义一些公共行
为的接口,=Java= 语言中最为接近的概念是 interface, Scala 语言中最为接近的概念是 trait.

Haskell 中,可以给任意类型实现任意 TypeClass, 而在 Java 中只能通过实现某个接口才能让 Class 具有接口定义
的行为, 如果要给某个类库的类型添加额外的行为,几乎是不可能的事情(通过代码生成技术可以).

为什么需要Type Class

TypeClass行为定义具有行为的对象 分离,更容易实现 DuckType; 同时, 在函数式编程中,通常将数据与行为
相分离,甚至是数据与行为按需绑定,已达到更为高级的组合特性.

Scala 中对 TypeClass的实现<a id="sec-1-3" name="sec-1-3"></a>

Scala 是基于JVM平台的语言,受JVM的限制,不具备像 Haskell 那样语言原生对 TypeClass 的支持, Scala 语言使用了
隐式转换的魔法,使之能够不那么完美地支持 TypeClass.

Scala 中实现 TypeClass 可总结为三板斧.

1. TypeClass Trait 定义

TypeClass定义即使用 trait 来定义相关行为接口,比如 半群 :

trait Semigroup[A] {
  def combine(a1: A, a2: A): A
}

2. 定义 TypeClass 隐式实例

针对需要实现 TypeClass 的隐式实例,比如我们实现 Int 加法半群,根据 Scalaimplicit 隐式的实现,会在伴生对象中
自动查找隐式实例,所以一般讲实例定义在 TypeClass 的伴生对象中,这样只要 import TypeClass 就可以获得隐式实例:

object Semigroup {
  implicit val intPlusInstance = new Semigroup[Int] {
    def combine(a1: Int, a2: Int): Int = a1 + a2
  }
}

一般地,还会在伴生对象中增加 apply 只能构造器来生成 TypeClass 实例,而 apply 方法可以省略,代码看起来会更加简洁:

object Semigroup {

  def apply[A](implicit instance: Semigroup[A]) = instance

  implicit val intPlusInstance = new Semigroup[Int] {
    def combine(a1: Int, a2: Int): Int = a1 + a2
  }
}

这样,我们就可以通过伴生对象,获取 TypeClass 实例,从而调用 TypeClass 的行为方法:

import Semigroup._

 2) // 3

3. 定义 TypeClass 语法结构

object Semigroup {

  def apply[A](implicit instance: Semigroup[A]) = instance

  implicit val intPlusInstance = new Semigroup[Int] {
    def combine(a1: Int, a2: Int): Int = a1 + a2
  }

  // 增加语法结构
  abstract class Syntax[A](implicit I: Semigroup[A]) {
    def a1: A
    def |+|(a2: A): A =  a2)
  }

  // 定义隐式转换方法,将
  implicit def to[A](target: A)(implicit I: Semigroup[A]): Syntax[A] = new Syntax[A] {
    val a1 = target
  }

}

定义完成后,我们就可以使用新的语法 |+| 来调用 Semigroup 这个 TypeClass 的方法了:

import Semigroup._

 2) // 3

// 使用语法操作符
1 |+| 2 // 3

改进,更简洁

将上述实现综合一下,实现一个 Semigroup TypeClass 需要以下代码:

trait Semigroup[A] {
  def combine(a1: A, a2: A): A
}

object Semigroup {

  def apply[A](implicit instance: Semigroup[A]) = instance

  implicit val intPlusInstance = new Semigroup[Int] {
    def combine(a1: Int, a2: Int): Int = a1 + a2
  }

  // 增加语法结构
  abstract class Syntax[A](implicit I: Semigroup[A]) {
    def a1: A
    def |+|(a2: A): A =  a2)
  }

  // 定义隐式转换方法,将
  implicit def to[A](target: A)(implicit I: Semigroup[A]): Syntax[A] = 
    new Syntax[A] {
      val a1 = target
    }

}

仔细分析,这里面有一些样板代码,比如伴生对象的 apply 方法、语法结构的类和隐式转换方法,
这对于所有的类型类都一样,都要写这些重复的代码。去掉这些样板代码,我们真正需要的是:

  1. TypeClass 的定义
  2. TypeClass 的类型实例
  3. TypeClass 的语法操作符

得益于 Scala 元编程的强大,我们可以通过 Macro 来自动生成这些样板代码。 Simulacrum
项目就是专注于此。

利用 Simulacrum, 我们可以将我们的代码改进为

import simulacrum._

@typeclass trait Semigroup[A] {
  @op("|+|") def combine(a1: A, a2: A): A
}

// 定义隐式实例
implicit val intPlusInstance = new Semigroup[Int] {
  def combine(a1: Int, a2: Int): Int = a1 + a2
}

// 使用
import Semigroup.ops._
1 |+| 2 // 3

参考资料

  1. Simulacrum:
  2. TypeClass in Cats:
  3. About Scala Macro:

Copyright © 2019-2025 yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务