higher-order functions: 自訂 function 且使用 lambda 為 parameters 或是 return values
inline functions: 可減輕使用 lambda 的 performance overhead
宣告 higher-order functions
例如 list.filter { x > 0 }
以 predicate function 為參數,就是一種 higher-order function
Function types
要知道如何定義 lambda 為參數的 function
我們已經知道,再不需要定義 type 也能使用 lambda
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
compiler 是將程式轉換為
// 以兩個 Int 為參數,回傳 Int
val sum: (Int, Int) -> Int = { x, y -> x + y }
// 沒有參數,沒有 return value
val action: () -> Unit = { println(42) }
val sum: (Int, Int) -> Int
其中 (Int, Int)
是 parameter types,後面的 Int 是 return type
如果可以回傳 null,就這樣定義
var canReturnNull: (Int, Int) -> Int? = { null }
宣告為 funcation type with a nullable return type
var funOrNull: ((Int, Int) -> Int)? = null
callback 是有參數名稱的 function type
fun performRequest(
url: String,
callback: (code: Int, content: String) -> Unit
) {
/*...*/
}
fun main(args: Array<String>) {
val url = "http://kotl.in"
performRequest(url) { code, content -> /*...*/ }
performRequest(url) { code, page -> /*...*/ }
}
calling functions passed as arguments
twoAndTree 的參數中 operation: (Int, Int) -> Int
是 function type
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
fun main(args: Array<String>) {
twoAndThree { a, b -> a + b }
twoAndThree { a, b -> a * b }
}
另一個例子
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if ( predicate(element) ) sb.append(element)
}
return sb.toString()
}
fun main(args: Array<String>) {
println("ab1c".filter { it in 'a'..'z' })
}
fun String.filter(predicate: (Char) -> Boolean): String
- 前面的 String 是 receiver type
- predicate 是 parameter name
- (Char) -> Boolean 是 function type
Using function type from Java
kotlin 宣告的有 function type 為參數的 function
/* Kotlin declaration */
fun processTheAnswer(f: (Int) -> Int) {
println(f(42))
}
在 Java 可用 lambda 使用
public class ProcessTheAnswerLambda {
public static void main(String[] args) {
processTheAnswer(number -> number + 1);
}
}
舊版 java 要用 anonymous class
import static ch08.ProcessTheAnswer.ProcessTheAnswer.*;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
/* Java */
public class ProcessTheAnswerAnonymous {
public static void main(String[] args) {
processTheAnswer(
new Function1<Integer, Integer>() {
@Override
public Integer invoke(Integer number) {
System.out.println(number);
return number + 1;
}
});
}
}
在 java 使用 forEach
import java.util.ArrayList;
import java.util.Collections;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import java.util.List;
/* Java */
public class UsingForEach {
public static void main(String[] args) {
List<String> strings = new ArrayList();
strings.add("42");
CollectionsKt.forEach(strings, s -> {
System.out.println(s);
return Unit.INSTANCE;
});
}
}
default and null values for parameters with function types
先前 joinToString 的實作
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString())
println(letters.joinToString { it.toLowerCase() })
println(letters.joinToString(separator = "! ", postfix = "! ",
transform = { it.toUpperCase() }))
}
缺點是 transform 裡面永遠會使用 toString 轉換字串
修改為 nullable parameter of a function type
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
transform: ((T) -> String)? = null
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
val str = transform?.invoke(element)
?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString())
println(letters.joinToString { it.toLowerCase() })
println(letters.joinToString(separator = "! ", postfix = "! ",
transform = { it.toUpperCase() }))
}
returning functions from functions
計算 cost of shipping depending on the selected shipping method,實作 logic variant,回傳對應的 function
enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)
// 宣告回傳 function 的 function
fun getShippingCostCalculator(
delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPEDITED) {
// return lambdas from the function
return { order -> 6 + 2.1 * order.itemCount }
}
return { order -> 1.2 * order.itemCount }
}
fun main(args: Array<String>) {
val calculator =
getShippingCostCalculator(Delivery.EXPEDITED)
// 呼叫 returned function
println("Shipping costs ${calculator(Order(3))}")
}
另一個例子,GUI contract management application,需要根據 UI 狀態,決定要顯示哪些 contracts
data class Person(
val firstName: String,
val lastName: String,
val phoneNumber: String?
)
class ContactListFilters {
var prefix: String = ""
var onlyWithPhoneNumber: Boolean = false
// 宣告產生 function 的 function
fun getPredicate(): (Person) -> Boolean {
val startsWithPrefix = { p: Person ->
p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
}
if (!onlyWithPhoneNumber) {
return startsWithPrefix
}
return { startsWithPrefix(it)
&& it.phoneNumber != null }
}
}
fun main(args: Array<String>) {
val contacts = listOf(Person("Dmitry", "Jemerov", "123-4567"),
Person("Svetlana", "Isakova", null))
val contactListFilters = ContactListFilters()
with (contactListFilters) {
prefix = "Dm"
onlyWithPhoneNumber = true
}
println(contacts.filter(
contactListFilters.getPredicate()))
}
removing duplication through lambdas
analyzes visits to a website: SiteVisit 儲存 path of each visit, duration, OS
data class SiteVisit(
val path: String,
val duration: Double,
val os: OS
)
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }
val log = listOf(
SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
要計算windows 的平均使用時間,直接寫一個 function
val averageWindowsDuration = log
.filter { it.os == OS.WINDOWS }
.map(SiteVisit::duration)
.average()
fun main(args: Array<String>) {
println(averageWindowsDuration)
}
改寫,讓 averageDurationFor 可傳入 OS 參數
fun List<SiteVisit>.averageDurationFor(os: OS) =
filter { it.os == os }.map(SiteVisit::duration).average()
fun main(args: Array<String>) {
println(log.averageDurationFor(OS.WINDOWS))
println(log.averageDurationFor(OS.MAC))
}
同時計算 ios, android
val averageMobileDuration = log
.filter { it.os in setOf(OS.IOS, OS.ANDROID) }
.map(SiteVisit::duration)
.average()
fun main(args: Array<String>) {
println(averageMobileDuration)
}
以 function 動態調整過濾的條件
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
fun main(args: Array<String>) {
println(log.averageDurationFor {
it.os in setOf(OS.ANDROID, OS.IOS) })
println(log.averageDurationFor {
it.os == OS.IOS && it.path == "/signup" })
}
inline function: 減輕 lambda 的 overhead
how inlining works
宣告 function 為 inline,就表示會直接替代 code,而不是用呼叫 function 的方式
以下是 synchronized 的做法,鎖定 lock 物件,執行 action,最後 unlock
inline fun <T> synchronized(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
val l = Lock()
synchronized(l) {
// ...
}
因宣告為 inline,以下這些 code
fun foo(l: Lock) {
println("Before sync")
synchronized(l) {
println("Action")
}
println("After sync")
}
synchronized(l)
會轉換為
l.lock()
try {
println("Action")
} finally {
l.unlock()
}
inline function 的限制
並非每一個使用 lambdas 的 function 都可以改為 inline
當 function 為 inlined,body of the lambda expression 傳入當作參數,會直接替換為 inline function,如果 function 直接被呼叫,就可以 inline,但如果需要儲存 function,後面才要使用,就不能用 inline
inlining collection operations
kotlin 的 filter 是定義為 inline, map 也是,因為 inline,就不會產生額外的 classes or objects
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun main(args: Array<String>) {
println(people.filter { it.age < 30 })
println(people.filter { it.age > 30 }.map(Person::name))
}
什麼時候要用 inline functon
使用 inline 只會在 function 使用 lambda 為參數的狀況下,改進效能
JVM 有支援 inlining support,他會分析 code,並嘗試 inline code
除了避免產生多餘的 class for each lambda 及 object for the lambda instance,另外 JVM 還不夠聰明,能夠找出所有可以 inline 的狀況
Using inlined lambdas for resource management
另一個 lambda 能減少重複程式碼的狀況是 resource management,取得 Resource -> 使用 -> release
Resource 可能是 file, lock, database transaction ....
通常會用 try/finally statement 包裝起來
kotlint 提供 "withLock" function,可處理 synchronized 的工作
val l: Lock = ...
l.withLock {
// access the resource protected by this lock
}
withLock 是這樣實作的
fun <T> Lock.withLock(action: () -> T): T {
lock()
try {
return action()
} finally {
unlock()
}
}
java 提供 try-with-resources statement
/* Java */
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
kotlin 沒有對應的語法,但可以用 "use" 搭配 lambda 提供相同的功能
import java.io.BufferedReader
import java.io.FileReader
import java.io.File
fun readFirstLineFromFile(path: String): String {
BufferedReader(FileReader(path)).use { br ->
return br.readLine()
}
}
control flow in higher-order functions
return statements in lambdas: return from an enclosing function
在 list of Person 裡面尋找 Alice
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
for (person in people) {
if (person.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
將 for 改為 forEach,在 lambda function 中留下 return
,這個 return 是 non-local return,這種 return 只對 function 使用 inlined lambda function 為參數有作用
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
因為 forEach 的 lambda function 是用 inlined,因此可使用 return
returning form lambdas: return with label
local return 類似 break 的功能,會中斷 lambda 的執行,
區分 local return 與 non-local,可使用 labels,可將 lambda expression 加上 label,並在 return 時參考此 label
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach label@{
if (it.name == "Alice") {
println("Found!")
return@label
}
}
// 永遠會執行這一行
println("Alice might be somewhere")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
也可以用 lambda 做為 label
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
return@forEach
}
}
println("Alice might be somewhere")
}
fun main(args: Array<String>) {
lookForAlice(people)
}
也可以將 this 加上 label
fun main(args: Array<String>) {
// implicit receiver 為 this@sb
println(StringBuilder().apply sb@{
listOf(1, 2, 3).apply {
// this 參考到 closest implicit receiver in the scope
this@sb.append(this.toString())
}
})
}
anonymous functions: local returns by default
使用 anonymous function 替代 lambda expression
return 會參考到 closest functon: 也就是 anonymous function
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
people.forEach(fun (person) {
if (person.name == "Alice") return
println("${person.name} is not Alice")
})
}
fun main(args: Array<String>) {
lookForAlice(people)
}
另一個例子
people.filter(fun (person): Boolean {
return person.age < 30
})
people.filter(fun (person) = person.age < 30)
沒有留言:
張貼留言