zhimoe

Captain your own Ship.


  • 首页

  • 归档

  • 编程

  • 翻译

  • 随想

  • 关于

  • 搜索

Scala Type Class

2019-03-31   |   编程  

scala type class notes:
关于scala type class非常好的文章

核心知识点

//scala没有专门的type class语法,而是借助trait + implicit + context bound来实现的,
//所以很多时候识别type class比较困难.
//type class 由三部分构成
//1. type class: 即下面的Show,定义一个行为toHtml.
//2. type class instances:希望实现toHtml方法的类型实例
//3. user interface: type class中伴生对象的同名方法或者隐式转换方法.

// type class
trait Show[A] {
  def toHtml(a: A): String
}

// 定义在伴生对象的好处就是implicit变量自动处于scope内
object Show {
  //利用伴生对象apply特点实现下面Show[A].toHtml调用方式,即隐藏implicit sh
  def apply[A](implicit sh: Show[A]): Show[A] = sh

  //如果没有apply,那么下面的toHtml需要一个隐式参数:
  //def toHtml[A](a: A)(implicit sh: Show[A]): String = sh.toHtml(a)
  //或者:
  //def toHtml[A: Show](a: A): String = implicitly[Show[A]].toHtml(a)


  //对外接口,提供toHtml("type")的调用形式
  def toHtml[A: Show](a: A) = Show[A].toHtml(a)
  
  //对外接口,通过隐式转换提供10 toHtml的调用形式
  implicit class ShowOps[A: Show](a: A) { // 惯例使用TypeCls+Ops
    def toHtml = Show[A].toHtml(a)
  }

  //为了避免运行开销,可以将Ops类定义为value class:
  //  implicit class ShowOps[A](val a: A) extends AnyVal {
  //    def toHtml(implicit sh: Show[A]) = sh.toHtml(a)
  //  }

  //上面两个对外接口都利用了伴生对象的apply方法和context bound

  //type class instance int
  implicit val intCanShow: Show[Int] =
    int => s"<int>$int</int>"

  //type class instance string
  implicit val stringCanShow: Show[String] =
    str => s"<string>$str</string>"
}

//use type class
import Show._
print(10 toHtml)
print(toHtml("type"))

若使用import Show._ 导入全部内容,则用户无法自己实现一些 type class instance则会覆盖默认实例导致歧义.
可以将对外接口移动到单独的ops对象中:

trait Show[A] {
  def toHtml(a: A): String
}

object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh

  object ops {
    def toHtml[A: Show](a: A) = Show[A].toHtml(a)

    implicit class ShowOps[A: Show](a: A) { // 惯例使用TypeCls+Ops
      def toHtml = Show[A].toHtml(a)
    }

  }

  implicit val intCanShow: Show[Int] = int => s"<int>$int</int>"
  implicit val stringCanShow: Show[String] = str => s"<string>$str</string>"
}

使用:

import xxx.Show //如果需要实现自定义的type class instance则需要
import xxx.Show.ops._

自定义

    case class Person(name: String, age: Int)
    implicit val personOps: Show[Person] = p => s"<person>name=${p.name},age=${p.age}</person>"

    print(Person("lee", 19) toHtml)//<person>name=lee,age=19</person>

Simulacrum

Simulacrum通过宏为 type class 添加便捷语法,是否使用取决于个人判断.
若使用 Simulacrum,则可以一眼找出代码中所有的 type class,并且可省去很多样板代码.
另一方面,使用 @typeclass(Simulacrum 主要注解)则意味着需要依赖 macro paradise 编译器插件.

使用 Simulacrum 重写我们的 Show type class:

import simulacrum._

@typeclass trait Show[A] {
  def toHtml(a: A): String
}

有了 Simulacrum,type class 定义变得非常简洁,我们在其伴生对象中添加 Show[String] 实例:

//Simulacrum 会为 Show 自动生成 ops 对象,与前面自定义的基本一致.
object Show {
  implicit val stringShow: Show[String] = s ⇒ s"String: $s"
}
#code #scala
Buy me a coffee
Pattern Matching Anonymous Function
Scala 学习笔记
评论加载中... 如果长时间无法加载,请针对 giscus.app 启用代理。
  • 文章目录
  • 站点概览
zhimoe

zhimoe

72 日志
4 分类
45 标签
GitHub ZhiHu
书签
  • 可视化Git
  • 绘画博物馆
  • Rust小抄
  • 谷歌机器学习
标签云
  • code
  • java
  • python
  • scala
  • rust
  • spring
  • qq空间
  • 并发
  • git
  • docker
  • aop
  • async
  • 核心知识点
  • 自定义
  • Simulacrum
2016 - 2023 zhimoe
0%