Function

ฟังก์ชั่น

การประกาศฟังก์ชั่นใน Swift จะมีหน้าตาประมาณนี้

func greet(person: String) -> String {
    return "Hello, " + person + "!"
}

เริ่มด้วย keyword func จากนั้นเป็นชื่อฟังก์ชั่น ตัวอย่างนี้คือ greet จากนั้นจะเป็นวงเล็บ ในวงเล็บจะเป็น argument ที่จะส่งให้กับฟังก์ชั่น ถ้าฟังก์ชั่นให้ผลลัพธ์ออกมา จะตามด้วยเครื่องหมาย -> แล้วตามด้วย Type ของผลลัพธ์จากฟังก์ชั่น

ในฟังก์ชั่น เราใช้ keyword return ในการคืนผลลัพธ์ออกจากฟังก์ชั่น

วิธีการเรียกใช้

let greetingMessage = greet(person: "pop")

ผลลัพธ์เก็บใน greetingMessage จากการเรียกฟังก์ชั่น greet ส่ง "Pop" เป็น input argument person ของฟังก์ชั่น

โดยปกติเวลาเขียน function signature แบบย่อ ๆ ของฟังก์ชั่นนี้เราจะเขียนได้ ประมาณนี้ greet(person:)

ฟังก์ชั่นสามารถส่งออกผลลัพธ์ได้แค่ Type เดียว ถ้าต้องการหลายผลลัพธ์เราอาจใช้ Tuple ช่วยได้แบบนี้

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")

ฟังก์ชั่นในภาษา Swift สามารถมีชื่อตัวแปรได้สองชื่อต่อหนึ่งตัว นั่นคือ

  1. Argument label เป็น label ตอนเรียกใช้งานจากภายนอกฟังก์ชั่น

  2. Parameter name ใช้งานภายในฟังก์ชั่น

(ในกรณีที่มีชื่อเดียว จะทำหน้าที่เป็นทั้ง argument label และ parameter name แบบฟังก์ชั่นที่เราเขียนกันไปแล้ว)

หน้าตาจะประมาณนี้

func someFunction(argumentLabel parameterName: Int) {

}

เช่น

func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)! from \(hometown)."
}

จะเห็นว่า argument ที่สองของฟังก์ชั่นมี argument label ว่า from และมี parameter name ว่า hometown เวลาเรียกฟังก์ชั่นจากภายนอกจะเป็นแบบนี้

greet(person: "John", from: "Bangkok")

ทำให้เราอ่านแล้วลื่นไหล ทั้งจากโค้ดที่เรียกฟังก์ชั่นและโค้ดที่อยู่ในฟังก์ชั่น เพราะบริบทต่างกัน

เวลาเขียน signature จะหน้าตาแบบนี้ greet(person:from:)

ในบางกรณีเราต้องการให้เรียกโดยไม่ต้องระบุ label ให้เรากำหนด argument label เป็น _ แทน เช่น

func append(_ item: String) {
}

append("Pop")

ถึงแม้ว่าจะมี label กำกับแต่ละ argument แล้ว เวลาเรียกก็ยังต้องเรียกแต่ละ argument ตามลำดับที่ประกาศ ไม่สามารถสลับได้

เราสามารถกำหนด default value ให้กับ argument ได้โดยการกำหนดค่าในตอนประกาศฟังก์ชั่นแบบนี้

func displayItem(name: String, isDone: Bool = false) {
    print("Item name: \(name), isDone: \(isDone)")
}

displayItem(name: "Learn DDD", isDone: true)
displayItem(name: "Learn iOS")

จะเห็นว่าเมื่อเรากำหนด default ให้แล้ว เราสามารถละการส่ง argument นั้นได้ตอนเรียกใช้ฟังก์ชั่น

ฟังก์ชั่นใน Swift ก็เป็น Type

func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)! from \(hometown)."
}

ฟังก์ชั่นนี้จะมี type เป็นแบบนี้

(String, String) -> String

เนื่องจากฟังก์ชั่นใน Swift เป็น Type เราสามารถประกาศตัวแปรที่มี Type เป็น function ได้ เช่น

var hello: (String, String) -> String = greet
// can also use like this
// var hello: (String, String) -> String = greet(person:from:)

hello("Pop", "Bangkok")

การใช้ Function Type มีข้อจำกัดอยู่ตรงที่ Function Type จะไม่สามารถมี Argument label ได้ จากตัวอย่างก่อนหน้า จะเห็นว่าเราเรียก hello("Pop", "Bangkok") โดยไม่มีการระบุ argument label สำหรับแต่ละ input

เหตุผลที่ทำให้ไม่สามารถใช้ argument labels กับ function types ดูเพิ่มเติมได้ที่ Swift evolution: 0111

และด้วยความที่ฟังก์ชั่นก็เป็น Type ทำให้เราสามารถส่ง ฟังก์ชั่นเป็น input ของฟังก์ชั่นได้ด้วย เช่น

func download(url: String, completion: (String) -> ()) {
    completion(url)
}

func display(text: String) {
    print(text)
}

download(url: "mock.com", completion:display)

รวมทั้งคืนผลลัพธ์เป็นฟังก์ชั่นด้วย แบบนี้

func greeting(prefix: String) -> (String) -> () {
    func greet(name: String) {
        print("\(prefix) \(name)")
    }
    return greet
}

greeting(prefix: "hello")("Pop")
greeting(prefix: "hello")("John")

เราสามารถเขียนประกาศฟังก์ชั่นในฟังก์ชั่นได้

Closure

ถ้าอธิบายแบบง่าย ๆ คือ ฟังก์ชั่นคือ closure ที่มีชื่อ หรือก็คือ closure เป็น block ของโค้ดที่ไม่มีการประกาศชื่อนั่นเอง ตัวอย่างเช่น

func greeting(prefix: String) -> (String) -> () {
    return { name in
        print("\(prefix) \(name)")
    }
}

Closure คือส่วนนี้ เนื่องจาก greeting ได้ระบุ Type ของผลลัพธ์ไว้แล้วทำให้ name ใน closure สามารถ infer type เป็น String

{ name in
    print("\(prefix) \(name)")
}

โดยปกติ Closure หมายถึง block ของ code ที่ capture external variable อย่างเช่น ฟังก์ชั่นที่ใช้ตัวแปรที่อยู่นอกเหนือสโคปของฟังก์ชั่น และฟังก์ชั่นที่ไม่มีชื่อเราจะเรียกว่า Closure expression แต่เพื่อความสั้นเราจึงมักเรียกมันสั้น ๆ ว่า Closure

การลดรูปการเรียกใช้ฟังก์ชั่น ในกรณีที่ input เป็นฟังก์ชั่น

ตัวอย่างเช่น การเรียกฟังก์ชั่น index ของ array เพื่อหา index ของ item แรกที่ตรงกับเงื่อนไขที่ระบุ

ฟังก์ชั่นรับ input เป็นฟังก์ชั่นในการหา โดยวนเรียกฟังก์ชั่นนี้กับทีละ item ใน array โดยที่ถ้า item ไหนทำให้ฟังก์ชั่นที่เป็น input นี้ได้ผลลัพธ์เป็น true firstIndex จะให้ผลลัพธ์เป็น Index ของ item นั้น

items.firstIndex(where: { (item) -> Bool in
    return item == "water"
})

ถ้าฟังก์ชั่นนั้นมี statement เดียวที่เป็นผลลัพธ์ของฟังก์ชั่น เราสามารถเริ่มลดรูปด้วยการละ keyword return ได้

items.firstIndex(where: { (item) -> Bool in
    item == "water"
})

ตัด -> Bool ออก เนื่องจากเรารู้จากฟังก์ชั่น firstIndex แล้วว่าต้องรับ ฟังก์ชั่นที่ return Bool

items.firstIndex(where: { (item) in
    item == "water"
})

ตัด (item) in ออกแทนที่ parameter แรกที่รับ ด้วย $0

items.firstIndex(where: { $0 == "water" })

ตัด (where: ) ออก parameter สุดท้ายถ้ารับเป็นฟังก์ชั่น สามารถละได้

items.firstIndex { $0 == "water" }

Last updated