currying與partial application威力巨大,假設要解決一個領域設計裡的問題:
module rec WeatherDemo =
type GenerateWeatherInfo = Location -> WeatherInfo
type Location = string
type LocationCode = int
type Temperature = float
type HtmlString = string
type WeatherInfo = HtmlString // Html doc
複製程式碼
實際流程的設計可以是:
type GenerateWeatherInfoFlow =
GetLocationCode
-> GetWeather
-> RenderWeatherInfo
-> GenerateWeatherInfo
type GetLocationCode = Location -> LocationCode option
type GetWeather = LocationCode -> Temperature option
type RenderWeatherInfo = Location -> Temperature option -> WeatherInfo
複製程式碼
GenerateWeatherInfoFlow其實展開就是:
type GenerateWeatherInfoFlow =
(Location -> LocationCode option)
-> (LocationCode -> Temperature option)
-> (Location -> Temperature option -> WeatherInfo)
-> Location -> WeatherInfo
複製程式碼
但是抽象成不展開的樣子有很多好處,設計領域的時候簡潔清楚,程式碼類似設計文件,而且用領域裡的語言描述,對於新進工程師也方便上手,一看就知道整個流程是什麼樣。
另外測試也方便,以前為了測試,很多依賴注入的東西都需要繁雜的mock,但是現在測試可以如下:
[<Fact>]
let ``should generate weather info success`` () =
generateWeatherInfo
(fun _ -> Some 1)
(fun _ -> Some -1.)
(fun x y -> sprintf "%s %f" x (y |> Option.get))
"shanghai"
|> should equal "shanghai -1"
複製程式碼
主流程的實現可以是:
let generateWeatherInfo: GenerateWeatherInfoFlow =
fun getLocationCode
getWeather
renderWeatherInfo
location ->
location
|> getLocationCode
|> Option.bind getWeather
|> renderWeatherInfo location
複製程式碼
一切按照定義好的圖紙來,所以簡單得像樂高積木一樣,比如最後的組裝可以是:
let generateWeatherInfoApi db mojiKey location =
generateWeatherInfo
(getLocationFromDb db)
(getWeatherFromMojitianqi mojiKey)
(simpleRenderWeatherInfo)
location
複製程式碼
另外有人可能會疑惑(getLocationFromDb db),不是前面的定義是
type GetLocationCode = Location -> LocationCode option
複製程式碼
麼,沒有db啊,妙的地方就在此:
let getLocationFromDb db: GetLocationCode =
fun location ->
(query {
for lc in db.LocationCodes do
where (lc.Location.Contains location)
select lc.Code
}).FirstOrDefault()
|> mapToOption
複製程式碼
各個功能的實現可以是有副作用的如訪問資料庫getLocationFromDb,發http請求 getWeatherFromMojitianqi等,也可以是純函式simpleRenderWeatherInfo,拼接簡單的html字串。我們可以組織好這些功能的實現,實現各種不同的組合形式來滿足專案經理各種無理的需求變更。
當然如果你的流程很複雜,你可以設計很多子流程然後嵌入到主流程裡面,這樣可以減少一個函式需要的引數。
總之,個人經驗感覺currying與partial application是functional programming裡非常炫酷有用的東西,定義簡單,方便測試,單一職責,靈活面對需求的更改。。更多的可以參見一本書“Domain Modeling Made Functional - Tackle Software Complexity with Domain-Driven Design and F#”。