美文网首页 移动 前端 Python Android Java
Kotlin(三)代数数据类型和模式匹配

Kotlin(三)代数数据类型和模式匹配

作者: zcwfeng | 来源:发表于2020-12-20 17:24 被阅读0次

代数数据类型(Algebraic Data Type,ADT)。用密封类和数据类构建代数数据类型

3.1 代数数据类型

ADT 其实是一个组合类型。

高中课本我们接触过代数和几何学。一个公式a + 2 = c 把这类公式转换成为编程语言中的类型或者值,他们中的某种操作,会得到某种新的类型。

简单说:代数或者数字转换成类型,这种被我们代数或者数字转换成的类型以及通这些类型产生的新的类型,就叫做代数数据类型(ADT)

每种类型在实例化的时候,都有对应的值。Boolean可能有true和false两种类型的取值。如果我们将数字2与取值种类关联就叫做计数,同理Unit 表示只有一个实例,那么他的计数是1.

ADT 常见的两种类型: 积类型 和 和类型

积类型我们可以理解和乘法相似,是一种组合类型
Boolean 和 Unity 组合会产生两种类型。表示如下:

class BooleanProductUnit(a:Boolean,b:Unit){}

val b0 = BooleanProductUnit(true,Unit)
val b1 = BooleanProductUnit(false,Unit)

实际上这是一种product操作,积类型就是同时持有某些类型的类型

和类型 相当于加法
比如我们 定义enum 类型表示周

enum class Day{SUN,MON,TUE,WEN,THU,FRI,SAT}

每一天只能取一个值,合起来是7种取值

  • 和类型 是类型安全的。 他是一个闭环,事先知道可能取值,在使用时候不用担心出现非法情况
  • 和类型 是一种OR的关系,对比 积类型 是一种 AND 关系

虽然枚举类是和类型,但是功能单一,扩展性不强。但是密封类表达上有更好的表现

使用密封类或者说和表达式,在使用when表达式的时候不用考虑非法的情况,可以省略else。如果遗漏或者多添加了额外情况,编译器检测会帮助报错

fun schedule(day:Day):String = when(day){
    Day.SUN -> "fishing"
    Day.MON -> "work"
    Day.TUE -> "study"
    Day.WEN -> "library"
    Day.THU -> "writing"
    Day.FRI -> "appointment"
    Day.SAT -> "basketball"
}

另一个小例子,计算面积:圆形,三角形和长方形

sealed class Shape{
    class Circle(val radius:Double):Shape()
    class Rectangle(val width:Double,val height:Double):Shape()
    class Triangle(val base:Double,val height:Double):Shape()
}

fun getArea(shape:Shape):Double = when(shape){
    is Shape.Circle -> Math.PI * shape.radius
    is Shape.Rectangle -> shape.width * shape.height
    is Shape.Triangle -> shape.base * shape.height
} 

3.2 模式匹配

我们应该很熟悉Java或者JS等都有正则表达式。模式匹配和他很相似,而且不只有匹配正则表达式,还可以匹配其他表达式。这里面的表达式就是模式

一个数字,一个对象实例,凡是能求出特定值的组合,我们叫做表达式

class Pattern(val text:String)
val a= 1
val b =2
表达式:5,a+b,Pattern("hello"),a>b
  1. 常量模式:之前的Day和when组合的例子
  2. 类型模式:之前的shape图像面积计算Sealed和when结合
  3. 逻辑表达式模式:
fun logicPattern(a:Int)= when (a) {
    in 2..11 -> "a in 2 .. 11"
    else -> "not in pattern"
}
  1. 嵌套表达式模式

定义一个简单的整数表达式

sealed class Expr {
    data class Num(val alue: Int) : Expr()
    data class Operate(val optName: String, val left: Expr, val right: Expr) : Expr()
}

Num 表示某个整数,Operate 是一个树形结构,optName操作符+ ,- ,* ,/ 等。用来表示一些复杂表达式。

定义非常简单,但是具体逻辑实现起来很繁琐,无论是java还是,kotlin

------》 转移一下视线 对比下Scala 因为它对于模式匹配支持的更好,我们看下思路。
Scala 语法 模式匹配

// test
sealed trait Expr
case class Num(value:Int) extends Expr
case class Operate(optName:String,left:Expr,right:Expr) extends Expr

def simplifyExpr(expr:Expr) = expr match{
    // 0 + x
    case Operate ("+",Num(0),x) => x
    // x + 0
    case Operate ("+",x,Num(0)) => x
    case _ => expr
}

正常实例化一个对象

val expr = Expr.Operate("+", Expr.Num(0), x)

反向看一下

val ("+", Expr.Num(0), x) = expr

会想到之前元祖的解构,kotlin里前两个文章中提到的解构

所以,推导下来的结论就是:
模式匹配中的模式就是表达式,模式匹配要匹配的就是表达式。模式匹配的核心就是解构,就是反向构造表达式。

我们需要用when去写模式匹配,还有其他的我们之后在研究。

fun simplefyExpr(expr: Expr):Expr = when (expr) {
  is Expr.Num -> expr
  is Expr.Operate -> when(expr){
      Expr.Operate("+",Expr.Num(0),expr.right) -> expr.right
      Expr.Operate("+",expr.left,Expr.Num(0)) -> expr.left
      else -> expr
  }
}

如果我们要完成这个表达式

val express = Expr.Operate("+",Expr.Num(0),Expr.Operate("+",Expr.Num(0),Expr.Num(0)))

先看when用递归和非递归实现

fun simplefyExpr2(expr: Expr):Expr = when (expr) {
    is Expr.Num -> expr
    is Expr.Operate -> when(expr){
        Expr.Operate("+",Expr.Num(0),expr.right) -> simplefyExpr2(expr.right)
        Expr.Operate("+",expr.left,Expr.Num(0)) -> expr.left
        else -> expr
    }
}

实际业务没有这么对称,而且我们指讨论“+” 还是有些复杂的

非递归方式

fun simplefyExpr3(expr: Expr): Expr = when (expr) {
    is Expr.Num -> expr
    is Expr.Operate -> when (expr) {
        (expr.left is Expr.Num && expr.left == 0) && (expr.right is Expr.Operate)
        -> when (expr.right) {
            Expr.Operate("+", expr.right.left, Expr.Num(0))
            -> expr.right.left
            else -> expr.right
        }
        else -> expr
    }
}

增强模式匹配

实现模式匹配的技术
类型测试或类型转换,面向对象的分解,访问者设计模式,TypeCase,样本类,抽取器。

前三种目前还有点搞头,后面三个不是考虑的范畴了

类型测试或类型转换---> 前面的Expr 的例子

面向对象的分解

sealed class Expr2 {
    abstract fun isZero(): Boolean
    abstract fun isAddZero(): Boolean
    abstract fun left(): Expr2
    abstract fun right(): Expr2


    data class Num(val value: Int) : Expr2() {
        override fun isZero(): kotlin.Boolean = this.value == 0

        override fun isAddZero(): Boolean = false

        override fun left(): Expr2 = throw Throwable("no element")

        override fun right(): Expr2 = throw Throwable("no element")
    }

    data class Operate(val optName: String, val left: Expr2, val right: Expr2) : Expr2() {
        override fun isZero(): kotlin.Boolean = false

        override fun isAddZero(): Boolean = this.optName ==
                "+" && (this.left.isZero() || this.right.isZero())

        override fun left(): Expr2 = this.left

        override fun right(): Expr2 = this.right
    }
}

fun simplefyExpr3(expr: Expr2): Expr2 = when  {
    expr.isAddZero() && expr.right().isAddZero() && expr.right().left().isZero()
    -> expr.right().right()
    else -> expr

}

实际业务很复杂,如果我们需要将类的方法,在每个子类再重新实现一遍。代价很高。比较简单的 业务还凑合

访问者模式

改造上面的实现,增加一个Visitor类,实现访问者模式

sealed class Expr3 {
    abstract fun isZero(v: Visitor): Boolean
    abstract fun isAddZero(v: Visitor): Boolean
    abstract fun simplefyExpr(v: Visitor): Expr3


    data class Num(val value: Int) : Expr3() {
        override fun isZero(v: Visitor): kotlin.Boolean = v.matchZero(this)

        override fun isAddZero(v: Visitor): Boolean = v.matchAddZero(this)

        override fun simplefyExpr(v: Visitor): Expr3 =v.doSimplefyExpr(this)

    }

    data class Operate(val optName: String, val left: Expr3, val right: Expr3) : Expr3() {
        override fun isZero(v:Visitor): kotlin.Boolean = v.matchZero(this)

        override fun isAddZero(v:Visitor): Boolean = v.matchAddZero(this)
        
        override fun simplefyExpr(v: Visitor): Expr3 = v.doSimplefyExpr(this,v)
    }
}


class Visitor {
    fun matchAddZero(expr: Expr3.Num): Boolean = false
    fun matchAddZero(expr: Expr3.Operate): Boolean = when (expr) {
        Expr3.Operate("+", Expr3.Num(0), expr.right)
        -> true
        Expr3.Operate("+", expr.left, Expr3.Num(0))
        -> false
        else -> false
    }
    
    fun matchZero(expr:Expr3.Num):Boolean = expr.value == 0
    fun matchZero(expr:Expr3.Operate):Boolean = false
    
    fun doSimplefyExpr(expr:Expr3.Num):Expr3= expr
    fun doSimplefyExpr(expr:Expr3.Operate,v:Visitor):Expr3= when{
        (expr.right is Expr3.Num && v.matchZero(expr) && v.matchAddZero(expr.right))
                && (expr.right is Expr3.Operate && expr.right.left is Expr3.Num)
                && v.matchAddZero(expr.right.left)
        -> expr.right.left
        else -> expr
    }

}

采用访问者设计模式
1.我们类中的设计方法,方法放到了类外实现,让类机构更清晰
2.子类特别多并且结构复杂 时候,访问者可以减少我们写很多判断逻辑,特定子类进行相关操作,会方便一些
3.缺点,新增加子类,在访问类中增加这个子类的操作,而且一些测试方法可能要重写,所以Visitor模式也要慎用

访问者设计及模式+when结合----》{数据结构在后期不会变动太大,业务逻辑相对复杂的情况}

相关文章

网友评论

    本文标题:Kotlin(三)代数数据类型和模式匹配

    本文链接:https://www.haomeiwen.com/subject/jrxknktx.html