<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      用函數式編程,從0開發3D引擎和編輯器(二):函數式編程準備

      (本文已過期,請看從0開發3D引擎(五):函數式編程及其在引擎中的應用
      大家好,本文介紹了本系列涉及到的函數式編程的主要知識點,為正式開發做好了準備。

      函數式編程的優點

      1.粒度小

      相比面向對象編程以類為單位,函數式編程以函數為單位,粒度更小。

      正所謂:

      我只想要一個香蕉,而面向對象卻給了我整個森林

      2.性能好

      大部分人認為函數式編程差,主要基于下面的理由(參考 JavaScript 函數式編程存在性能問題么?):
      1)柯西化、函數組合等操作增加時間開銷
      2)map、reduce等操作,會進行多次遍歷,增加時間開銷
      3)Immutable數據每次操作都會被拷貝為新的數據,增加時間和內存開銷

      而我說性能好,是指通過“Reason的編譯優化+Immutable/Mutable結合使用+遞歸/迭代結合使用”,可以解決這些問題:
      1)由于Bucklescript編譯器在編譯時的優化,柯西化等操作和Immutable數據被編譯成了優化過的js代碼,大幅減小了時間開銷
      2)由于Reason支持Mutable和for,while迭代操作,所以可以在性能熱點使用它們,提高性能。

      3.擅長處理數據,適合3D領域編程

      通過高階函數、柯西化、組合等工具,函數式編程可以像流水線一樣對數據進行管道操作,非常方便。

      3D程序有大量的數據要操作,從函數式編程的角度來看:

      3D程序=數據+邏輯

      因此,我們可以:
      使用Immutable/Mutable、Data Oriented等思想和數據結構表達數據;
      使用函數表達邏輯;
      使用組合、柯西化等工具,把數據和邏輯關聯起來。

      更多討論

      FP之優點
      函數式編程(Functional Programming)相比面向對象編程(Object-oriented Programming)有哪些優缺點?

      本系列使用的函數式編程語言

      我們使用Reason語言,它是從Ocaml而來的,屬于非純函數式編程語言。

      而我們熟知的Haskell,屬于純函數式編程語言。

      Reason學習文檔

      為什么不用純函數式編程語言

      1.更高的性能
      Reason支持Mutable、迭代操作,提高了性能

      2.更簡單易用
      1)允許非純操作,所以不需要使用Haskell中的各種Monad
      2)嚴格求值相對于惰性求值更簡單。

      搭建Reason開發環境

      詳見Reason的介紹和搭建Reason開發環境

      本系列涉及的函數式編程知識點

      數據

      • Immutable

      介紹
      創建不可變數據之后,對其任何的操作,都會返回一個拷貝后的新數據。

      示例
      Reason的變量默認為immutable:

      let a = 1;
      
      /* a為immutable */
      

      Reason也有專門的不可變數據結構,如Tuple,List,Record。

      這里以Record為例,它類似于Javascript中的Object:
      首先定義Record的類型:

      type person = {
        age: int,
        name: string
      };
      

      然后定義Record的值:

      let me = {
        age: 5,
        name: "Big Reason"
      };
      

      使用這個Record,如修改"age"的值:

      let newMe = {
          ...me,
          age: 10
      };
      
      Js.log(newMe === me); /* false */
      
      

      newMe是從me拷貝而來,任何對newMe的修改,都不會影響me。

      在Wonder中的應用

      在編輯器中的應用
      編輯器的所有數據都是Immutable的,這樣的好處是:
      1)不用關心數據之間的關聯關系,因為每個數據都是獨立的
      2)不用擔心狀態被修改,減少了很多bug
      3)實現Redo/Undo功能時非常簡單,直接把Immutable的數據壓入History的棧里即可,不用深拷貝/恢復數據。

      在引擎中的應用
      大部分函數的局部變量都是Immutable的(如使用tuple,record結構)。

      相關資料
      Reason->Let Binding
      Reason->Record
      facebook immutable.js 意義何在,使用場景?
      Introduction to Immutable.js and Functional Programming Concepts

      • Mutable

      介紹
      對可變數據的任何操作,都會直接修改原數據。

      示例
      Reason通過"ref"關鍵字,標志變量為Mutable。

      let foo = ref(5);
      
      //將foo的值取出來,設置到five這個Immutable變量中
      let five = foo^; 
      
      //修改foo的值為6,five的值仍然為5
      foo := 6;
      
      

      Reason也可以通過"mutable"關鍵字,標志Record的字段為Mutable。

      type person = {
        name: string,
        mutable age: int
      };
      let baby = {name: "Baby Reason", age: 5};
      baby.age = baby.age + 1; /* 修改原數據baby的age為6 */
      
      

      在Wonder中的應用

      因為操作Mutable數據不會造成拷貝,沒有垃圾回收cg的開銷,所以在性能熱點處,常常使用Mutable數據。

      相關資料
      Reason->Mutable

      函數

      函數是第一公民,函數是數據。

      相關資料:
      如何理解在 JavaScript 中 "函數是第一等公民" 這句話?
      Reason->Function

      • 純函數

      介紹

      純函數是這樣一種函數,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。

      示例

      let a = 1;
      
      
      /* func2是純函數 */
      let func2 = value => value;
      
      /* func1是非純函數,因為使用了外部變量"a" */
      let func1 = () => a;
      
      
      

      在Wonder中的應用

      腳本的鉤子函數(如init,update,dispose等函數)屬于純函數(但不能算嚴格的純函數),這樣是為了:
      1)能夠正確序列化
      腳本會先序列化為字符串,保存在文件中(如編輯器導出的包中);
      然后在導入該文件時(如編輯器導入包),將腳本字符串反序列化為函數(執行:eval('(' + funcStr + ')'))。如果腳本的鉤子函數不是純函數(如調用了外部變量),則會報錯。

      2)支持多線程
      目前腳本是在主線程執行的,但因為它是純函數,所以未來可以放在單獨的腳本線程中執行,提高性能。

      注意
      雖然純函數好處很多,但Wonder中大多數的函數都是非純函數,這是因為:
      1)為了性能
      2)為了簡單易用,所以允許副作用,很少使用容器

      相關資料
      第 3 章:純函數的好處

      • 高階函數

      介紹
      函數能夠作為數據,成為高階函數的參數或者返回值。

      示例

      let func1 = func => func(1);
      
      let func2 = value => value * 2;
      
      func1(func2);   /* func1是高階函數,因為func2是func1的參數 */
      

      在Wonder中的應用

      多個函數中常常有一些共同的邏輯,需要消除重復,可以通過提出一個私有的高階函數來解決。具體示例如下:
      重構前:

      let add1 = value => value + 2;
      
      let add2 = value => value + 10;
      
      let minus1 = value => value - 10;
      
      let minus2 = value => value - 200;
      
      let compute1 = value => value |> add1 |> minus1;
      
      let compute2 = value => value |> add2 |> minus2;
      
      /* compute1,compute2有重復邏輯 */
      
      

      重構后:

      ...
      
      let _compute = (value, (addFunc, minusFunc)) =>
        value |> addFunc |> minusFunc;
      
      let compute1 = value => _compute(value, (add1, minus1));
      
      let compute2 = value => _compute(value, (add2, minus2));
      

      相關資料
      理解 JavaScript 中的高階函數

      • 柯西化

      介紹

      只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
      你可以一次性地調用 curry 函數,也可以每次只傳一個參數分多次調用。

      示例

      let func1 = (value1, value2) => value1 + value2;
      
      let func2 = func1(1);
      
      func2(2);   /* 3 */
      

      在Wonder中的應用

      應用的地方太多了,此處省略。

      相關資料
      第 4 章: 柯里化(curry)
      Currying

      類型

      相關資料
      The "Understanding F# types" series

      • 基本類型

      介紹
      Reason是強類型語言,包含int、float、string等基本類型。

      示例

      type a = string;   /* 定義a為string類型 */
      
      let str:a = "zzz";   /* 變量str為a類型 */
      

      在Wonder中的應用

      類型在wonder中應用廣泛,包括以下的使用場景:
      1)類型驅動設計
      2)領域建模
      3)枚舉

      相關資料

      Reason->Type
      Algebraic type sizes and domain modelling

      • Discriminated Union Type

      介紹
      類型可以接受參數,還可以組合其它的類型。

      示例

      type result('a, 'b) =
        | Ok('a)
        | Error('b);
      
      type myPayload = {data: string};
      
      let payloadResults: list(result(myPayload, string)) = [
        Ok({data: "hi"}),
        Ok({data: "bye"}),
        Error("Something wrong happened!")
      ];
      

      在Wonder中的應用

      1)作為容器的實現
      2)是實現本文后面的Recursive Type的基礎

      相關資料
      Reason->Type Argument
      Reason->Null, Undefined & Option
      Discriminated Unions

      • 抽象類型

      介紹
      有時候我們想定義一個類型,它不是某一個具體的類型,可以將其定義為抽象類型。

      示例

      type value;
      
      type a = value; /* a為value類型 */
      

      在Wonder中的應用

      包括以下的使用案例:
      1)在封裝WebGL api的FFI中(什么是FFI?),把WebGL的上下文定義為抽象類型。

      示例代碼如下:

      /* FFI */
      
      
      /* 抽象類型 */
      type webgl1Context;
      
      [@bs.send]
      external getWebgl1Context : ('canvas, [@bs.as "webgl"] _) => webgl1Context = "getContext";
      
      [@bs.send.pipe: webgl1Context]
      external viewport : (int, int, int, int) => unit = "";
      
      
      
      
      /* client code */
      
      /* canvasDom是canvas的dom,此處省略了獲取它的代碼 */
      /* gl是webgl1Context類型 */
      /* 編譯后的js代碼為:var gl = canvasDom.getContext("webgl"); */
      let gl = getWebgl1Context(canvasDom);   
      
      /* 編譯后的js代碼為:gl.viewport(0,0,100,100); */
      gl |> viewport(0,0,100,100);
      
      

      2)腳本->屬性->value可以為int或者float類型,因此將value設為抽象類型,并且定義抽象類型和int、float類型之間的轉換FFI。

      示例代碼如下:

      
      type scriptAttributeType =
        | Int
        | Float;
      
      
      /* 抽象類型 */
      type scriptAttributeValue;
      
      type scriptAttributeField = {
        type_: scriptAttributeType,
        value: scriptAttributeValue
      };
      
      /* 定義scriptAttributeValue和int,float類型相互轉換的FFI */
      
      external intToScriptAttributeValue: int => scriptAttributeValue = "%identity";
      
      external floatToScriptAttributeValue: float => scriptAttributeValue =
        "%identity";
      
      external scriptAttributeValueToInt: scriptAttributeValue => int = "%identity";
      
      external scriptAttributeValueToFloat: scriptAttributeValue => float =
        "%identity";
        
        
      /* client code */
      
      /* 創建scriptAttributeField,設置value的數據(int類型) */
      
      let scriptAttributeField = {
          type_: Int,
          value:intToScriptAttributeValue(10) 
      };
      
      
      
      /* 修改scriptAttributeField->value */
      
      let newScriptAttributeField = {
          ...scriptAttributeField,
          value: (scriptAttributeValueToInt(scriptAttributeField.value) + 1) |> intToScriptAttributeValue
      };
      

      相關資料
      抽象類型(Abstract Types)

      • Recursive Type

      介紹
      從類型定義上看,可以看成是Discriminated Union Type,只是其中至少有一個union type為自身類型,即遞歸地指向自己。

      示例
      還是看代碼好理解點,具體示例如下:

      type nodeId = int;
      
      /* tree是Recursive Type,它的文件夾節點包含了子節點,而子節點的類型為自身 */
      type tree =
        | LeafNode(nodeId)
        | FolderNode(
            nodeId,
            array(tree),
          );
      
      

      在Wonder中的應用

      在編輯器中的應用

      Recursive Type常用在樹中,如編輯器的資產樹的類型就是Recursive Type。

      相關資料
      The "Recursive types and folds" series
      Map as a Recursion Scheme in OCaml

      過程

      • 組合

      介紹
      多個函數可以組合起來,使得前一個函數的返回值是后一個函數的輸入,從而對數據進行管道處理。

      示例

      let func1 = value => value1 + 1;
      
      let func2 = value => value1 + 2;
      
      10 |> func1 |> func2;   /* 13 */
      

      在Wonder中的應用

      在引擎中的應用

      組合可以應用在多個層面,如函數層面和job層面。

      job = 多個函數的組合
      

      我們來看下job組合的應用示例:

      從時間序列上來看:

      引擎=初始化+主循環
      

      而初始化和每一次循環,都是多個job組合而成的管道操作:

      初始化 = create_canvas |> create_gl |> ...
      
      
      每一次循環 = tick |> dispose |> reallocate_cpu_memory |> update_transform |> ...
      

      相關資料

      第 5 章: 代碼組合(compose)

      • 遞歸

      介紹

      遍歷操作可以分成兩類:
      迭代
      遞歸

      遞歸就是指函數調用自己,滿足終止條件時結束。如深度優先遍歷是遞歸操作,而廣度優先遍歷是迭代操作。

      注意:
      盡量寫成尾遞歸,這樣Reason會將其編譯成迭代操作。

      示例

      let rec func1 = (value, result) => {
          value > 3 ? result : func1(value + 1, result + value);
      };
      
      func1(1, 0);   /* 0+1+2+3=6; */
      

      在Wonder中的應用

      幾乎所有的遍歷都是尾遞歸,只有在少數使用Mutable和少數性能熱點的地方,使用迭代操作(使用for或while命令)。

      相關資料
      什么是尾遞歸?
      Reason->Recursive Functions

      • 模式匹配

      介紹
      使用switch結構代替if else處理程序分支。

      示例

      let func1 = value => {
          switch(value){
              | 0 => 10 
              | _ => 100
          }
      };
      
      func1(0);   /* 10 */
      func1(2);   /* 100 */
      

      在Wonder中的應用

      主要用在下面三種場景:

      1)取出容器的值

      type a = 
          | A(int)
          | B(string);
          
      switch(a){
          | A(value) => value
          | B(value) => value
      };
      

      2)處理Option

      let a = Some(1);
      
      switch(a){
          | None => ...
          | Some(value) => ...
      }
      

      3)處理枚舉類型

      type a = 
          | A
          | B;
          
      switch(a){
          | A => ...
          | B => ...
      }
      

      相關資料
      Reason->Pattern Matching!
      模式匹配

      異步

      • 函數反應式編程

      介紹
      處理異步,主要有以下的方法:
      1)回調函數
      缺點:過多的回調導致嵌套層次太深,容易陷入回調地獄,不易維護。
      2)Promise
      3)await,aync
      4)使用函數反應式編程的流
      優點:能夠使用組合,像管道處理一樣處理各種流,符合函數式編程的思維。

      Wonder使用流來處理異步,其中也用到了Promise,不過都被封裝成了流。

      示例
      使用most庫實現FRP,因為它的性能比Rxjs更好。

      /* 
      輸出:
      next:2
      next:4
      next:6
      complete
      */
      let subscription =
        Most.from([|1, 2, 3|])
        |> Most.map(value => value * 2)
        |> Most.subscribe({
             "next": value => Js.log2("next:", value),
             "error": e => Js.log2("error:", e##message),
             "complete": () => Js.log("complete"),
           });
      
      
      

      在Wonder中的應用

      凡是異步操作,如事件處理、多線程等,都用流來處理。

      相關資料
      你一直都錯過的反應型編程
      函數式反應型編程 (FRP) —— 實時互動應用開發的新思路
      函數式響應型編程(Functional Reactive Programming)會在什么問題上有優勢?

      容器

      • 容器

      介紹

      為了領域建模,或者為了保證純函數而隔離副作用,需要把值封裝到容器中。外界只能操作容器,不直接操作值。

      示例

      1)領域建模示例

      比如我們要開發一個圖書管理系統,需要對“書”進行建模。
      書有書號、頁數這兩個數據,有小說書、技術書兩種類型。
      建模為:

      type bookId = int;
      
      type pageNum = int;
      
      type book = 
          | Novel(bookId, pageNum)
          | Technology(bookId, pageNum);
          
      

      現在我們創建一本小說,一本技術書,以及它們的集合:

      let novel = Novel(0, 100);
      
      let technology = Technology(1, 200);
      
      let bookList = [
          novel,
          technology
      ];
      

      對“書”這個容器進行操作:

      let getPage = (book) => 
      switch(book){
          | Novel(_, page) => page
          | Technology(_, page) => page
      };
      
      
      let setPage = (page, book) => 
      switch(book){
          | Novel(bookId, _) => Novel(bookId, page)
          | Technology(bookId, _) => Technology(bookId, page)
      };
      
      /* client code */
      
      /* 將技術書的頁數設置為集合中所有書的總頁數 */
      let newTechnology =
      bookList
      |> List.fold_left((totalPage, book) => totalPage + getPage(book), 0)
      |> setPage(_, technology);
      
      

      在Wonder中的應用

      包含以下使用場景:
      1)領域建模
      2)錯誤處理
      3)處理空值
      使用Option包裝空值。

      相關資料

      Railway Oriented Programming
      The "Map and Bind and Apply, Oh my!" series
      強大的容器
      Monad
      Applicative Functor

      多態

      • GADT

      介紹
      全稱為Generalized algebraic data type,可以用來實現函數參數多態。

      示例
      重構前,需要對應每種類型,定義一個isXXXEqual函數:

      let isIntEqual = (source: int, target: int) => source == target;
      
      let isStringEqual = (source: string, target: string) => source == target;
        
        
      isIntEqual(1, 1); /*true*/
      
      isStringEqual("aaa", "aaa"); /*true*/
      

      使用GADT重構后,對應多個類型,只有一個isEqual函數:

      type isEqual(_) =
        | Int: isEqual(int)
        | Float: isEqual(float)
        | String: isEqual(string);
      
      let isEqual = (type g, kind: isEqual(g), source: g, target: g) =>
        switch (kind) {
        | _ => source == target
        };
      
      isEqual(Int, 1, 1); /*true*/
      
      isEqual(String, "aaa", "aaa"); /*true*/
      

      在Wonder中的應用

      1)契約檢查
      如需要判斷兩個變量是否相等,則使用GADT,定義一個assertEqual方法替換assertStringEqual,assertIntEqual等方法。

      相關資料
      Why GADTs matter for performance(需要FQ)
      維基百科->Generalized algebraic data type

      • Module Functor

      介紹

      module可以作為參數,傳遞給functor,返回一個新的module。

      類似于面向對象的“繼承”,可以使用函子functor,在基module上擴展出新的module。

      示例

      module type Comparable = {
        type t;
      
        let equal: (t, t) => bool;
      };
      
      module MakeAdd = (Item: Comparable) => {
        let add = (x: Item.t, newItem: Item.t, list: list(Item.t)) =>
          Item.equal(x, newItem) ? list : [newItem, ...list];
      };
      
      module A = {
        type t = int;
        let equal = (x1, x2) => x1 == x2;
      };
      
      /* module B有add函數,該方法調用了A.equal函數 */
      module B = MakeAdd(A);
      
      let list = B.add(1, 2, []);    /* list == [2] */
      let list = list |> B.add(1, 1);    /* list == [2] */
      

      在Wonder中的應用

      在編輯器中的應用

      1)錯誤處理
      錯誤被包裝為容器Result;
      由于容器Result中的值的類型不一樣,所以將Result分成RelationResult、SameDataResult。

      這兩類Result有共同的模式,因此可以提出基module:Result,然后增加MakeRelationResult、MakeSameDataResult這兩個module functor。它們將Result作為參數,返回新的module:RelationResult、SameDataResult,從而消除重復。

      相關資料
      Reason->Module Functions

      函數式編程學習資料

      JS 函數式編程指南
      這本書作為我學習函數式編程的第一本書,非常容易上手,作者講得很簡單易懂,推薦~

      Awesome FP JS
      收集了函數式編程相關的資料。

      F# for fun and profit
      這個博客講了很多F#相關的函數式編程的知識,非常推薦!
      如果你正在使用Reason或者Ocaml或者F#語言,建議到該博客中學習!

      歡迎瀏覽上一篇博文:用函數式編程,從0開發3D引擎和編輯器(一)
      歡迎瀏覽下一篇博文:用函數式編程,從0開發3D引擎和編輯器(三):初步需求分析

      posted @ 2019-05-04 08:09  楊元超  閱讀(899)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 色偷偷亚洲女人天堂观看| 色婷婷亚洲精品综合影院| 麻豆a级片| 国产一区二区不卡在线| 欧美人成精品网站播放| 狠狠色狠狠色综合久久蜜芽| 久久久综合香蕉尹人综合网 | 国产精品久久露脸蜜臀| 国产a在视频线精品视频下载 | 亚洲精品无码久久一线| 国产精品自拍视频免费看| 亚洲AV无码AV在线影院| 国产午夜精品理论大片| 狠狠亚洲狠狠欧洲2019| 又粗又硬又黄a级毛片| 国产成人高清亚洲综合| 国产亚洲精品久久久久久无亚洲| 国产精品亚洲五月天高清| 亚洲AV日韩AV高清在线观看| 少妇xxxxx性开放| 亚洲人成亚洲人成在线观看 | 茄子视频国产在线观看 | 精品国产国语对白主播野战| 中文字幕久久国产精品| 大陆一级毛片免费播放| 国产欧美日韩高清在线不卡| 新宁县| av一区二区中文字幕| 国色天香成人一区二区| 99热成人精品热久久66| 99热成人精品热久久66| 国产真人无遮挡免费视频| 男人av无码天堂| 国产日韩综合av在线| 亚洲男人精品青春的天堂| 欧美亚洲一区二区三区在线| 国产乱人伦真实精品视频| 疯狂做受XXXX高潮国产| 99久久伊人精品综合观看| 国产午夜福利不卡在线观看| 亚洲精品日本一区二区|