函數語言程式設計嘗試之俄羅斯方塊

slaveoftime發表於2019-03-02

加減數字示例

  1. Redux裡的Store對應Model,目前只有數字x;
  2. Redux裡的Action對應Msg,目前可以對狀態進行加或減;
  3. Redux裡的初始化對應init,目前x設為0,併發一個Msg說要進行加操作,所以介面應該顯示為1;
  4. Redux裡的Reducer對應update,這是唯一一個能對狀態進行更改的地方,而且fsharp本身預設都是immutable的,所以你也無法對狀態在其他任何地方進行更改;在redux裡會用immutablejs來做,但是個人使用發現挺麻煩。
  5. view裡面就是基於狀態model進行渲染,使用者的點選等操作通過釋出Msg來通知Program呼叫update來修改狀態;
  6. Program就是把init, update, view繫結在一起然後跑起來。
    type Model = { x : int }

    type Msg = Increment | Decrement

    let init () = 
        { x = 0 }, Cmd.ofMsg Increment

    let update msg model =
        match msg with
        | Increment -> { model with x = model.x + 1 }, Cmd.none
        | Decrement -> { model with x = model.x - 1 }, Cmd.none

    let view model dispatch =
        div []
            [
                div [] [str (string model.x)]
                button [OnClick (fun e -> dispatch Increment)] [str "+" ]
                button [OnClick (fun e -> dispatch Decrement)] [str "-" ]
            ]

    Program.mkProgram init update view 
    |> Program.run
複製程式碼

整個程式真的是太簡潔易懂了,可能會有一些語法需要習慣,比如type Msg = Increment | Decrement中的|相當於就是,而match msg with類似switch case,但是功能強大很多。view裡面的div [放HTML標籤屬性] [放子元素]。整個程式幾乎每一行都是在寫自己的業務,冗餘的內容很少,真是太優雅了。忍不住要把自己的第一篇文章要用來寫這個

函數語言程式設計的語言很多,能用做前端的似乎也不少,包括fsharp, scala等。由於本人主要混在.net平臺,所以對fsharp瞭解比較多。用fsharp寫前端主要是靠Fable把fsharp編譯成js(FSharp |> Fable |> Bable |> Webpack |> js)。

Elmish 是一個設計思想,應該是和mvvm平級,理念類似redux,但是時間是應該更早一點。具體可以參看https://elmish.github.io/elmish/。所以debug的時候也可以用redux的chrome外掛檢視狀態等。

函數語言程式設計嘗試之俄羅斯方塊

Fable只是一個編譯器,除了view其他的都是單純的邏輯,而view裡面的東西可以是很多前端的框架技術比如react(上例用的就是), react native, 也可以是原生的html,或者vue等,但是Fable社群裡面最流行的還是react。所以react生態裡的所有控制元件都可以使用,但是為了有fsharp的型別提醒需要寫一些型別宣告,這個類似於typescript的.d.ts裡寫的東西;所以社群裡也有人寫了一些工具把.d.ts的內容直接翻譯成fsharp可使用的型別(目前還沒有試過)。當然也可以自行繫結原生js,比如需要引入npm的庫的時候就要做這種事情。具體參見http://fable.io/docs/interacting.html

俄羅斯方塊

程式碼託管在https://github.com/albertwoo/Tetris

專案初始化是用社群裡一個模板生成的[https://github.com/SAFE-Stack],包括的內容很多,如自動化編譯,打包,熱更新,測試等,程式碼也有很多如Client, Server, Test等,目前不需要Server,所以目前的主要程式碼都在src/Client下面。

TetrisDomain.fs定義了俄羅斯方塊的基本型別以及一些操作比如操作Block,檢視是否相撞,或者清除滿足條件的行等。Square是指最小的馬賽克方塊,Block是指下落的物體:

type Square = { Location: int * int; Color: int * int * int * float }
type BlockType = T | L | J | I | O | Z | RZ | X
type Block = { Type: BlockType; Squares: Square list }
type Action = Rotate | Left | Right | Down
...
複製程式碼

Tetris.fs相當於寫了一個元件,包括了狀態,更新狀態,以及介面的一些東西:

type Model = {
    SquareSize: int
    Boundry: int * int
    AllSquares: Square list
    PreviewBlock: Block option
    MovingBlock: Block option
    PrectionBlock: Block option
    IsOver: bool
    Score: int
    DefaultSpeed: int
    Speed: int
    SpeedCount: int }
type Msg = | Action of Action | ReachBottom | ReachLeft | ReachRight
複製程式碼

App.fs是整個程式的入口,會把Tetris定義的東西整合進來,也包含了一些介面的佈局,開始,暫停,重新開始等操作。

type Model = {
    Tetris: Tetris.Model
    TimeCost: int
    TouchStartPoint: (float * float) option
    TouchMovingPoint: (float * float) option
    TouchTime: DateTime option
    IsPaused: bool
    IsRestarting: bool
    HideDetail: bool }

type Msg =
    | TetrisMsg of Tetris.Msg
    | BeginRestart | CancelRestart | Restart
    | Tick
    | TouchStart of float * float | TouchMove of float * float | TouchEnd of float * float
    | Pause | Continue
    | HideDetail
複製程式碼

整個程式的元件的拆分沒有做得很好,邏輯不是很清晰,作為學習勉強接受吧。 整個效果如下,也可以線上體驗,觸控最佳,鍵盤勉強可用:

函數語言程式設計嘗試之俄羅斯方塊

結語

函數語言程式設計似乎離成為主流還有很長的路,在現代的伺服器方面的應用應該比較多,畢竟微服務的流行導致框架和語言的選擇變得更靈活。但是在前端方面的應用很少,android, xamarin等本地應用的開發都大量使用mvvm的設計模式。以前我開發過wpf,都是基於mvvm,現在開發angular幾乎自然上手,設計思想如出一轍。React倒是特立獨行,包括其中流行的redux狀態管理也很有獨到之處。vue給我的感覺就顯得抱負很大什麼都想做,但是個人淺嘗後最後還是選擇了angular,畢竟有typescript的完美融合。

因為專案啟動後已經使用了angular,但是也想利用redux的一些先進思想,後來嘗試ngrx,但是發現需要寫太多冗餘的程式碼,而且分散在很多不同的檔案,加之js的語言特性,寫actions, reducers, selectors的時候簡直婆婆媽媽,讓我失去耐心,冗餘程式碼幾乎趕上業務邏輯程式碼,所以最後還是放棄。不過angular本身的設計已經在我的專案夠用了,除了有點囉嗦。

物件導向還大行其道,並且對各種問題都有著成熟的解決方案,上面所言都是在下實踐所得的經驗,沒有嚴格的理論支援,純屬個人妄言,聽聽就是,也可嗤之以鼻。

相關文章