wanyu790103
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # [第5章] 針對複雜應用的設計模式 ## :triangular_flag_on_post: 本章內容 :::success 1.命令是處理異常方式的問題<br> 2.使用容器,以防訪問無效數據<br> 3.用functor的實現來做數據轉換<br> 4.利于組合的Monad數據類型<br> 5.使用Monadic類型來鞏固錯誤處理策略<br> 6.Monadic類型的組合與交錯 ::: ### :pencil2: 5.1 命令式錯誤處理的不足 :::success 在許多情況下都會發生javaScript錯誤,特別是在與服務器通信時,或是在試圖訪問一個Null對象屬性時。此外,第三方庫也有可能拋出異常來表示某些特定的錯誤。因此開發者在編程時總是需要做最好最壞的打算 異常是通過try-catch處理 try...catch 語法標記出一整塊需要測試的語句,並指定一個以上的回應方法,萬一有例外拋出時,try...catch 語句就會捕捉。 ::: ### :pencil2: 5.1.1 try catch ``` javascript try { // code that might throw an exception in here //需要測試的語句 } catch (e) { // 處理錯誤的發生,並做顯示 console.log('ERROR' + e.message); } ``` #### 這個語法會先將 try 區塊中的程式碼執行一次,如果裡面的程式碼有問題就會立刻中止執行,執行步驟會直接跳到 catch 區塊的程式碼,如果 catch 的程式碼又有問題,那網頁就會跳錯。 ``` javascript // To name a few // 資料查找 :: DB, String -> Object // const findObject = R.curry(function (db, id) { const result = find(db, id) if(!result) { throw new Error('Object with ID [' + id + '] not found'); } return result; }); const findStudent = findObject(DB('students')); ``` ``` javascript try { // 需要測試的語句 var student = findStudent('444-44-4444'); } catch (e) { console.log('ERROR' + e.message); } //這段程式碼使用了柯里化的概念來實現一個具有錯誤處理的 //創建了一個特定資料庫的查找函數 findStudent。 ``` ### :pencil2: 5.1.2 函數式程序不應拋出異常 命令式的javaScript代碼結構有很多缺陷,而且也會與函數式的設計有兼容問題。<br> 1.<b>可讀性和可維護性</b>:通常會一系列的指令和狀態改變來描述程式的執行流程,這可能導致程式碼變得難以閱讀、理解和維護。當程式碼的邏輯複雜時,命令式的結構可能導致程式碼變得冗長和難以管理。<br> 2.<b>可變狀態</b>:命令式的 JavaScript 常常使用可變的狀態(mutable state),這使得程式的行為難以預測。當多個部分共享同一個狀態時,容易出現錯誤和副作用,這可能導致難以追蹤程式行為的問題。<br> 3.<b>副作用</b>:命令式的 JavaScript 經常使用副作用(side effects),例如修改全域變數或修改DOM。這種副作用可能導致程式碼的可測試性降低,並增加理解和調整的困難。<br> 4.<b>兼容性問題</b>:函數式設計強調純函數(pure functions)和不可變數據(immutable data),而命令式的 JavaScript 通常偏向於可變狀態和副作用。這可能導致在使用函數式設計技巧時,需要克服一些兼容性問題,或進行額外的轉換和調整。 ### :thought_balloon: 5.1.3 空值(null)檢查問題 <!-- 向前面所說的,Try & Cache 會造成無法連貫 --> 除去拋出錯誤外的另外一種錯誤處理,就是回傳 `null` 以下的例子是根據學校去查出學生的住址:point_down: ``` javascript function getCountry(student) { let school = student.getSchool(); if(school !== null) { let addr = school.getAddress(); if(addr !== null) { var country = addr.getCountry(); return country; } // null 回傳最大的保證了單一回傳的特性,雖然沒有比較好。 // 這增加了我們要多花點時間和程式去做棘手的 `null` 判定 return null; } throw new Error('Error extracting country info'); } ``` ## :triangular_flag_on_post: 5.2 Functor 把值裝進一個容器,而且只能使用對外的 API 處理值,這樣做有什麼好處?換個方式問:讓容器自己去運用函數的好處是什麼?答案就是抽象,也更 陳述性的declarative,我們換個角度看,當我們傳入一個函數給 map() 時,我們請求容器幫我們對值執行這個函數 參考:https://ithelp.ithome.com.tw/articles/10240162 <!-- 雖然說我們可以用一個簡單的過濾去判斷是否為`Null`在做對應的回傳 --> <!-- 但這和 Try & Cache 都依樣是被動的檢查,如果能夠更有效並且減少這些冗余代碼能夠省下多少時間。 --> ### :thought_balloon: 5.2.1 Wrapping unsafe values - Containerizing (or wrapping) values(比較實際的例子是封裝)能夠最大程度的保證值的不可變動與安全的存取或修改。 <!-- 這段我們將會用第三章講過的 Map 來做解釋 --> - Map 就是Functional programing 所說的 pure function 5.1 以下將用 Wrapper 這個範例來做解釋 補充 ``` javascript // 封裝一個值,可以避免使用者去直接的 access 或是 manipulate class Wrapper { constructor(value) { this._value = value; } // map :: (A -> B) -> A -> B map(f) { // 錯誤 1 return f(this.val); }; Simple type that stores a single value of any type Maps a function over this type (just like arrays) toString() { // 錯誤 2 return 'Wrapper (' + this.value + ')'; } } // wrap :: A -> Wrapper(A) // 注意:這個 wrap 後面會很常使用到 const wrap = (val) => new Wrapper(val); Helper function that quickly creates wrappers around values ``` 例: ``` javascript const wrappedValue = wrap('Get Functional'); // 用 Ramda 去取得值 // tips: identity 並不做任何事情,只回傳接收到的參數 wrappedValue.map(R.identity); //-> 'Get Functional' 或是 wrappedValue.map(console.log); // 好處是如果需要 access Wrapper 都必須要經過 .map,而不能直接取得值 // 也最大程度的提升我們整個參數的不可變動性,並且保護我們的變數 ``` 以上的方法雖然保護到變數,但依然沒辦法很好的處理 null/undefined,接下來我們會根據 null/undefined 來做處理。 #### 如果錯誤能夠被其他方法來做預先的判斷並處理,並保證我們進入 Function 時候的值不會是*錯誤*的 * 這裡錯誤包含 null, 空字串, 負數等非預期的回應資訊 ``` javascript // 以這來當例子,我們建立一個 fmap 靜態 function // fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B] Wrapper.prototype.fmap = function (f) { // 判斷邏輯可以塞這裡,在驗證之後,才送給 wrap 做儲存 return wrap(f(this.val)); }; ``` Factor 就是像洋蔥一樣層層的包裹著變數,然後做處理. - 上面的 Function 先新建一個*容器 - 接著將值拋給由外部傳入的 function - 最後再將取得到的最終運算值,回傳給*同屬性的容器 後面我們會再進一步的說明這個容器,還有他的相關用法。 ### :pencil2: 5.2.2 Functors explained A functor is simply something that can be mapped over 這句話分成兩部分,一個是 something 一個是 can be mapped over,只要符合這兩個就是 Functor 了 ### Something (或稱 Category)<br> A set of values arranged in some shape ![](https://hackmd.io/_uploads/HyD3d8iH3.jpg) ↑ The values are in yellow, and the shape is in blue ``` javascript // Something 1: Array [1,2,3,4,5] // 被存放在 Array 裡的一組值 // Something 2: Object { age: 22, name: 'Tom'} // 被存在物件裡的一組值 // Something 3: Single value 39 // 某個值,除了 Number 也可以是任何型別 // => [ 5, 7, 9 ] ``` 不止 Array 是 Something,任何原始型別 ( string 、 number... ) 或是 Object 甚至 Function 都是 Something ### Can be mapped over<br> 代表你可以把 list 中每一個值做一些事然後輸出 ``` javascript // Something 1: Array [ 1, 2, 3, 4, 5 ].map(x => x + 3)); // [ 4, 5, 6, 7, 8 ] ``` ![](https://hackmd.io/_uploads/rynz38irn.jpg) ## :triangular_flag_on_post: 5.3 使用Monad函數式地處理錯誤 <span style="font-size:18px">Functional error handling using monads</span> ``` javascript // 找到學生 const findStudent = R.curry(function(db, ssn) { return wrap(find(db, ssn)); }); // 找到學生的地址 const getAddress = function(student) { return wrap(student.fmap(R.prop('address'))); } // Compose 兩個功能為一個,目的尋找特定學生地址 const studentAddress = R.compose( getAddress, findStudent(DB('student')) ); // 範例 studentAddress('444-44-4444'); //-> Wrapper(Wrapper(address)) // 第一次 map,应用 R.identity // const result1 = studentAddress('444-44-4444').map(R.identity); // 第二次 map,再次应用 R.identity // const result2 = result1.map(R.identity); // 為了取得最後的 Result - address studentAddress('444-44-4444').map(R.identity).map(R.identity); ``` ### :pencil2: 5.3.1 Monad 從控制流到數據流 Data control flow ``` javascript Wrapper(2).fmap(half); //-> Wrapper(1) Wrapper(3).fmap(half); //-> Wrapper(1.5) // Functor 被賦予函數並返回結果 const Empty = function(_){}; Empty.prototype.map = function() { return this; }; const empty = () => new Empty(); //(n % 2 == 0) 来判断 n 是否是偶数。如果 n 是有限数字且是偶数,则返回 true,否则返回 false // half(4), half(7) const isEven = (n) => Number.isFinite(n) && (n % 2 == 0); const half = (val) => isEven(val) ? wrap(val / 2) : empty(); half(4).fmap(plus3); //-> Wrapper(5) //由于 3 不是偶数,所以会返回一个表示空值的 Empty 实例 half(3).fmap(plus3);// Empty ``` 有關 Monad 1.或是說 FP,就是一個抽象也沒有任何意義的 Fn<br> 2.簡單來說就是將值包裝起來,不停的轉送,中間會經過拆解、加工(Chain),直到最後解開外包後顯示內容.<br> 3.如上方的範例 Wrapper,但不同的 Monad 有不同的實現原則(例如: map, fmap)。 重構 Wraper ``` javascript class Wrapper { constructor(value) { this._value = value; } static of(a) { return new Wrapper(a); } map(f) { return Wrapper.of(f(this.value)); } // 遞回自己,用於解構取值 join() { if(!(this.value instanceof Wrapper)) { return this; } return this.value.join(); } toString() { return `Wrapper (${this.value})`; } } // 使用 Wrapper 类 const flattenedWrapper = nestedWrapper.join(); console.log(flattenedWrapper.toString()); // Wrapper (7) ``` ### :thought_balloon: 5.3.2 Error handling with Maybe and Either monads - 除了有效的包裝值(如前面所講的例子)外,一元結構(monadic structures)也可以用來模擬不存在(absence of one)的狀況 - 例如 null 或 undefined。 - 所以 FP 具體的將錯誤轉變成一件可以預期的事情,用 Maybe 或 Either 來做處理,而不是直接中斷出錯。 兩者都有以下的優勢 - 隔離不需要的邏輯與變數 - 將 null/undeined 處理併入程式中 - 避免錯誤拋出中斷程式 - 支援功能合併處理的作法 - 集中邏輯並且設置預設值 ### Maybe 做空值處理 Maybe 一個空型別和兩個具體的子類型,它幫忙處理了 “nullable” 的值 (null and undefined),所以我們可以專心的處理邏輯相關的事情。 - Just - 用於回傳一個包裹過的值 // 可以想像 nothing 就是一個什麼事情都不做,讓這段程式 pass 過去 - Nothing - 用於回傳兩種包裹過的值 *空值* 或是 *不重要的錯誤資訊* 底下我們來看個範例 ``` javascript // 這宣告一個外框架 class Maybe { static just(a) { return new Just(a); } static nothing() { return new Nothing(); } // 這裡來做判斷使用 Just 或是 nothing static fromNullable(a) { return a !== null ? just(a) : nothing(); } static of(a) { return just(a); } get isNothing() { return false; } get isJust() { return false; } } // 用來做事情的 Just class Just extends Maybe { constructor(value) { super(); this._value = value; } get value() { return this._value; } map(f) { return of(f(this.value)); } getOrElse() { return this.value; } filter(f) { Maybe.fromNullable(f(this.value) ? this.value : null); } get isJust() { return true; } toString () { return `Maybe.Just(${this.value})`; } } // 就真的啥事都不做的 Nothing,最多就是當你要值的時候,給你個錯誤訊息 class Nothing extends Maybe { map(f) { return this; } get value() { throw new TypeError('Can't extract the value of a Nothing.'); } getOrElse(other) { return other; } filter() { return this.value; } get isNothing() { return true; } toString() { return 'Maybe.Nothing'; } } ``` 從上面程式可以看得出來,Maybe 就是一個骨架,而有兩個子類別(Just, Nothing)去繼承,最終的流向則會由程式的邏輯去處理會觸發哪一個。 <!-- 這邊可以舉個例子 jQuery --> 實例上來說,這裡 `wrap.map` 的結果會決定最後要使用那一個子類別。 如 Students 有取到就會儲存於 Just 內部 ![](https://hackmd.io/_uploads/rkM4jJm8n.png) 讓我們回到 5.4 的例子,要從本地端的 DB 去取得學生地址,我們沒辦法確定是不是一定能取到值,所以我們需要做一些檢查。 ``` javascript // 我們把空值的檢查 Curry 化,這樣其他地方有需要也可以重複的使用 // safeFindObject :: DB -> String -> Maybe const safeFindObject = R.curry(function(db, id) { return Maybe.fromNullable(find(db, id)); }) // 指定我要尋找 student 的 table // safeFindStudent :: String -> Maybe(Student) const safeFindStudent = safeFindObject(DB('student')); const address = safeFindStudent('444-44-4444').map(R.prop('address')); address; //-> Just(Address(...)) or Nothing // 如果給予不正確的值,當取用變數 (get()) 時 我們會得到對應的錯誤,不取用則不會有任何事發生。 ``` <!-- 根據學號取的地址 也就是 Just('444-44-4444') 或是 nothing --> 看完上面之後,可以很直白的由命名知道他要做什麼或用途是什麼。 另外一個有趣得應用是 `getOrElse()` ``` javascript const userName = findStudent('444-44-4444').map(R.prop('firstname')); // 如果到了這裡正確,會直接顯示結果,但錯誤的話則會顯示預設的文字作替代 document.querySelector('#student-firstname').value = username.getOrElse('Enter first name'); ``` 接下來讓我們再回頭看一下之前那個醜醜的 `getCountry()` ``` javascript function getCountry(student) { let school = student.getSchool(); if(school !== null) { let addr = school.getAddress(); if(addr !== null) { return addr.country(); } } // 當這被回傳,我們很難清楚到底原因是什麼 return 'Country does not exist!'; } ``` <!-- 甚至於可能會被繁雜的 null/undefined 檢查搞得很火大,或是避免型別錯誤造成的意外錯誤。 --> 讓我們用 Maybe 來寫這個實際的運用 ``` javascript const country = R.compose(getCountry, safeFindStudent); // 如同上面描述,這邊會根據你是 Just 或是 Nothing 來做呈現,如果是 Nothing 那我就會拋出最底下的 else 錯誤。 const getCountry = (student) => student .map(R.prop('school')) .map(R.prop('address')) .map(R.prop('country')) .getOrElse('Country does not exist!'); ``` Function lifting ```javascript const safeFindObject = R.curry(function(db, id) { return Maybe.fromNullable(find(db, id)); } ``` > 這裡可以提出一個問題,我真的每個地方都需要去做安全檢查嗎? 接下來如果想要更精確的知道到底錯誤是什麼原因發生的,有另一種 Error handling 叫做 Either....... ------------------------------------- To be continue ------------------------------------- ### 5.3.2 Either Monad 來處理異常 #### 使用Either從故障中恢復 一條是 Happy Path (Right),就是運算過程一切順利;<br> 另一條是 Sad Path (Left),只要某一處的運算出現錯誤,就會跳過之後運算直接輸出失敗結果

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully