Swift-基本語法-字面量協議-運算符重載-函數式編程的綜合實例

Swift相比於Objective-C的語法來說更加的酷炫,不過也更加的複雜的難懂,爲了將Swift的各類語法可以穿插起來,作一個綜合筆記,這裏實現一個應用多種Swift特性來實現的小功能。git

假設如今有一個零售水果的在線商店,須要頻繁的用到不一樣的重量單位,可是最終服務器都是以kg來計算和存儲,因此須要頻繁的將其餘的重量單位轉爲kg來作爲數據的存儲,又須要頻繁的將kg轉換成其餘的重量單位來做爲顯示。github

存儲屬性和計算屬性

這裏採用封裝爲一個結構體的方式實現:編程

struct Kilogram {
	var kg: Double
	var lb: Double {
		set {
			self.kg = newValue * 0.45359237
		}
		get {
			self.kg * 2.20462262
		}
	}

	init(_ kg: Double) {
		self.kg = kg
	}
	
	init() {
		self.kg = 0
	}
}
複製代碼

上面結構體中,只有kg是存儲屬性,而lb是計算屬性,這樣就實現咱們上面的需求:實現以kg爲基礎單位的存儲功能,同時實現其餘單位的換算。api

字面量協議

如今結構體能夠經過初始化方法初始化:bash

var w1 = Kilogram()
var w2 = Kilogram(2)
print(w1.kg, w1.lb) // 0.0 0.0 
print(w2.kg, w2.lb) // 2.0 4.40924524 
複製代碼

爲了更方便的直接經過字面量初始化,能夠實現對應類型的協議:服務器

struct Kilogram: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, ExpressibleByNilLiteral {
	var kg: Double
	var lb: Double {
		set {
			self.kg = newValue * 0.45359237
		}
		get {
			self.kg * 2.20462262
		}
	}
	
	init(_ kg: Double) {
		self.kg = kg
	}
	
	init() {
		self.kg = 0
	}
	
	init(floatLiteral value: Double) {
		self.kg = value
	}
	
	init(integerLiteral value: Int) {
		self.kg = Double(value)
	}
	
	init(stringLiteral value: String) {
		self.kg = Double(value) ?? 0
	}
	
	init(nilLiteral: ()) {
		self.kg = 0
	}
}

複製代碼

如今就能夠直接經過字面量初始化:app

var w1: Kilogram = nil
var w2: Kilogram = "1"
var w3: Kilogram = 2
var w4: Kilogram = 3

print(w1.kg, w2.kg, w3.kg, w4.kg) // 0.0 1.0 2.0 3.0
複製代碼

運算符重載

已知兩個Kilogram的實例,求他們的重量和,並得轉換爲lb是多少:函數式編程

var w1: Kilogram = 3
var w2: Kilogram = 4

var sum = w1.kg + w2.kg
var w3 = Kilogram(sum)
print(w3.kg, w3.lb)
複製代碼

Swift支持運算符重載,爲Kilogram結構體添加如下方法:函數

static func + (v1: Kilogram, v2: Kilogram) -> Kilogram {
		Kilogram(v1.kg + v2.kg)
	}
	
	static func - (v1: Kilogram, v2: Kilogram) -> Kilogram {
		Kilogram(v1.kg - v2.kg)
	}
	
	static func * (v1: Kilogram, v2: Kilogram) -> Kilogram {
		Kilogram(v1.kg * v2.kg)
	}
	
	static func / (v1: Kilogram, v2: Kilogram) -> Kilogram {
		Kilogram(v1.kg / v2.kg)
複製代碼

下面計算兩個Kilogram實例的和:ui

var w1: Kilogram = 3
var w2: Kilogram = 4

var w3 = w1 + w2
print(w3.kg, w3.lb) // 7.0 15.432358339999999
複製代碼

配合字面量協議支持複雜的運算:

var w1: Kilogram = 3
var w2: Kilogram = 4
var w3: Kilogram = 2

var w4 = w1 + w2 * w3 - 8 // 3 + 4 * 2 - 8 = 3
print(w4.kg, w4.lb) // 3.0 6.613867859999999
複製代碼

枚舉的關聯值

Swift的枚舉相比Objective-C增長很是多的新特性,關聯值就是其中之一,關鍵值能夠將枚舉的成員值和其餘類型的值關聯並存儲在一塊兒,一樣枚舉也支持範型、方法(包括初始化方法)、屬性(計算實例屬性、計算類型屬性、存書類型屬性)。

按照零售水果的開發需求,須要封裝一個水果類,這個水果類須要如下基本信息:名稱和單價,而因爲平臺稱重單位有兩種,因此單價便可能是kg也多是lb,這裏假設一種水果有且只有一種重量單位的計價方式。那麼這個類按照Objective-C的設計方式,起碼須要兩個屬性來表示單價:

typedef NS_ENUM(NSInteger, FruitPriceType) {
	FruitPriceByKg,
	FruitPriceByLb
};

@interface Fruit: NSObject

@property (nonatomic, assign) double price;
@property (nonatomic, assign) FruitPriceType priceType;

@end
複製代碼

經過Swift枚舉類型關聯值,只須要一個屬性就能夠實現:

class Fruit {
    enum Price<Double> {
        case kg(Double)
        case lb(Double)
    }
    
    var name: String
    var price: Price<Double>
    init(name: String, price: Price<Double>) {
        self.name = name
        self.price = price
    }
}
複製代碼

在須要計算總價時,只須要判斷枚舉的類型,取出對應關聯值便可:

var apple = Fruit(name: "🍎", price: .lb(3))
var appleWeight: Kilogram = 10

func originalPrice(_ fruit: Fruit, _ weight: Kilogram) -> Double{
    switch fruit.price {
    case let .kg(kg):
        return kg * weight.kg
    case let .lb(lb):
        return lb * weight.lb
    }
}

var price = originalPrice(apple, appleWeight)
print(price)  // 66.13867859999999
複製代碼

函數式編程

下面開始實現水果的總價計算,當用戶選擇了必定重量的水果,須要根據重量計算出須要支付的總價,假如計算總價的規則以下:

單價 * 重量 * 折扣率 - 優惠金額

這裏假設有以下幾個拆分的計算總價的分步方法:

/// 原價 = 單價 * 重量
func originalPrice(_ weight: Kilogram, _ fruit: Fruit) -> Double{
    switch fruit.price {
    case let .kg(kg):
        return kg * weight.kg
    case let .lb(lb):
        return lb * weight.lb
    }
}

/// 折扣價 = 原價 * 折扣率
func discountPrice(_ originPrice: Double, _ rate: Double) -> Double {
    return originPrice * rate
}

/// 最終價 = 折扣價 - 優惠金額
func reductionPrice(_ discountPrice: Double, _ reduction: Double) -> Double {
    return discountPrice - reduction
}
複製代碼

假如當前蘋果的單價爲3/lb,折扣爲0.9,優惠金額固定爲3,那麼10kg蘋果的價格計算以下:

var apple = Fruit(name: "🍎", price: .lb(3))
var appleWeight: Kilogram = 10

var price = reductionPrice(discountPrice(originalPrice(apple, appleWeight), 0.9), 3) // 56.52481073999999
複製代碼

這樣的方法調用會嵌套不少層,可讀性差,而且很難體現出計算過程。

下面將上面三個分步計算方法分別重寫爲如下三個方法:

func originalPrice(_ fruit: Fruit) -> (_ weight: Kilogram) -> Double {
    {
        weight in
        switch fruit.price {
        case let .kg(kg):
            return kg * weight.kg
        case let .lb(lb):
            return lb * weight.lb
        }
    }
}

func discountPrice(_ rate: Double) -> (_ originPrice: Double) -> Double {
    {
        originPrice in
        rate * originPrice
    }
}

func reductionPrice(_ reduction: Double) -> (_ discountPrice : Double) -> Double {
    {
        discountPrice in
        discountPrice - reduction
    }
}
複製代碼

將每個方法都重寫爲傳入一個參數,返回一個須要傳入一個參數而且有一個返回值的方法。

以originalPrice方法爲例:

func originalPrice(_ weight: Kilogram, _ fruit: Fruit) -> Double{
    switch fruit.price {
    case let .kg(kg):
        return kg * weight.kg
    case let .lb(lb):
        return lb * weight.lb
    }
}

func originalPrice(_ fruit: Fruit) -> (_ weight: Kilogram) -> Double {
    {
        weight in
        switch fruit.price {
        case let .kg(kg):
            return kg * weight.kg
        case let .lb(lb):
            return lb * weight.lb
        }
    }
}
複製代碼

原方法須要傳入水果實例和重量兩個參數,返回對應的原價。重寫後方法改成:傳入水果實例,返回一個函數,這個函數傳入重量,而且返回原價。這樣至關於將一個函數拆分爲兩步返回,第一步返回一個函數,由第一步返回的函數完成最終的函數功能:

var price = originalPrice(apple, appleWeight) // 66.13867859999999

var fn = originalPrice(apple)
price = fn(appleWeight) // 66.13867859999999
複製代碼

接下來,在建立一個自定義運算符,功能就是將兩個函數合併成一個函數:

infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B, _ f2: @escaping (B) -> C) -> (A) -> C {
    {
        a in
        f2(f1(a))
    }
}
複製代碼

將上面三個函數的計算過程,合併成一個函數:

var applePriceFuction = originalPrice(apple) >>> discountPrice(0.9) >>> reductionPrice(3)
複製代碼

接下來,計算10kg蘋果的重量,只須要一行代碼就能夠完成:

var price = applePriceFuction(10) // 56.52481073999999
複製代碼

柯里化

上面將三個函數分別重寫後的形式,就是函數的柯里化。這裏將上面三個函數和自定義運算符函數,分別作語法糖的代碼簡化:

func originalPrice(_ fruit: Fruit) -> (Kilogram) -> Double {
    {
        switch fruit.price {
        case let .kg(kg):
            return kg * $0.kg
        case let .lb(lb):
            return lb * $0.lb
        }
    }
}

func discountPrice(_ rate: Double) -> (Double) -> Double {{ rate * $0 }}

func reductionPrice(_ reduction: Double) -> (Double) -> Double {{ $0 - reduction }}

infix operator >>> : AdditionPrecedence
func >>><A, B, C>(_ f1: @escaping (A) -> B, _ f2: @escaping (B) -> C) -> (A) -> C { { f2(f1($0)) } }
複製代碼

上面三個函數其實目的都是將一個傳入兩個參數的函數,柯里化成傳入一個參數,返回一個須要一次參數的函數,而函數的實際過程自己就是原函數的執行步驟,惟一不一樣的就是函數參數類型不一樣而已。針對這個共性,能夠將函數柯里化過程,封裝爲一個函數:

prefix func ~<A, B, C>(_ fn: @escaping (A, B) -> C) -> (B) -> (A) -> C {
    {
        b in
        {
            a in
            fn(a, b)
        }
    }
}
複製代碼

該方法傳入一個有兩個參數和一個返回值的函數,A、B、C三個範型爲傳入參數的兩個參數和一個返回值的類型。返回一個新的函數,新的函數有一個參數和一個返回值。新的函數參數類型爲原函數第二個參數的類型。新的函數的返回值是一個須要傳入一個參數的函數,該函數傳入的參數類型爲原函數第一個函數的類型,返回值類型爲原函數的返回值類型。

這個函數的做用就是:將須要兩個A類型、B類型參數,並返回C類型值的原函數Z,轉變成另外一個函數X。X函數只有一個B類型參數,而且返回一個須要傳入A類型參數,而且返回C類型值的函數Y。在函數Y中,將X函數的B類型參數b,和Y函數中的A類型參數a,做爲原函數Z的兩個參數,調用原函數Z,而且返回C類型的返回結果。

將原來的分步柯里化函數都刪除或者註釋掉,直接使用柯里化函數來柯里化對應的函數,並將返回的函數直接組合:

var applePriceFuction = (~originalPrice)(apple) >>> (~discountPrice)(0.9) >>> (~reductionPrice)(3)

print(applePriceFuction("5")) // 26.762405369999996
print(applePriceFuction(4.5)) // 23.786164832999997
print(applePriceFuction(10)) // 56.52481073999999
複製代碼

項目源碼

項目源碼

相關文章
相關標籤/搜索