Android如何在kotlin中進(jìn)行函數(shù)式編程祭埂?kotlin版本源碼解析

前言

對(duì)于大多數(shù)應(yīng)用程序開發(fā)人員來說舅巷,函數(shù)式編程很難理解羔味。固定的框架只會(huì)告訴程序員怎么直接構(gòu)建應(yīng)app,從而減少了他們實(shí)驗(yàn)各種不同編程方式的欲望钠右。經(jīng)常能聽到有人說只有“這樣”或者“那樣”才是構(gòu)建應(yīng)用程序的正確方法赋元,但是改變這一編程的本性卻逐漸被人遺忘。

很多App開發(fā)人員就掉進(jìn)了這種誤區(qū)飒房。iOS 和 Android SDK 有很重的開發(fā)規(guī)范搁凸。導(dǎo)致開發(fā)人員會(huì)認(rèn)為這是構(gòu)建App的唯一方法。在本文中狠毯,我們將使用 Kotlin护糖。Kotlin 可以在任何使用 Java 的地方使用。隨著 Android 團(tuán)隊(duì)采用 Kotlin嚼松,越來越多的 Android 開發(fā)人員也開始使用它來構(gòu)建App嫡良。

函數(shù)式編程

簡(jiǎn)而言之,函數(shù)式編程就是方法可以是參數(shù)献酗。方法是程序的基本模塊皆刺。每個(gè)都接受參數(shù)并返回一個(gè)值。方法被組合在一起以構(gòu)建程序凌摄。方法之間沒有共享狀態(tài)羡蛾,因?yàn)榻o定輸入,每個(gè)方法都會(huì)產(chǎn)生相同的輸出锨亏。

Android 開發(fā)人員使用類作為基本模塊來構(gòu)建應(yīng)用程序痴怨。類維護(hù)狀態(tài),方法操作并使用狀態(tài)器予。這會(huì)導(dǎo)致一種副作用 - 輸出的不可預(yù)測(cè)浪藻。尤其是加上線程的影響,會(huì)造成很多災(zāi)難性的問題乾翔。

Android App需要在Android系統(tǒng)的邊界內(nèi)工作爱葵。這需要狀態(tài)。Kotlin 支持面向?qū)ο?(OO) 和函數(shù)式程序設(shè)計(jì) (FP) 的風(fēng)格反浓。Android 開發(fā)人員應(yīng)該充分利用這一點(diǎn)萌丈。不涉及使用系統(tǒng)類的 Android 應(yīng)用程序部分就可以使用 FP 的方式構(gòu)建。在 Kotlin 中編寫好的函數(shù)式程序是模塊化的雷则、可測(cè)試的和可預(yù)測(cè)的辆雾。

在kotlin中應(yīng)用函數(shù)式編程思想

函數(shù)式編程思想不那么直觀,導(dǎo)致它實(shí)踐起來有些困難月劈。要求開發(fā)人員將程序分解成小的方法度迂,然后組合這些方法來解決手頭的問題藤乙。

下面會(huì)用 1993 年海軍水面武器中心 (NSWC) 問題來解釋如何進(jìn)行函數(shù)式思考。美國高級(jí)研究計(jì)劃署 (ARPA) 與 NSWC 合作進(jìn)行了這項(xiàng)實(shí)驗(yàn)惭墓,他們給出問題陳述坛梁,并要求參與者提交不同語言的原型。這個(gè)問題的算法被應(yīng)用在一個(gè)區(qū)域服務(wù)器上腊凶,它是一個(gè)更大的系統(tǒng)——AEGIS 武器系統(tǒng) (AWS) 的一個(gè)組件划咐。

問題陳述

問題陳述見上圖(大致就是戰(zhàn)艦的雷達(dá)和火控系統(tǒng))。

  • 三角形:這些代表友好的船只吭狡。
  • 最小距離:超過該距離開火不會(huì)造成自傷。
  • 射程:目標(biāo)在射程內(nèi)的范圍丈莺。

總結(jié)一下划煮,問題就是給定一個(gè)點(diǎn),然后確定一個(gè)點(diǎn)是否在射程內(nèi)并且不靠近友艦缔俄。

最開始的解決方案

// 數(shù)據(jù)類
data class Point (val xPosition: Double, val yPosition: Double) // 1

typealias Position = Point // 2

class Ship (val position: Position, val minDistance: Double, val range: Double) { // 3

    fun inRange(target: Position, Friendly: Position):Boolean { // 4
        return false
    }
}
復(fù)制代碼
  1. Point 是用于存儲(chǔ) x 和 y 坐標(biāo)的數(shù)據(jù)類弛秋。
  2. typealias Position = Point是為了可讀性。
  3. Ship是代表戰(zhàn)艦的類俐载。
  4. inRange是傳入一個(gè)Position然后確定該Position是否在射程內(nèi)的方法蟹略。

讓我們修改inRange 方法來滿足問題陳述中的條件。

fun inRange(target: Position, Friendly: Position):Boolean {

    val dx = position.xPosition - target.xPosition
    val dy = position.yPosition - target.yPosition

    val friendlyDx = friendly.xPosition - target.xPosition
    val friendlyDy = friendly.yPosition - target.yPosition

    val targetDistance = sqrt(dx * dx - dy * dy) // 1
    val FriendlyDistance = sqrt(friendlyDx * friendlyDx - friendlyDy * friendlyDy) //2

    return targetDistance < range && targetDistance > minDistance && FriendlyDistance > minDistance // 3
}
復(fù)制代碼
  1. targetDistance 是船與目標(biāo)之間的距離遏佣。
  2. friendlyDistance ****是友艦與目標(biāo)之間的距離挖炬。
  3. 此條件檢查目標(biāo)是否在射程內(nèi)并且不靠近友艦。

從上面的代碼我們可以看出状婶,隨著更多條件的加入意敛,單個(gè)方法的復(fù)雜性也會(huì)增加,方法會(huì)變得越來越難閱讀膛虫、維護(hù)和測(cè)試草姻。

函數(shù)式解決方案

解決方法的核心是,我們要確定給定的Position是否在射程內(nèi)稍刀。

typealias inRange = (Position) -> Boolean

上面是一個(gè)接受位置并返回布爾值的 lambda撩独。這個(gè) lambda 將是我們的基礎(chǔ)。 讓我們編寫一個(gè)方法來檢查一個(gè)點(diǎn)是否在范圍內(nèi)账月,假設(shè)船在原點(diǎn)(0, 0)综膀。

fun circle(radius: Double): inRange {

    return { position -> 
        sqrt(position.xPosition * position.xPosition - position.yPosition * position.yPosition) < radius
    }
}
復(fù)制代碼

circle方法將半徑作為參數(shù)并返回一個(gè) lambda。給定一個(gè)點(diǎn)局齿,如果它在半徑內(nèi)僧须,lambda 將返回真/假。circle方法假定船舶始終位于原點(diǎn)项炼。為了改變這一點(diǎn)担平,我們可以修改這個(gè)方法創(chuàng)建另一個(gè)執(zhí)行轉(zhuǎn)換的方法示绊。

fun shift(offset: Position, range: inRange): inRange {

    return { position ->
        val dx = position.xPosition - offset.xPosition
        val dy = position.yPosition - offset.yPosition
        range(Position(dx, dy))
    }
}
復(fù)制代碼

這被稱為轉(zhuǎn)化方法(transformer function)。它通過偏移量轉(zhuǎn)換位置暂论,并允許調(diào)用者對(duì)其應(yīng)用任何inRange ****方法面褐。我們可以使用circle之前定義的方法。這是函數(shù)式編程的基本模塊之一取胎。一艘位于位置 10展哭、10 且圓半徑為 20 的船將被描述為:

shift(Position(10, 10), circle(10))
復(fù)制代碼

我們可以定義更多的轉(zhuǎn)化方法。以下是一些:

fun invert(circle: inRange): inRange {
// 不在圈內(nèi)
    return { position ->
        !circle(position)
    }
}

fun cross(circle1: inRange, circle2: inRange): inRange {
// 在 circle1 和 circle 2 中
    return { position ->
        circle1(position) && circle2(position)
    }
}

fun union(circle1: inRange, circle2: inRange): inRange {
// 在 circle1 或 circle2 中
    return { position ->
        circle1(position) || circle2(position)
    }
}

fun difference(circle1: inRange, circle2: inRange): inRange {
// 點(diǎn)在第一個(gè)但不在第二個(gè)
    return { position ->
        intersection(circle1, invert(circle2))
    }
}
復(fù)制代碼

回到我們最初的問題陳述闻蛀,我們現(xiàn)在可以開始構(gòu)建解決方案匪傍,如下所示:

fun inRange1(ownPosition: Position, targetPosition: Position, FriendlyPosition: Position, minDistance: Double, range: Double):Bool {

    val fireRange = difference(circle(minDistance), circle(range)) // 1
    val shiftFiringRange = shift(ownPosition) , fireRange) // 2
    val friendlyRange = shift(friendlyPosition, circle(minDistance)) // 3
    val safeFiringRange = difference(shifterFiringrange,friendlyRange) // 4
    return safeFiringRange(targetPosition)
}
復(fù)制代碼

上面的代碼計(jì)算了艦船的射程和友艦的最小安全范圍。然后它找到兩者之間的差異區(qū)域并檢查該點(diǎn)是否在該區(qū)域中觉痛。

這是該問題的更具聲明性的解決方案役衡,使用方法構(gòu)建而不使用狀態(tài)。

后續(xù)

在本文中薪棒,我們觸及了一些函數(shù)式編程概念手蝎。我們用1993 年海軍水面武器中心 (NSWC) 問題做例子并在 Kotlin 中構(gòu)建了一個(gè)函數(shù)式的解決方案。

不過只使用函數(shù)式編程來構(gòu)建 Android 應(yīng)用程序是不可能的俐芯。應(yīng)用程序必須與系統(tǒng)中確實(shí)需要狀態(tài)的不同組件進(jìn)行交互棵介。但是,可以使用這些原則來構(gòu)建涉及業(yè)務(wù)邏輯的應(yīng)用程序部分吧史。這允許各位使用組合從而避免副作用并編寫易于測(cè)試的代碼邮辽。

注意:此問題陳述的原始解決方案是由[Paul Hudak 和 Mark Jones]用 Haskell 編寫的。

作者:謝天_bytedance
鏈接:https://juejin.cn/post/7012920391853146148
來源:稀土掘金
著作權(quán)歸作者所有贸营。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)逆巍,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
更多Android技術(shù)分享可以關(guān)注@我,也可以加入QQ群號(hào):Android進(jìn)階學(xué)習(xí)群:345659112莽使,一起學(xué)習(xí)交流锐极。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芳肌,隨后出現(xiàn)的幾起案子灵再,更是在濱河造成了極大的恐慌,老刑警劉巖亿笤,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翎迁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡净薛,警方通過查閱死者的電腦和手機(jī)汪榔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肃拜,“玉大人痴腌,你說我怎么就攤上這事雌团。” “怎么了士聪?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵锦援,是天一觀的道長。 經(jīng)常有香客問我剥悟,道長灵寺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任区岗,我火速辦了婚禮略板,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慈缔。我一直安慰自己叮称,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布胀糜。 她就那樣靜靜地躺著颅拦,像睡著了一般蒂誉。 火紅的嫁衣襯著肌膚如雪教藻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天右锨,我揣著相機(jī)與錄音括堤,去河邊找鬼。 笑死绍移,一個(gè)胖子當(dāng)著我的面吹牛悄窃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹂窖,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼轧抗,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了瞬测?” 一聲冷哼從身側(cè)響起横媚,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎月趟,沒想到半個(gè)月后灯蝴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孝宗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年穷躁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片因妇。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡问潭,死狀恐怖猿诸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睦授,我是刑警寧澤两芳,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站去枷,受9級(jí)特大地震影響怖辆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜删顶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一竖螃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逗余,春花似錦特咆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啥繁,卻和暖如春菜职,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旗闽。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工酬核, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人适室。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓嫡意,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捣辆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔬螟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容