Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum

2024-04-09 05:08

本文主要是介绍Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  Scalaz是由一堆的typeclass组成。每一个typeclass具备自己特殊的功能。用户可以通过随意多态(ad-hoc polymorphism)把这些功能施用在自己定义的类型上。scala这个编程语言借鉴了纯函数编程语言Haskell的许多概念。typeclass这个名字就是从Haskell里引用过来的。只不过在Haskell里用的名称是type class两个分开的字。因为scala是个OOP和FP多范畴语言,为了避免与OOP里的type和class发生混扰,所以就用了typeclass一个字。实际上scalaz就是Haskell基本库里大量typeclass的scala实现。

  在这篇讨论里我们可以通过介绍scalaz的一些比较简单的typeclass来了解scalaz typeclass的实现、应用方法以及scalaz函数库的内部结构。

  我们首先看看Equal:这是个比较典型的typeclass,适合用来介绍scalaz typeclass的一些实现方式、应用模式以及函数库结构。

我们知道,scalaz typeclass的几个重要元素就是:

1、特质 trait

2、隐式实例 implicit instances

3、方法注入 method injection

Equal Trait 在 core/.../scalaz/Equal.scala里,比较简单:

trait Equal[F]  { self =>def equal(a1: F, a2: F): Booleandef contramap[G](f: G => F): Equal[G] = new Equal[G] {def equal(a1: G, a2: G) = self.equal(f(a1), f(a2))}/** @return true, if `equal(f1, f2)` is known to be equivalent to `f1 == f2` */def equalIsNatural: Boolean = false
...


只要实现equal(a1,a2)这个抽象函数就可以了。Equal typeclass主要的功能就是对两个相同类型的元素进行等比。那和标准的 == 符号什么区别呢?Equal typeclass提供的是类型安全(type safe)的等比,在编译时由compiler发现错误,如下面的例子:

scala> 2 == 2.0
res3: Boolean = truescala> 2 === 2.0
<console>:14: error: type mismatch;found   : Double(2.0)required: Int2 === 2.0^

以上的 === 是Equal typeclass的符号方法(symbolic method),就是这个equal(a1,a2),是通过方法注入加入到Equal typeclass里的。我们可以看到equal对两个比对对象的类型要求是非常严格的,否则无法通过编译(除非在隐式作用域implicit scode内定义Double到Int的隐式转换implicit conversion)。

但是,在Equal Trait里的equal是个抽象函数(abstract function),没有实现。那么肯定在隐式作用域(implicit scope)里存在着隐式Equal实例。比如以上的例子我们应该试着找找Equal的Int实例。

我在scalaz.std/AnyValue.scala里发现了这段代码:

  implicit val intInstance: Monoid[Int] with Enum[Int] with Show[Int] = new Monoid[Int] with Enum[Int] with Show[Int] {override def shows(f: Int) = f.toStringdef append(f1: Int, f2: => Int) = f1 + f2def zero: Int = 0def order(x: Int, y: Int) = if (x < y) Ordering.LT else if (x == y) Ordering.EQ else Ordering.GTdef succ(b: Int) = b + 1def pred(b: Int) = b - 1override def succn(a: Int, b: Int) = b + aoverride def predn(a: Int, b: Int) = b - aoverride def min = Some(Int.MinValue)override def max = Some(Int.MaxValue)override def equalIsNatural: Boolean = true}


这是个Int实例。但好像没有继承Equal trait,因而也没有发现equal函数的实现。但是它继承了Enum。那么在scalaz/Enum.scala中的Enum trait是这样的:

trait Enum[F] extends Order[F] { self =>

Enum又继承了Order,再到scalaz/order.scala看看Order trait:

trait Order[F] extends Equal[F] { self =>def apply(x: F, y: F): Ordering = order(x, y)def order(x: F, y: F): Ordering def equal(x: F, y: F): Boolean = order(x, y) == Ordering.EQ

原来Order就是Equal,所以Enum就是Equal。equal(a1,a2)是在Order trait里用order(a1,a2)实现的,而order(a1,a2)是在Int隐式实例intInstance里实现了。这样就解决了隐式实例的问题,所以我们可以使用 2.===(2.0) >>> 2 === 2.0这样的语法。

我们再来看看方法注入是怎么实现的吧。Scalaz方法注入标准写法:放在scalaz/syntax/EqualSyntax.scala里:

/** Wraps a value `self` and provides methods related to `Equal` */
final class EqualOps[F] private[syntax](val self: F)(implicit val F: Equal[F]) extends Ops[F] {final def ===(other: F): Boolean = F.equal(self, other)final def /==(other: F): Boolean = !F.equal(self, other)final def =/=(other: F): Boolean = /==(other)final def ≟(other: F): Boolean = F.equal(self, other)final def ≠(other: F): Boolean = !F.equal(self, other)/** Raises an exception unless self === other. */final def assert_===[B](other: B)(implicit S: Show[F], ev: B <:< F) =if (/==(other)) sys.error(S.shows(self) + " ≠ " + S.shows(ev(other)))}

scalaz一般把字符方法(symbolic method)放在scalaz/syntax目录下。也就是 ===, =/=这两个操作符号,对应的是 ==, !=这两个标准操作符。注意这个符号方法容器类EqualOps需要一个隐式参数(implicit parameter)F: Equal[F],因为具体的equal(a1,a2)是在Equal[F]的实例里实现的。具体的方法注入黏贴还是通过隐式解析实现的:

trait ToEqualOps  {implicit def ToEqualOps[F](v: F)(implicit F0: Equal[F]) =new EqualOps[F](v)}

但是这个隐式转换ToEqualOps为什么是在trait里?隐式作用域必须是在某个object里的。我们再看看scalaz/syntax/syntax.scala里的这一段代码;

trait ToTypeClassOpsextends ToSemigroupOps with ToMonoidOps with ToEqualOps with ToShowOpswith ToOrderOps with ToEnumOps with ToPlusEmptyOpswith ToFunctorOps with ToContravariantOps with ToApplyOpswith ToApplicativeOps with ToBindOps with ToMonadOps with ToComonadOpswith ToBifoldableOps with ToCozipOpswith ToPlusOps with ToApplicativePlusOps with ToMonadPlusOps with ToTraverseOps with ToBifunctorOpswith ToBitraverseOps with ToComposeOps with ToCategoryOpswith ToArrowOps with ToFoldableOps with ToChoiceOps with ToSplitOps with ToZipOps with ToUnzipOps with ToMonadTellOps with ToMonadListenOps with ToMonadErrorOpswith ToFoldable1Ops with ToTraverse1Ops with ToOptionalOps with ToCatchableOps with ToAlignOps

trait ToTypeClassOps继承了ToEqualOps。然后在scalaz/Scalaz.scala里:

object Scalazextends StateFunctions        // Functions related to the state monadwith syntax.ToTypeClassOps    // syntax associated with type classeswith syntax.ToDataOps         // syntax associated with Scalaz data structureswith std.AllInstances         // Type class instances for the standard library typeswith std.AllFunctions         // Functions related to standard library typeswith syntax.std.ToAllStdOps   // syntax associated with standard library typeswith IdInstances              // Identity type and instances

object Scalaz继承了ToTypeClassOps。这样ToEqualOps的隐式作用域就在object Scalaz里了。

为了方便使用,Equal typeclass提供了构建函数:

  def equal[A](f: (A, A) => Boolean): Equal[A] = new Equal[A] {def equal(a1: A, a2: A) = f(a1, a2)}

我们可以这样构建Equal实例:

scala> case class Person(name: String, age: Int)
defined class Person
scala> implicit val personEqual: Equal[Person] = Equal.equal{(a,b) => a.name == b.name && a.age == b.age}
personEqual: scalaz.Equal[Person] = scalaz.Equal$$anon$7@7e5716escala> Person("Jone",23) === Person("Jone",23)
res0: Boolean = truescala> Person("Jone",23) === Person("Jone",22)
res1: Boolean = falsescala> Person("Jone",23) === Person("John",23)
res2: Boolean = false

当然我们也可以通过实现抽象函数equal(a1,a2)函数的方式来构建Equal实例:

scala> implicit val personEqual = new Equal[Person] {| def equal(a1: Person, a2: Person): Boolean = a1.name == a2.name && a1.age == a2.age| }
personEqual: scalaz.Equal[Person] = $anon$1@247cc8fscala> Person("John",32) === Person("Joe",32)
res0: Boolean = falsescala> Person("John",32) === Person("John",32)
res1: Boolean = true

在Equal trait 里有个有趣的函数:

 def contramap[G](f: G => F): Equal[G] = new Equal[G] {def equal(a1: G, a2: G) = self.equal(f(a1), f(a2))}

从函数名称来看它是个逆变(contra)。把函数款式概括化如下:

def contramap[G](f: G => F): Equal[F] => Equal[G]

它的意思是说:如果提供G => F转换关系,就可以把Equal[F]转成Equal[G]。与正常的转换函数map比较:

def map[G](f: F => G): Equal[F] => Equal[G]

函数f是反方向的,因而称之逆变contramap。Equal的伴生对象提供了另外一个构建函数:

  def equalBy[A, B: Equal](f: A => B): Equal[A] = Equal[B] contramap f

equalBy的意思是:假如已经有了Equal[B]实例,如果能提供A => B得转换,就可以通过equalBy构建Equal[A]实例。

举例:case class MoneyCents(cents: Int)

我们有现成的Equal[Int]实例,只要能提供MoneyCents与Int之间的转换关系,我们就可以等比MoneyCents了:

scala> case class MoneyCents(cents: Int)
defined class MoneyCents
scala> def moneyToInt(m: MoneyCents): Int = m.cents * 100
moneyToInt: (m: MoneyCents)Intscala> implicit val moneyEqual: Equal[MoneyCents] = Equal.equalBy(moneyToInt)
moneyEqual: scalaz.Equal[MoneyCents] = scalaz.Order$$anon$7@138ad7f5scala> MoneyCents(120) === MoneyCents(120)
res2: Boolean = truescala> MoneyCents(122) === MoneyCents(120)
res3: Boolean = false

这个逆变在以上例子的主要用途是:我们知道如何等比Int,我们又可以提供MoneyCents和Int之间的转换关系,那么我们就可以构建Equal[MoneyCents]实例。

介绍了Equal typeclass的实现和应用原理后,解释其它的typeclass就简单许多了。

我们再看看Order typeclass:

Scalaz的Order tyeclass提供了一组操作符号:在scalaz/syntax/OrderSyntax.scala里

/** Wraps a value `self` and provides methods related to `Order` */
final class OrderOps[F] private[syntax](val self: F)(implicit val F: Order[F]) extends Ops[F] {final def <(other: F): Boolean = F.lessThan(self, other)final def <=(other: F): Boolean = F.lessThanOrEqual(self, other)final def >(other: F): Boolean = F.greaterThan(self, other)final def >=(other: F): Boolean = F.greaterThanOrEqual(self, other)final def max(other: F): F = F.max(self, other)final def min(other: F): F = F.min(self, other)final def cmp(other: F): Ordering = F.order(self, other)final def ?|?(other: F): Ordering = F.order(self, other)final def lte(other: F): Boolean = F.lessThanOrEqual(self, other)final def gte(other: F): Boolean = F.greaterThanOrEqual(self, other)final def lt(other: F): Boolean = F.lessThan(self, other)final def gt(other: F): Boolean = F.greaterThan(self, other)}

其中cmp(?|?)方法使用了Ordering类型。Ordering是另外一个typeclass: scalaz/Ordering.scala

object Ordering extends OrderingInstances with OrderingFunctions {case object LT extends Ordering(-1, "LT") { def complement = GT }case object EQ extends Ordering(0,  "EQ") { def complement = EQ }case object GT extends Ordering(1,  "GT") { def complement = LT }
}

主要定义了LT,EQ,GT三个状态。

我们应该尽量使用lt,lte,gt,gte来确保类型安全(让compiler来发现错误):

scala> 1 < 1.0
res4: Boolean = falsescala> 1 lt 1.0
<console>:21: error: type mismatch;found   : Double(1.0)required: Int1 lt 1.0^scala> 1 ?|? 1.0
<console>:21: error: type mismatch;found   : Double(1.0)required: Int1 ?|? 1.0^scala> 1 ?|? 2
res7: scalaz.Ordering = LTscala> 1 lt 2
res8: Boolean = true

与Equal typeclass 同样,如果我们需要在自定义的类型T上使用Order typeclass的话,有几种方法可以构建Order[T]:

1、实现Order trait抽象函数order(a1,a2),在scalaz/std/AnyValue.scala中的Int实例intInstance中是这样实现order(a1,a2)函数的:

    def order(x: Int, y: Int) = if (x < y) Ordering.LT else if (x == y) Ordering.EQ else Ordering.GT

我们可以在Person类型上使用Order:

scala> case class Person(name: String, age: Int)
defined class Personscala> implicit val personAgeOrder = new Order[Person] {| def order(a1: Person, a2: Person): Ordering = | if (a1.age < a2.age) Ordering.LT else if (a1.age > a2.age) Ordering.GT else Ordering.EQ| }
personAgeOrder: scalaz.Order[Person] = $anon$1@736d65e9
scala> Person("John",23) ?|? Person("Joe",24)
res11: scalaz.Ordering = LTscala> Person("John",23) lt  Person("Joe",24)
res12: Boolean = truescala> Person("John",23) gt  Person("Joe",24)
res13: Boolean = false

2、用object Order里的构建函数order[A](f: (A,A) => Ordering): Order[A]

scala> case class Meat(cat: String, weight: Int)
defined class Meat
scala> implicit val meatWeightOrder: Order[Meat] = Order.order(_.weight ?|? _.weight)
meatWeightOrder: scalaz.Order[Meat] = scalaz.Order$$anon$11@7401c09fscala> Meat("Pork",13) lt Meat("Pork",14)
res14: Boolean = truescala> Meat("Beef",13) gt Meat("Pork",14)
res15: Boolean = false


3、逆变构建函数orderBy:

scala> case class Money(amount: Int)
defined class Moneyscala> val  moneyToInt: Money => Int = money => money.amount
moneyToInt: Money => Int = <function1>scala> implicit val moneyOrder: Order[Money] = Order.orderBy(moneyToInt)
moneyOrder: scalaz.Order[Money] = scalaz.Order$$anon$7@3e3975d0scala> Money(20) lt Money(21)
res16: Boolean = truescala> Money(20) ?|? Money(12)
res17: scalaz.Ordering = GT


在使用逆变构建函数时我们不需要再考虑如何实现对两个对象值的对比来获取这个Ordering返回值,我们只知道Order[Int]实现了两个Int的对比就行了。

Show 是一个简单的typeclass。我们用Shows(T)来实现对类型T的字符描述:

在scalaz/Syntax/ShowSyntax.scala里的注入方法:

final class ShowOps[F] private[syntax](val self: F)(implicit val F: Show[F]) extends Ops[F] {final def show: Cord = F.show(self)final def shows: String = F.shows(self)final def print: Unit = Console.print(shows)final def println: Unit = Console.println(shows)}

我们用Show来描述Person类型:

scala> case class Person(name: String, age: Int)
defined class Person
scala> implicit val personShow: Show[Person] = Show.show {p => p.name + "," + p.age + " years old" }
personShow: scalaz.Show[Person] = scalaz.Show$$anon$4@1d80fcd3
res19: String = Harry,24 years oldscala> Person("Harry",24).shows
res20: String = Harry,24 years oldscala> Person("Harry",24).println
Harry,24 years old


Enum typeclass 提供了下面这些方法:

final class EnumOps[F] private[syntax](val self: F)(implicit val F: Enum[F]) extends Ops[F] {final def succ: F =F succ selffinal def -+-(n: Int): F =F.succn(n, self)final def succx: Option[F] =F.succx.apply(self)final def pred: F =F pred selffinal def ---(n: Int): F =F.predn(n, self)final def predx: Option[F] =F.predx.apply(self)final def from: EphemeralStream[F] =F.from(self)final def fromStep(step: Int): EphemeralStream[F] =F.fromStep(step, self)final def |=>(to: F): EphemeralStream[F] =F.fromTo(self, to)final def |->(to: F): List[F] =F.fromToL(self, to)final def |==>(step: Int, to: F): EphemeralStream[F] =F.fromStepTo(step, self, to)final def |-->(step: Int, to: F): List[F] =F.fromStepToL(step, self, to)}

下面是使用这些操作符号的例子:

scala> 'a' to 'e'
res22: scala.collection.immutable.NumericRange.Inclusive[Char] = NumericRange(a, b, c, d, e)scala> 'a' |-> 'e'
res23: List[Char] = List(a, b, c, d, e)scala> 'a' |=> 'e'
res24: scalaz.EphemeralStream[Char] = scalaz.EphemeralStreamFunctions$$anon$4@2f8a4dfdscala> 'a'.succ
res25: Char = b
scala> 'a' -+- 2
res26: Char = cscala> 'd' --- 2
res27: Char = b

Enum实例需要实现抽象函数succ,pred。下面是char裂隙Enum实例Enum[Char]的实现:在scalaz/std/AnyVal.scala里的char object

    def succ(b: Char) = (b + 1).toChardef pred(b: Char) = (b - 1).toCharoverride def succn(a: Int, b: Char) = (b + a).toCharoverride def predn(a: Int, b: Char) = (b - a).toChar

Enum trait如下;

trait Enum[F] extends Order[F] { self =>def succ(a: F): Fdef pred(a: F): F

Enum实例必须实现抽象函数succ,pred。除此之外由于Enum继承了Order,所以还必须实现Order trait的抽象函数order(a1,a2)。










这篇关于Scalaz(4)- typeclass:标准类型-Equal,Order,Show,Enum的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887242

相关文章

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

C 标准库 - `<float.h>`

C 标准库 - <float.h> 概述 <float.h> 是 C 标准库中的一个头文件,它定义了与浮点数类型相关的宏。这些宏提供了关于浮点数的属性信息,如精度、最小和最大值、以及舍入误差等。这个头文件对于需要精确控制浮点数行为的程序非常有用,尤其是在数值计算和科学计算领域。 主要宏 <float.h> 中定义了许多宏,下面列举了一些主要的宏: FLT_RADIX:定义了浮点数的基数。

Mysql BLOB类型介绍

BLOB类型的字段用于存储二进制数据 在MySQL中,BLOB类型,包括:TinyBlob、Blob、MediumBlob、LongBlob,这几个类型之间的唯一区别是在存储的大小不同。 TinyBlob 最大 255 Blob 最大 65K MediumBlob 最大 16M LongBlob 最大 4G

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

MyBatis 切换不同的类型数据库方案

下属案例例当前结合SpringBoot 配置进行讲解。 背景: 实现一个工程里面在部署阶段支持切换不同类型数据库支持。 方案一 数据源配置 关键代码(是什么数据库,该怎么配就怎么配) spring:datasource:name: test# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource# @需要修改 数据库连接及驱动u