iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 14
0
自我挑戰組

Some thing with Reason系列 第 14

BuckleScript-Function

補齊一些之前漏掉的關於 Function 的知識

BuckleScript - Function

在 Reason 綁定 function 和一般變數一樣

[@bs.val] external encodeURI: string => string = "encodeURI";
let result = encodeURI("hello");

標記參數

可以利用標記參數來讓外部 Javascript 取得的函數更加易懂

[%%raw "function draw(x, y, border = false) {
  return border ? (x + y): 0;
}"];

[@bs.val] external draw: (~x: int, ~y: int, ~border: bool=?, unit) => int = "";

Js.log(draw(~x=10, ~y=20, ~border=true, ())); /* 30 */

note: 這有一個特別的狀況,因為 border 是Option 的參數,所以最後需要有一個 unit 而在執行的時候的最後一個參數要傳入 () 來避免警告

物件中的 函式

要使用物件中的 function 的時候有特別的方式來綁定 @bs.send

type processType;
let foo = () => Js.log("foo");

[@bs.send] external nextTick: (processType, (unit) => unit) => unit = "";
[@bs.val] external process: processType = "process";

nextTick(process, foo);

note: 使用 send 之後的函式第一個參數就是綁定的物件

function foo() {
  console.log("foo");
  return /* () */0;
}

process.nextTick(foo);

Variadic Function Arguments

在 Javascript 中有的函數參數數量並沒有一定數量的

BuckleScript 支援這類的函式

但是前提是所有的類型都是要一樣的

遇到這種狀況就加上 bs.splice 在 external 的前面

[@bs.module "path"] [@bs.splice] external join: array(string) => string = "";
let v = join([|"a", "b"|]);
Js.log(v);

用 Join 當成範例

但是在 Javascript 中常常會有一些 function 可以任意丟入各種不同型態的參數

  1. 多個 external

你可以列舉多個 function 以不同的名稱綁定

[@bs.module "Drawing"] external drawCat: unit => unit = "draw";
[@bs.module "Drawing"] external drawDog: (~giveName: string) => unit = "draw";
[@bs.module "Drawing"] external draw: (string, ~useRandomAnimal: bool) => unit = "draw";
  1. Polymorphic Variant + bs.unwrap
function padLeft(value, padding) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

上面的範例中 padding 可能是數字也可能是字串

[%%raw "function padLeft(value, padding) {
  if (typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  if (typeof padding === 'string') {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}"];

[@bs.val]
external padLeft: (
  string,
  [@bs.unwrap] [
    | `Str(string)
    | `Int(int)
  ]
) => string = "";

let num = 4;
let str = "Message from BS: ";
padLeft("Hello world", `Int(num));
padLeft("Hello World", `Str(str));

利用 bs.unwrap 再加上 variants 的類型應用

可以得到一個參數多種輸入值得實作

約束參數

關於 fs.readFileSync 的第二個參數

他可以帶入一個字串其實內含 ascii, utf8

但是你仍然可以就像字串一樣綁定並使用

但是如果可以透過 variants + bs.string 來使用的話會更好

[@bs.module "fs"]
external readFileSync: (
  ~name: string,
  [@bs.string] [
    | `utf8
    | [@bs.as "ascii"] `useAscii
  ]
) => string = "";

readFileSync(~name="xx.txt", `useAscii);
  • [@bs.string] 使他以同樣的名稱做編譯
  • [@bs.as "foo"] 定義最後的名稱(alias)

另外你也可以利用 [@bs.int] 將參數編譯為 int 就像 [@bs.string]方式一樣

[@bs.val]
external test_int_type: (
  [@bs.int] [
    | `on_closed
    | [@bs.as 20] `on_open
    | `in_bin
  ])
  => int = "";

test_int_type(`in_bin);

note:on_closed 會編譯為 0, on_open 是 20 in_bin 則是 21

特別的案例 - Event Listeners

使用 readline 寫一個 Event 的範例

type readline;

[@bs.send]
external on: (
  readline,
  [@bs.string] [
    | `close(unit => unit)
    | `line(string => unit)
  ]
) => readline = "";

let register = rl =>
  rl
  -> on(`close(event => ()))
  -> on(`line(line => print_endline(line)));

output

function register(rl) {
  return rl.on("close", (function () {
              return /* () */0;
            }))
            .on("line", (function (line) {
              console.log(line);
              return /* () */0;
            }));
}

剛剛有談到 [@bs.send] 會將第一個參數當成物件

這種範例的話只能夠使用 -> 作為 Fast Pipe

|> 會造成錯誤

固定的參數

可以利用預定的參數給予外部的函數有時是很方便的

[@bs.val]
external process_on_exit: (
  [@bs.as "exit"] _,
  int => unit
) => unit = "process.on";

let () = process_on_exit(exit_code =>
  Js.log("error code: " ++ string_of_int(exit_code)));

[@bs.as "exit"]  以及 _ 放在一起代表你第一個參數希望放入的是字串 "exit"

也可以用 Json 關鍵字搭配

  • [@bs.as] [@bs.as {json|true|json}] - true
  • [@bs.as {json|{"name": "John"}|json}] - {"name": "John"}

Curry & Uncurry

Curry 指的是可以每次輸入較少的參數

在最後所有的參數都輸入完成後再回傳結果

let add: (int, int, int) => int;
let addFive: (int, int) => int;
let twelve: int;

add 必須丟入三個參數

addFive 則是已經取得第一個參數 還需要兩個參數

在 Javascript 這種動態語言中 currying 是危險的

忘記傳遞參數時在編譯時並不會有錯誤通知

因為上述的原因所以 函式有多個參數時

BuckleScript 很難 100% 的完整的轉譯為 Js 函式

  1. 所以當函式已經丟入所有參數時,轉譯不會做 currying 的動作,就像原本的 JS 函式一般呼叫使用
  2. 檢測函式是否完整很困難,所以會利用 module Curry 來處理
  3. 某些 JS APIs 像是 throttle debounce promise 和 curry 邏輯相違背的則是利用一般函式來使用

this-based callback

Js 中有很多使用會如下例

x.onload = function(v) {
  console.log(this.response + v)
}

在這個範例中如果是 Javascript 的邏輯來看

this 是指向 x

他並不是沒有回傳值所以宣告 unit => unit 是錯誤的

可以利用 [@bs.this] 範例如下

type x;
[@bs.val] external x: x = "";
[@bs.set] external set_onload: (x, [@bs.this] ((x, int) => unit)) => unit = "onload";
[@bs.get] external resp: x => int ="response";

set_onload(
  x, 
  [@bs.this] (
    (o, v) => Js.log(resp(o) + v)
  )
);

bs.thisbs 是一樣的

只是他第一個參數保留給 this

bs.get

之前好像有提過 不過我忘了

就再打一次

丟入一個物件值

固定取出某個欄位得值

[%%raw "var test2 = {'a': 'aa'}"];
type x;
[@bs.val] external test2: x = "";
[@bs.get] external getA: x => string = "a";

test2 -> getA -> Js.log;

上一篇
Express-MongoDB-PartII
下一篇
研究 - bs-mongodb
系列文
Some thing with Reason30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言