c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud 等に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開発中です。

Clojerl から Erlang を呼ぶ: How to interoperate with Erlang and Clojerl

何でこの document が無いんだ。無かったから書いた。英語にしたいが氣力が無い。

Clojure Advent Calendar 2020 が空いてゐたので參加しやう。

やってみる: Getting started

先づ rebar3_clojerl で project を作ろう。First, create a project by rebar3_clojerl.

rebar3 を install する。

github.com

$ sudo wget -O /usr/local/bin/rebar3 https://s3.amazonaws.com/rebar3/rebar3
$ sudo chmod +x /usr/local/bin/rebar3
$ rebar3 local install

Clojerl project の雛形を作る爲に global な rebar.config に rebar3_clojerl を追加する。

github.com

$ cat ~/.config/rebar3/rebar.config
{plugins, [rebar3_clojerl]}.

project を作る。

$ rebar3 new clojerl_app example
===> Writing example/src/example/core.clje
===> Writing example/src/example/app.clje
===> Writing example/src/example/sup.clje
===> Writing example/src/example.app.src
===> Writing example/test/example/core_test.clje
===> Writing example/rebar.config
===> Writing example/.gitignore
===> Writing example/LICENSE
===> Writing example/README.md
$ cd example

REPL を起動してみよう。雛形の rebar3_clojerl の ver. が古いので上げる。

diff --git a/rebar.config b/rebar.config
index f7132a2..62590c3 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,4 +1,4 @@
 {erl_opts, [debug_info]}.

 {deps, [{clojerl, "0.7.0"}]}.
-{plugins, [{rebar3_clojerl, "0.8.3"}]}.
+{plugins, [{rebar3_clojerl, "0.8.4"}]}.

そして起動する。

$ rebar3 clojerl repl
===> Fetching rebar3_clojerl v0.8.4
===> Analyzing applications...
===> Compiling rebar3_clojerl
===> Verifying dependencies...
===> Fetching clojerl v0.7.0
===> Analyzing applications...
===> Compiling clojerl
===> Analyzing applications...
===> Compiling example
===> Getting log of git repo failed in /Users/ne-sachirou/dev/example. Falling back to version 0.0.0
===> Clojerl Compiling clojerl
===> Compiling clojure/core.clje...
===> Compiling clojure/pprint.clje...
===> Compiling clojure/core/reducers.clje...
===> Compiling clojure/core/server.clje...
===> Compiling clojure/data.clje...
===> Compiling clojure/zip.clje...
===> Compiling clojure/repl.clje...
===> Compiling clojure/test.clje...
===> Compiling clojure/xml.clje...
===> Compiling erlang/core.clje...
===> Clojerl Compiling example
===> Compiling example/core.clje...
===> Compiling example/sup.clje...
===> Compiling example/app.clje...
=INFO REPORT==== 24-Dec-2020::00:17:04.695885 ===
    application: clojerl
    exited: stopped
    type: permanent

Clojure 0.7.0
clje.user=>

Clojure の函數を呼んでみよう。

clje.user=> (str "momonga" "MOMONGA")
"momongaMOMONGA"
clje.user=> (-> "momonga" (str "MOMONGA"))
"momongaMOMONGA"
clje.user=> (let [m1 "momonga" m2 "MOMONGA"] (str m1 m2))
"momongaMOMONGA"
clje.user=> (let [[m1 m2] ["momonga" "MOMONGA"]] (str m1 m2))
"momongaMOMONGA"
clje.user=> (let [{m1 :m1 m2 :m2} {:m1 "momonga" :m2 "MOMONGA"}] (str m1 m2))
"momongaMOMONGA"
clje.user=> (let [{:keys [m1 m2]} {:m1 "momonga" :m2 "MOMONGA"}] (str m1 m2))
"momongaMOMONGA"

Clojure の標準 library は入ってゐないが、基本的な函數は使へる。

いよいよ Erlang の函數を呼んでみよう。

clje.user=> (math/log2 65536)
16.0
clje.user=> (base64/encode "momonga")
"bW9tb25nYQ=="

OTP を Clojerl の標準 library として使へる訣だ。

Erlang の supervisor を使ってみよう。inets module を使って簡單な HTTP server を立てる。inets application が起動してゐる必要があるから src/example.app.src に書く。

diff --git a/src/example.app.src b/src/example.app.src
index 1b8fb83..678771e 100644
--- a/src/example.app.src
+++ b/src/example.app.src
@@ -6,6 +6,7 @@
     , [ stdlib
       , kernel
       , clojerl
+      , inets
       ]
     }
   , {mod, {'example.app', []}}

4000 番 port で待ち受け、priv/public を公開する HTTP server は Erlang ではこう書く。

PublicDir = filename:join(code:priv_dir(:example), "public"),
{ok, _} = inets:start(
  httpd,
  [
    {port, 4000},
    {server_name, "example"},
    {server_root, PublicDir},
    {document_root, PublicDir},
    {bind_address,"localhost"}
  ]
).

そのまま Clojerl に直すとこうだ。

(let [public-dir (binary/bin_to_list (filename/join (code/priv_dir :example) "public"))]
  (inets/start :httpd
               #erl(#erl[:port 4000]
                    #erl[:server_name #erl"example"]
                    #erl[:server_root public-dir]
                    #erl[:document_root public-dir]
                    #erl[:bind_address #erl"localhost"])))

これを supervisor にぶら下げよう。src/example/core.clje に實裝を書き、src/example/sup.clje に有る supervisor にぶら下げる。

diff --git a/priv/public/index.html b/priv/public/index.html
new file mode 100644
index 0000000..2e52729
--- /dev/null
+++ b/priv/public/index.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<title>Clojerl</title>
+<p>Clojerl</p>
diff --git a/src/example/core.clje b/src/example/core.clje
index a0c7d74..9dc114f 100644
--- a/src/example/core.clje
+++ b/src/example/core.clje
@@ -1 +1,17 @@
 (ns example.core)
+
+(defn start-link []
+  (gen_server/start_link #erl[:local :example.core]
+                         :example.core
+                         #erl()
+                         #erl()))
+
+(defn init [_]
+  (let [public-dir (binary/bin_to_list (filename/join (code/priv_dir :example) "public"))]
+    (inets/start :httpd
+                 #erl(#erl[:port 4000]
+                      #erl[:server_name #erl"example"]
+                      #erl[:server_root public-dir]
+                      #erl[:document_root public-dir]
+                      #erl[:bind_address #erl"localhost"])))
+  #erl[:ok nil])
diff --git a/src/example/sup.clje b/src/example/sup.clje
index 4c5f198..f8e58fd 100644
--- a/src/example/sup.clje
+++ b/src/example/sup.clje
@@ -4,7 +4,11 @@
                     :intensity 1
                     :period    5})

-(def child-specs #erl())
+(def child-specs #erl(#erl{:id :example.core
+                           :start #erl[:example.core
+                                       :start-link
+                                       #erl()]
+                           :restart :permanent}))

 (defn start-link []
   (supervisor/start_link #erl[:local :example.sup]

REPL を再起動し、application を呼び出さう。

clje.user=> (example.app/start :normal nil)

http://localhost:4000/index.html を見ると先の index.html の中身が見える筈だ。

Hex.pm の library も使へる。

Recon を入れて VM の樣子を覗いてみよう。Recon は Erlang 實行環境の樣子を安全に知る事のできる debug tool だ。Erlang VM を運用するなら是非入れておくといい。

ferd.github.io

diff --git a/rebar.config b/rebar.config
index 90fa363..e476f18 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,5 +1,6 @@
 {erl_opts, [debug_info]}.

-{deps, [{clojerl, "0.7.0"}]}.
+{deps, [{clojerl, "0.7.0"},
+        {recon, "2.5.1"}]}.
 {plugins, [{rebar3_clojerl, "0.8.4"}]}.

install する。

$ rebar3 upgrade
===> Analyzing applications...
===> Verifying dependencies...
===> Fetching recon v2.5.1
===> No upgrade needed for clojerl
===> No upgrade needed for recon

REPL を立ち上げて呼んでみよう。今囘は VM の樣子を覗いてみる。普通に呼ぶと Erlang の term が返ってくる。

clje.user=> (recon/node_stats_list 1 1000)
#erl(#erl[#erl(#erl[:process_count 81] #erl[:run_queue 0] #erl[:memory_total 63948120] #erl[:memory_procs 20665656] #erl[:memory_atoms 622772] #erl[:memory_bin 1882056] #erl[:memory_ets 6677800]) #erl(#erl[:bytes_in 0] #erl[:bytes_out 0] #erl[:gc_count 7] #erl[:gc_words_reclaimed 110416] #erl[:reductions 60219810] #erl[:scheduler_usage #erl(#erl[1 0.008229647869447678] #erl[2 0.0016643060189602482] #erl[3 0.0010973392240004188] #erl[4 0.0014953839650194726] #erl[5 0.0013679809490133251] #erl[6 0.0015487337521235827] #erl[7 0.0010311711991118494] #erl[8 0.0015200885780010886] #erl[9 0.0012850169590631723] #erl[10 0.00136601901759231] #erl[11 0.0014677412982497728] #erl[12 0.0011447580649143999] #erl[13 0.0013334281548910145] #erl[14 0.0013403316357774714] #erl[15 0.0013136567958034098] #erl[16 0.0012386041504103987] #erl[17 0.0] #erl[18 0.0] #erl[19 0.0] #erl[20 0.0] #erl[21 0.0] #erl[22 0.0] #erl[23 0.0] #erl[24 0.0] #erl[25 0.0] #erl[26 0.0] #erl[27 0.0] #erl[28 0.0] #erl[29 0.0] #erl[30 0.0] #erl[31 0.0] #erl[32 0.0])])])

erl->clj 函數で Clojure の data に變換できる。

clje.user=> (erl->clj (recon/node_stats_list 1 1000))
([([:process_count 81] [:run_queue 0] [:memory_total 63989136] [:memory_procs 20700568] [:memory_atoms 622926] [:memory_bin 1885320] [:memory_ets 6679160]) ([:bytes_in 0] [:bytes_out 0] [:gc_count 1] [:gc_words_reclaimed 27412] [:reductions 117717] [:scheduler_usage ([1 3.095390962856307e-5] [2 7.938116639390832e-4] [3 2.2965689260245194e-5] [4 2.7958342070315232e-5] [5 2.895673863248307e-5] [6 2.795823040377676e-5] [7 3.0954095077000806e-5] [8 3.794331269083989e-5] [9 2.9955276771779733e-5] [10 3.1952295223231713e-5] [11 3.794331269083989e-5] [12 3.594629623342726e-5] [13 3.095378599750572e-5] [14 3.09535696455317e-5] [15 3.095369327486081e-5] [16 3.295070574420667e-5] [17 0.0] [18 0.0] [19 0.0] [20 0.0] [21 0.0] [22 0.0] [23 0.0] [24 0.0] [25 0.0] [26 0.0] [27 0.0] [28 0.0] [29 0.0] [30 0.0] [31 0.0] [32 0.0])])])

pretty print するには OTP の io module を使ふ。

clje.user=> (io/fwrite "~120p~n" #erl((recon/node_stats_list 1 1000)))
[{[{process_count,81},
   {run_queue,0},
   {memory_total,64171600},
   {memory_procs,20845064},
   {memory_atoms,623317},
   {memory_bin,1903656},
   {memory_ets,6685032}],
  [{bytes_in,0},
   {bytes_out,0},
   {gc_count,1},
   {gc_words_reclaimed,25919},
   {reductions,120052},
   {scheduler_usage,[{1,5.4930677485013915e-5},
                     {2,9.128479858418676e-4},
                     {3,2.2971010584642182e-5},
                     {4,3.495588567228158e-5},
                     {5,3.2958406491008346e-5},
                     {6,2.696596894718865e-5},
                     {7,4.694076075992098e-5},
                     {8,2.896344812846188e-5},
                     {9,5.0935719122467445e-5},
                     {10,3.096058717252937e-5},
                     {11,2.5967151553285093e-5},
                     {12,5.1934354975311204e-5},
                     {13,6.491736020046481e-5},
                     {14,2.596697001414201e-5},
                     {15,2.896336134789491e-5},
                     {16,2.9962337341961154e-5},
                     {17,0.0},
                     {18,0.0},
                     {19,0.0},
                     {20,0.0},
                     {21,0.0},
                     {22,0.0},
                     {23,0.0},
                     {24,0.0},
                     {25,0.0},
                     {26,0.0},
                     {27,0.0},
                     {28,0.0},
                     {29,0.0},
                     {30,0.0},
                     {31,0.0},
                     {32,0.0}]}]}]
:ok

VM の樣子がよく解る。

Erlang との遣り取り

data 型

Erlang の函數に渡せる data は Clojerl では以下の樣に書く。

Erlang Clojerl Elixir
整數 -42 -42 -42
浮動小數点數 -42.29e3 -42.29e3 -42.29e3
atom atom or 'atom' :atom :atom
charlist "charlist" #erl"charlist" 'charlist'
binary <<"binary">> "binary" "binary" or <<"binary">>
list [Item, Item] #erl(item item) [item, item]
tuple {Element, Element} #erl[element element] {element, element}
map #{Key => Value} #erl{key value} %{key => value}

list は Clojure の list '(item item) に由來し、tuple は vector [item item] に、map は map {key value}#erl reader macro を附けたものである。

clj->erl 函數と erl->clj 函數で變換してもいい。

函數を data にするには arity を指定しなければならない。

clje.user=> math/log.1
#<math/log>
clje.user=> example.app/start.2
#<example.app/start>

data にした函數は高階函數に渡せる。

clje.user=> (lists/map (fn [x] (math/sin x)) #erl(1 2))
#erl(0.8414709848078965 0.9092974268256817)
clje.user=> (lists/map #(math/sin %) #erl(1 2))
#erl(0.8414709848078965 0.9092974268256817)
clje.user=> (lists/map math/sin.1 #erl(1 2))
#erl(0.8414709848078965 0.9092974268256817)

函數の呼び出し

函數の呼び出しは自然である。a.b module の f 函數は、

(a.b/f arg1 arg2)

で呼べる。Clojerl で定義した函數も Erlang で定義した函數も同じである。

project 管理

rebar3 を使ふといい。rebar3_clojerl を入れておけば便利な command を使へる。

github.com

$ rebar3 new --help
app (built-in): Complete OTP Application structure.
clojerl_app (plugin): Complete Clojerl application structure
clojerl_escript (plugin): Complete Clojerl escriptized application structure
clojerl_lib (plugin): Complete Clojerl library (no processes) structure
clojerl_release (plugin): Clojerl release structure for executable programs
cmake (built-in): Standalone Makefile for building C/C++ in c_src
escript (built-in): Complete escriptized application structure
lib (built-in): Complete OTP Library application (no processes) structure
plugin (built-in): Rebar3 plugin project structure
release (built-in): OTP Release structure for executable programs
umbrella (built-in): OTP structure for executable programs (alias of 'release' template)
$ rebar3 help clojerl

clojerl <task>:
  compile           Compile clojerl project
  escriptize        Generate escript archive.
  release           Build a release for the Clojerl project.
  repl              Start a clojerl repl
  run               Run the project's -main function.
  test              Test clojerl project