Kotlin(十七)函数式编程<2>

实现Typeclass

Java中常见的一阶参数多态,这是我们经常说的泛型。利用泛型多态,在很大程度上能减少大量相同代码,但是需要高阶抽象的时候,还是需要很多冗余代码。

如标准库List,Set 等都实现了Iterabale接口,他们都有相同的方法,如filter,remove .

interface Iterable<T> {
    fun filter(p:(T)->Boolean):Iterator<T>
    fun remove(p:(T)->Boolean):Iterator<T> = filter{x->!p(x)}
}
interface List<T>:Iterable<T>{
    override fun filter(p: (T) -> Boolean): Iterator<T>
    override fun remove(p: (T) -> Boolean): Iterator<T> = filter{x->!p(x)}
}
interface Set<T>:Iterable<T>{
    override fun filter(p: (T) -> Boolean): Iterator<T>
    override fun remove(p: (T) -> Boolean): Iterator<T> = filter{x->!p(x)}
}

我们需要用高阶类型消除泛型设计冗余

高阶类型:用类型构造新类型

我们要了解什么是“类型构造器”。

通常我们熟悉的是“值构造器",给一个函数传递一个参数值,构造出来一个新的值。

(x:Int)->x

类型构造器,可以传递一个类型变量,然后构造出一个新的类型。
比如List[T],当我们传入Int,就可以构造出List<Int>

  • 一阶值构造器: 通过传入一个具体的值,然后构造出另一个具体的值。
  • 一阶类型构造器:通过一个具体的类型变量,然后构造出另一个具体类型

假设有一个类型构造器Container<X>,我们就可以消除泛型上面例子的冗余

interface Iterable<T,Container<X>> {
    fun filter(p:(T)->Boolean):Container<T>
    fun remove(p:(T)->Boolean):Container<T> = filter{x->!p(x)}
}
interface List<T>:Container<T,List>
interface Set<T>:Iterable<T,Set>

高阶类型和Typeclass

所谓的高阶函数,Kotlin并没有真正的支持,但是Kotlin的扩展语法可以替换Typeclass语言特性,代替高阶类型这个特性。

函子:高阶类型之间的映射。

由于函数式编程非常类似数学领域,是范畴论的理论思想。不必理会这些专业术语。

理解函子可以理解,高阶类型的参数之间的映射。

目前我们还不知道Kotlin如何支持,先看看Scala的支持例子,在考虑Kotlin的方式

trait Functor[F[_]]{
  def fmap[A , B](fa : F[A],f : A => B): F[B]
}
  1. trait 相当与kotlin中的interface,Functor支持高阶类型传入F,也是一个高阶类型
  2. Functor 实现了fmap方法,接收一个类型F[A]的变量fa,以及一个函数f,通过函数f我把fa中的A映射为B,fmap的返回类型F[B]

应用例子,把List[T] 集成Functor

implicit val listFunctor = new Functor[List]{
  def fmap(fa:List[A])(f:A=>B) = fa.fmap(f)
}

Kotlin 用扩展方法实现Typeclass

前面铺垫这么久就是为了理解Kotlin的用法

我们还用Functor这个例子模拟

// 模拟高阶类型
interface Kind<out F, out A>
interface Functor<F> {
    fun <A, B> Kind<F, A>.map(f: (A) -> B): Kind<F, B>
}

构造器F和应用类型A产生新的类型

自定义一个List类型,去应用

sealed class List<out A> : Kind<List.K, A> {
    object K
}
object Nil : List<Nothing>()
data class Cons<A>(val head: A, val tail: List<A>) : List<A>()

List 两种状态,一种Nil空列表,另一种Const由head和tail连接成的列表

List 实现了Kind<List.K,A>,带入上面的Kind的定义,我们得到List<A>是类构造器List.K应用类型参数A之后得到的类型。

由此我们可以用List.K 代表List这个高阶类型

@Suppress("UNCHECK_CAST","NOTHING_TO_INLINE")
inline fun <A> Kind<List.K, A>.unwrap(): List<A> = this as List<A>

object ListFunctor : Functor<List.K> {
    override fun <A, B> Kind<List.K, A>.map(f: (A) -> B): Kind<List.K, B> {
        return when (this) {
            is Cons -> {
                val t = this.tail.map(f).unwrap()
                Cons<B>(f(this.head), t)
            }
            else -> Nil
        }
    }

}

我们无法直接将Kotlin的object的扩展方法导入
XXXX错误XXXXX

import ListFunctor.*
Cons(1,Nil).map{it+1}

Kotlin中的receiver机制可以将object成员引入作用域,我们使用run函数即可

ListFunctor.run{
  Cons(1,Nil).map{it+1}
}

可以绕回前面文章,看下
run,also,let,takeIf,apply 函数介绍
Kotlin(六)多态和扩展

Typeclass 设计常见功能

  • 利用类型的扩展语法定义通用的Typeclass接口
  • 通过object定义预提类型的Typeclass
  • 在实例函数run的闭包中,目标类型对象或值就支持Typeclass类型
    实现几个例子
  1. Eq
interface Eq<F> {
    fun F.eq(that: F): Boolean
}
object IntEq : Eq<Int> {
  override fun Int.eq(that: Int): Boolean {
    return this == that
  }
}

IntEq.run {
                val a = 1
                println(a.eq(1))
                println(a.eq(2))
            }

Eq的高阶支持。利用之前的Functor,实现ListEq

abstract class ListEq<A>(val a: Eq<A>) : Eq<Kind<List.K, A>> {
    override fun Kind<List.K, A>.eq(that: Kind<List.K, A>): Boolean {
        val curr = this
        return if (curr is Cons && that is Cons) {
            val headEq = a.run {
                curr.head.eq(that.head)
            }
            if (headEq) curr.tail.eq(that.tail) else false
        } else curr is Nil && that is Nil
    }

}

IntListEq.run {
                val a = Cons(1, Cons(2, Nil))
                println(a.eq(Cons(1, Cons(2, Nil))))
                println(a.eq(Cons(1, Nil)))
            }
  1. Show 和 Fodable
    类似Java的toString方法,我们实现一个Show展示详细信息的Typeclass
interface Show<F> {
    fun F.show(): String
}

class Book(val name: String)

object BookShow : Show<Book> {
    override fun Book.show(): String = this.name
}

BookShow.run {
                println(Book("David is studing dive into kotloin").show())
            }

如果让List像Eq一样支持Show如何做?需要讲元素打印出来在拼装,List类型增加一个fold操作

设计一个Fodable 的Typeclass类型

interface Fodable<F> {
    fun <A, B> Kind<F, A>.fold(init: B): ((B, A) -> B) -> B
}

//inline fun <A> Kind<List.K,A>.unwrap():List<A> = this as List<A>
object ListFoldable : Fodable<List.K> {
    override fun <A, B> Kind<List.K, A>.fold(init: B): ((B, A) -> B) -> B = { f ->
        fun fold0(l: List<A>, v: B): B {
            return when (l) {
                is Cons ->
                    fold0(l.tail, f(v, l.head))
                else -> v

            }
        }
        fold0(this.unwrap(), init)
    }

}

abstract class ListShow<A>(val a: Show<A>) : Show<Kind<List.K, A>> {
    override fun Kind<List.K, A>.show(): String {
        val fa = this
        return "[" +
                ListFoldable.run {
                    fa.fold(listOf<String>())({ r, i ->
                        r + a.run { i.show() }
                    }).joinToString() + "]"
                }
    }
}

object BookListShow : ListShow<Book>(BookShow)

Foldable 为Kind<List.K,A> 类型扩展了fold操作,所以可以实现ListShow,类似ListEq这里需要Foldable的额外辅助操作

测试

BookListShow.run {
                println(
                    Cons(
                        Book("David study dive into kotlin"),
                        Cons(Book("Thinking in Java"), Nil)
                    ).show()
                )
            }

结果打印:
[David study dive into kotlin, Thinking in Java]
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容