2016年5月24日火曜日

DoNe2

  • 動いてると思うけど書き殴りだから細かいことは気にしないでねちょっと整理してコメント追加。こういうリファクタリングも静的型の言語は本当に楽。
  • 単に機能が増えて面倒になっただけで、状態渡しのしかたは前回と同じ。ちなみに今回も頑張れば状態渡しすら使わずに書けそうだけど、これは使ったほうが楽かな。
  • メインループではなくハンドラでOCaml部分の状態渡しをしてるので、「メインループを書き直してるからスケールしない」というのとは少し違う。
  • (状態渡しや関数型云々とは無関係に)やっぱりJavaScript + Facebook React(functionalでないreactive programming)に比べれば、DOMかつ静的型だとちょっと長くなるね(定数倍ファクターなのでスケール云々とも無関係)。前回のから改造しててOCaml部分の不整合はデバッグしなくてもコンパイラが見つけてくれるので開発は明らかに楽だったけど。
    • 補足:静的型つけ言語に慣れてる人はよくやるのでわかると思いますが、一部だけ適当に改造・コンパイルしてみて型エラーが出たところから直す、を繰り返すだけでいつのまにか全体が完成して楽だった、という意味です。JavaScriptで同じことをやったらデバッグ地獄になります。ちなみに静的型っていうのは「イミュータブル」のことじゃないです……。あと、JavaScriptで変数にconstをつけても、参照されるオブジェクト(のフィールド)はconstにもfunctionalにもなりませんよ。
    • 慣れてきたので削除も実装してみた。もちリストの破壊的更新もメモリリークもしない。これは関数型プログラミングの基本的リスト操作と、数行のUI部分だけで本当に楽だった(5分ぐらい?)。
  • 俺もOCamlの勉強になったけど、「できない」と断言されたことをすぐにやって見せたんだから(それも3回。しかも他人に「課題」とか言った本人は1年がかり、かつ関数型ではなく全くの命令型。決して認めないようだが。)、もうつきあう義理はないよね。
  • 「中国製不正アクセス機器を使って誹謗中傷してる犯罪者集団」みたいな妄想はマジ勘弁。それを釣りで煽ってる奴ら(単独?)もな。

DoNe2

List#1
    module H = Dom_html
    
    (* js_of_ocamlやDOMやリストに関する一般的な補助関数 *)
    let g x = Js.Opt.get x (fun () -> assert false)
    let o = Js.Opt.return
    let s = Js.string
    let i = string_of_int
    let h f = H.handler (fun _ -> f (); Js._false)
    let rec rm_all_child p =
      Js.Opt.case (p##firstChild) (fun () -> ()) (fun c ->
        ignore (p##removeChild(c));
        rm_all_child p)
    let iteri_child n p f =
      let rec iteri_sib n c' f =
        Js.Opt.case c' (fun () -> ()) (fun c ->
          f n c;
          iteri_sib (n + 1) (c##nextSibling) f) in
      iteri_sib 0 p##firstChild f
    let rec add v pos = function (* 「リストのリスト」の第pos要素の末尾に要素vを追加 *)
      | [] -> assert false
      | items :: itemss when pos = 0 -> (items @ [v]) :: itemss
      | items :: itemss -> items :: add v (pos - 1) itemss
    let rec del pos n = function (* 「リストのリスト」の第pos要素の第n要素を削除 *)
      | [] -> assert false
      | items :: itemss when pos = 0 ->
          let rec del' n = function
            | [] -> assert false
            | _ :: items when n = 0 -> items
            | item :: items -> item :: del' (n - 1) items in
          del' n items :: itemss
      | items :: itemss -> items :: del (pos - 1) n itemss
    let iteri lst f = List.iteri f lst
    
    type state = { itemss : (Js.js_string Js.t) list list; cur : int }
    
    let () = H.window##onload <- h (fun () ->
      Firebug.console##debug(s "DoNe2 starting");
      let d = H.document in
      let e id = g (d##getElementById (s id)) in
      let tm = e "todo2_time" in
      let nu = g (H.CoerceTo.input (e "todo2_new")) in
      let selects = e "todo2_selects" in
      let selected = e "todo2_selected" in
      let input = g (H.CoerceTo.input (e "todo2_input")) in
      let go = g (H.CoerceTo.input (e "todo2_go")) in
      let ul = g (H.CoerceTo.ul (e "todo2_ul")) in
      (* ここまでDOMの準備 *)
    
      (* ここから本体 *)
      let _ =
        H.window##setInterval (* タイマーの設定 *)
          (Js.wrap_callback (fun () ->
            tm##textContent <- o (jsnew Js.date_now ()##toString ())),
           100.) in
    
      let rec configure_ui st = (* 状態を受け取ってUIを設定する関数 *)
        selected##textContent <- o (s ("List#" ^ i st.cur));
    
        let items = List.nth st.itemss (st.cur - 1) in
        go##value <- s ("NewDoNe#" ^ i (1 + List.length items));
    
        (* 各項目と削除ボタンの設定 *)
        rm_all_child ul;
        iteri items (fun n item ->
          let li = H.createLi d in
          li##textContent <- o item;
          let cancel = H.createInput ~_type:(s "submit") d in
          cancel##value <- s "Cancel";
          cancel##onclick <- h (fun () ->
            configure_ui { st with itemss = del (st.cur - 1) n st.itemss });
          Dom.appendChild li cancel;
          Dom.appendChild ul li);
    
        (* 項目追加ボタンの設定 *)
        go##onclick <- h (fun () ->
          let v = input##value in
          Firebug.console##debug(s ("DoNe2 adding to List#" ^ i st.cur ^ ": " ^ Js.to_string v));
          input##value <- s "";
          configure_ui { st with itemss = add v (st.cur - 1) st.itemss });
    
        (* 新規リスト作成ボタンの設定 *)
        nu##onclick <- h (fun () ->
          let new_list = H.createInput ~_type:(s "submit") d in
          let new_cur = selects##childNodes##length + 1 in
          let new_itemss = st.itemss @ [[]] in
          new_list##value <- s ("List#" ^ i new_cur);
          Dom.appendChild selects new_list;
          configure_ui { (* st with *) itemss = new_itemss; cur = new_cur });
    
        (* 各リスト選択ボタンの設定 *)
        iteri_child 0 selects (fun n select' ->
          let select = g (H.CoerceTo.input (g (H.CoerceTo.element select'))) in
          select##onclick <- h (fun () ->
            configure_ui { st with cur = 1 + n })) in
    
      (* 初期状態でUIを設定 *)
      configure_ui { itemss = [[]]; cur = 1 })
    

    0 件のコメント:

    コメントを投稿