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

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

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

音樂は SoundCloud に公開中です。

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

Programming は GitHub で開發中です。

Elixir で stateful なアプリケーションを作るのは簡單です

Let's create stateful systems, by Elixir. Elixir で stateful なアプリケーションを作るのは簡單です。

Elixir の得意な事としてよく眞っ先に擧げられるのは竝列性 parallelism です。確かに、簡單で安全に或る程度效率好く parallel にできるのは Elixir の得意とするところです。しかし parallel である事が得意な言語/環境は他にも在ります。Go は人気で簡單に parallel に成り、そこまで安全ではありませんがそこまで危險でもなく、高速です。Clojure (core.async) は parallel な變換處理を簡單に構築し組織できます。Akka (Scala) は Erlang と殆ど同じ思想で作られ、殆ど同じ事を實現できます。また SIMDGPU 等の parallelism を扱ふのは、Erlang VM は未だ苦手です (これも得意にしようと云ふ試みは在り、樂しみです)。

少なくとも今のところ、Elixir が他の言語/環境と比べて得意なのは、stateful なアプリケーションを作り運用する事です。stateful なアプリケーションを長期間運用するのには面倒な前提を色々と滿たさなければなりませんんが、Elixir と Erlang VM にはその仕組みが備はってゐるので簡單ですね、と云ふ話を以下でしました。GenStage や Phoenix.Channel は典型的な stateful アプリケーションです。

Let's create stateful systems, by Elixir

數年前に Elixir に關する記事を書く事が流行って以來、Elixir に入門する情報は充實してゐます。これは同じやうな內容でも場に細かく合はせて繼續して書かれる事の要る領野なので、今後も増えてゆく事が期待されますが、Elixir に入門し使へる段階から、Elixir で優れた機能を作る爲の、と云ふ中間的な段階の資料は全く不足してゐます。その資料の一つであるべく、Elixir の表面的な動作を説明するものが以下です。

Elixir 完全に理解した

Advanced resources

實際の know how を開示したものが以下です。

一つ目は Elixir でのリアルタイム對戰システムの開發開始から運用迄で、困った事とその解決に就いて亂雜且つ網羅的に載せたものです。

ステートフルで大規模アクセスのある soft-realtime なゲームサーバーを easy につくる

二つ目以降は、上のそれぞれの topic を展開したものです。下は、Phoenix.Channel で二種類のアプリケーションを作った時に、どちらも scale in/out せず、それぞれ scale in/out させる手法が異なった事を説明したものです。

Phoenix at Scale

これには、process 間通信は μ sec の速度でしか動かないので、process を使はずに state を管理したりアプリケーションの構造を組み立てる中で、他の函數型言語と共通はするものの、Elixir 特有の困難を解く幾つかの方法を載せました。

DDD: Data Driven Development

Elixir を Docker container で開發し Kubernetes で運用する時の tips をあらんかぎり載せました。載せてゐないのは多分 CI/CD に就いてくらいだと思ひます。どこかで書けるとよいですね。

Elixir on Containers

今後

個人的には Elixir の商用運用からは離れてしまったので、個人的な研鑽のできるところを見附けつつ、何かします。

ヨコガナの紹介

英語を書くのであれば Latin 文字とアラビア數字を使って左から右へ書く。日本語は長年の書き方の變化を結構保存してゐて、書字方向は上から下への縱書きと左から右への横書き、半世紀ほど前であれば又今でもトラックの側面等に右から左への横書きを使ふ。字の種類には先ず漢字が在り、漢字には略字を含め舊字體と新字体が在るし、慣れてゐない人間から見ると楷書體と草書體とが同じ字だと云ふ事は判りづらい。かな文字にひらがなとカタカナが在り、更に Latin 文字とアラビア數字も多用する。私は普段文を手書きしてゐ、先の內で日々使ってゐるのは、漢字 (新字体)、ひらがな、カタカナ、Latin 文字 + アラビア數字である。これに加えて私は「ヨコガナ」と「縦 Latin」と云ふ文字を使ってゐる。このヨコガナを紹介したい。

ヨコガナはかな文字であり、ひらがなやカタカナと同種のものだ。「ヨコガナ」と云ふ語をヨコガナで書くとかう成る。

ヨコガナ

ヨと書きコと書きカに濁點が附きナと書いてある。ヨコガナの字はひらがなやカタカナの字と一對一に對應する。

ヨコガナは私の知人が作った字で、私が使ってゐるものはそれとは幾つか字の形を變へてある。ひらがなの替はりに作られた文字だ。ひらがなは昔日本語を縦に連綿して書く樣に使はれ始め、縦に速く美しく書く事が出來る。ここ半世紀ほどで日本語が左から右へ橫に書かれるやうに成り、ひらがなも橫に書かれるやうになった。手書きでも橫である。この時迄にひらがなは活字體を基に作り直され、連綿せず一字づつ離して書く事が多く成った。かう成ったひらがなを速く且つ美しく書く方法は自明ではなく、各人が各人の工夫を凝らしてきた (丸文字はその工夫の一つであらう)。私の知人はそこにもう一つ、各人の工夫を編み出したのである。

ヨコガナはかなを連綿して書けるやう、Latin やキリルの筆記體を參考にしてかな文字を置き換へてある。對應表が有るから載せよう。

友人に依るもの。

カタカナ → ヨコガナ對應表 1

私のもの。

カタカナ → ヨコガナ對應表 2

一字づつ紹介するのも興味深いが、これはもう書けば覺えるのであるから、書例を載せる事で代へる。書例は Pinterest に集めてある。

ヨコガナ

ヨコガナは好く出來てあり、書く者毎に字の形をそれなりに自由に出來るやうに成ってゐる。アの高さを私はタの高さ迄上げて書くが、友人はイと同じ高さで書く。ケの第一畫を友人はアラビア數字の 2 のやうに丸めるが、私は鋭角に角ばらせる。ニに在る s のやうな形は上から下ろしても好いし、下から跳ね上げても好い。ルの第ニ畫も同じく言へる。等。これらで字の印象は可成り變はる。辨別が出來る限りは書き易い形を見附けて書くのが好いと思ふ。

雜=多樣性と云ふ概念の假説

雜と云ふ在り方の概念を重視出來るのではないかと云ふ考へを育ててゐる。はっきりと際立った概念ではないが、雜をどう定めるのが好いかと云ふ假説は持って有り、細分を續けても常に更に細分出來る狀態である在り方を雜と名附けやうとしてゐる。私は今 programmer として在り、いわく定め難い team と云ふ集まりで過ごしてゐる。此う云った生活に適合してゆく事と、私が古く育んだ考へとを繋げる事が出來ず、其の成り行きでどうしたら眞である事が出來るかを長く考へ、長く考へ過ぎてゐるが、其所で眞と主體は近い形をしてゐると考へるやうに成った (發言の超越論的な根拠)。其の反対側である、日々に適合すると云ふ側から雜と云ふ概念を考へればどこかで衝突するのではないかと思ってゐる。

假説としては二つが有る。

  • 多樣性と云ふ概念を重視出來るのではなからうか。
  • 其の多樣性と云ふ概念は上記で定まる雜と云ふ語で名附けるのが有効ではないか。

雜を上の樣に定める事は、言葉や認識や行為に依っては汲み尽くせない實在を考へる事に相當する。しかし體驗に依れば雜には過去に於ける蓄財が要り未來で蓄財を汲み出すと云ふ前提が要る。此れは雜が感覺に依って汲み尽くされないものではあるがしかし感覺されるものである、或いは感覺されるものでもあるからだと思ふ。蓄財が尽きれば雜は在り方から去って了ふ。

名實の組みを建てるならば、名を先に取り名 (普遍) が在れば對應する實が實在する、不當な名は名同士の關係で定まると云ふ考へ (礼) と、實を先に取り實 (個物) に適切な名を附ける、不當な名は實が無い事で定まると云ふ考へ (生得) が在る。雜はどちらでもない。雜は不當な名の不當さが一意である事を更に細分する。名が名である事と實が實である事は名實に於いては一意であり、雜は其の一意である事を更に細分する。此の細分は手續きであり終はらない手續きであるから過ぎゆくものとしての時間である。そこで雜は蓄財と汲み尽くしと云ふ形で瞬間であり、細分と云ふ形で時間である。

此の記事や考へそのものが、より基になる根拠が無いと云ふ事で雜であるが、日常の考へ方を素直に氣遣ひ延ばしてゆくと云ふ事ではストア派風ではある。此の素直と云ふ概念は、似てゐるものを似てゐるとし似てゐないものを似てゐないとすると云ふ風に定まると考へてゐた事が有った。今は好く解らない。

Develop Google Apps Script in ClojureScript

qiita.com🎂

What is Google Apps Script?

f:id:Kureduki_Maari:20181223190842p:plain

Google Apps Script (以下 Apps Script) は、Google の server 上での serverless な JavaScript 實行環境です。serverless な JavaScript 實行環境ですので Cloud Function の仲間です。函數を起動できる event の種類が限られている、また SLA (Service Level Agreement) が不明である為、Cloud Function のやうに大規模な servise を作る事は、できません。代はりに、Docs や Gmail 等[など]の Google Apps と連携するやう簡單に設定できる為、この意味で VBA (Visual Basic for Applications) の仲間でもあります。更に Google Apps が使へる環境であれば無料です。その代はり一日当りの實效囘數等が制限されてゐます。

Apps Script と云へば Google Apps 向けの VBA である、と思はれる向きもあり、Gmail と Spreadsheet を同時に見たり更新したりできると云ふ使ひ途もあるのですが、cron の代はりとして Google Apps に全く觸らない函數も作れます。であるので、Google Apps account で使へる serverless FaaS (Function as a Service) だと見るのがよいです。今の所書ける言語は JavaScript だけです。

Google Apps の web site 上に Apps Script の editor があるのですが、ただの text editor であり、version 管理も難しく、複數人で編集したり test を書いたり library を使ったりする事ができません。programmer ですので、日々使ってゐる editor (Emacs) で書き、日々使ってゐる version 管理 system (Git) で管理し、人間にも讀める code を書きたいものです。これをお膳立てしてくれるのが clasp です。

github.com

Apps Script 用の CLI tool であり、local の script を upload する、Google Apps 上の script を download する、Google Apps 上の script を實行する、Google Apps 上の現在の version に tag を打つ、Apps Script が吐いた log を見る、等の事ができます。まともな開發に要る事はできさうです。人間でゐませう (發狂せずとも濟みさうと云ふ意)。

What is ClojureScript?

f:id:Kureduki_Maari:20181223190914p:plain:w128

ClojureScript (a.k.a. cljs) は Clojure の實裝の一つです。Clojure と云へば JVM 上の Lisp の一つとして生まれましたが、その後.NET CLR 上でも實裝され、JavaScript への transpiler も作られました。この二つは Clojure と同じ或いは近しい community で作られてゐます。他にも Erlang VM 上で動くclojerlや、C に transpile するclojurec (これの開發は停まってゐます) もあります。廣く實行環境を見れば、Android 上で動かすlein-droid (これも開發が停まってゐます)、React Native で動かすRe-Natal、Unity で動くArcadia等があり、更には Graal で JVM 用の Clojure を native code に落とせるので (Graal が MySQL に搭載されたらそこでも動く) (clojure-clr を Mono LLVM に流すと云ふ手も在ります)、どこででも動きますね (Ethereum の contract 自體を書く方法は見附からないので EthereumVM では動きませんね)。CloduinoArduino も弄れるみたいです。珍しい所ではQuilから Processing で映像を描けたり、Overtoneから SuperCollider で音樂を生成したりできます。

Clojure は immutable な data を基礎とし、vector や map 等の data 構造 & 使ひ易い記法を持った Lisp です。Lisp である事が我々にとってはまづ一つの利点ですし、data が immutable ですから、program を抽象化し易く、concurrency を簡單に扱へます。ClojureScript は Clojure から JavaScript への transpiler です。JVMAPI は使へませんが、Clojure の core API の大半が使へますし、JavaScript を簡單に呼び出せます。無論 NPM も使へます。

Isomorphic と云ふ言葉は懐かしいですが、懐かしくなったからといって重要で無くなった譯ではありません。Reagent 等便利な Web frontend framework が在ります。

Develop Apps Script in ClojureScript.

以下の二つを install します。

  • cljs : Mac であればbrew install clojurescriptで ClojureScript を、或いはbrew install leiningenで Leiningen を入れて project 毎に install します。
  • clasp : NPM を入れ、npm install -g @google/claspで入れます。

私は Leiningen で project を作ったので、こんなproject.cljができます。

(defproject apple-is-dead "0.1.0"
  :description "Appleの障害情報を監視し通知する"
  :url "〜〜〜"
  :license {:name "Do What The F*ck You Want To Public License"
            :url "http://sam.zoy.org/wtfpl/COPYING"}
  :plugins [[jonase/eastwood "0.3.3"]
            [lein-ancient "0.6.15"]
            [lein-cljfmt "0.5.7"]
            [lein-cljsbuild "1.1.7"]]
  :dependencies [[org.clojure/clojure "1.9.0"]
                 [org.clojure/clojurescript "1.10.439"]]
  :hooks [leiningen.cljsbuild]
  :aliases {"lint" ["run", "-m" "apple-is-dead.tasks.lint/go"]
            "release" ["run", "-m" "apple-is-dead.tasks.release/go"]}
  :cljsbuild {:builds
              [{:source-paths ["src"]
                :compiler {:main apple-is-dead.main
                           :output-to "release/main.js"
                           :output-dir "target"
                           :optimizations :advanced
                           :pretty-print false
                           :foreign-libs [{:file "src/entrypoint.js"
                                           :provides ["apple-is-dead.entrypoint"]}]
                           :externs ["src/extern.js"]}}]})

lein-cljsbuild が、Leiningen に ClojureScript を build するやり方を教えます。

  :hooks [leiningen.cljsbuild]
  :cljsbuild {:builds
  〜〜〜

と hook すると、lein compileで ClojureScript の file である*.cljsを探して JavaScript に transpile します。

:aliasesの lint では

prettier --write src/*.js
lein cljfmt fix
lein ancient check
lein eastwood

のやうな事をさせてゐ、release では

lein compile
clasp push

のやうな事をさせてゐます。

作った ClojureScript の project 内でclasp create 〜〜〜を叩き、Apps Script project を作ります。この時に作られる file 達が大切なので、これらを cp しておき、これらが在る diractory で以後clasp command を叩きます。

あとは ClojureScript と Apps Script の referense を御覽ください。Apps Script では log を見たり error を mail に通知する事ができますので、そこを適切に作ると運用が樂になります。error を catch する時は(js/console.error err)で log を吐く、實行の重要な step 毎に(js/console.info message)で log を吐く、catch すべきでない error を catch しない、retry されてもよいやうに冪等にする等、batch 處理を書く時と似た事を注意する筈です。

:foreign-libs:externsは載せておきます。

                           :foreign-libs [{:file "src/entrypoint.js"
                                           :provides ["apple-is-dead.entrypoint"]}]
                           :externs ["src/extern.js"]

:foreign-libsJavaScript から ClojureScript を呼ぶ時に要ります。Apps Script は書かれたものが JavaScript であらうと前提して呼び出しますから、JavaScript 側から呼べる interface を開けておかなければなりません。ここに指定した file に JavaScript を書いておくと、それは Closure Compiler (ClojureScript が Closure Compiler を呼び出して、code を圧縮したり dead code を消したりします) で變換されずに、出力される JavaScript にそのまま載ります。

// entrypoint.js
function main(evt) {
  apple_is_dead.main.main();
}
; main.cljs
(defn ^:export main []
  (doseq [service ((apple.get-status) :services)]
    (check-apple-service service)))

これで Apps Script はmain()を呼び出せ、それによりapple_is_dead.main.main()、すなはち(apple-is-dead.main.main)を實行できます。

:externsは、Closure Compiler が圧縮してはいけない變數名を列擧する file です。これらは外の環境に global に定義されてゐるから、このままの名前で呼び出さなければならない、例えば JavaScript の core に定義された module 達ですが、これらはどうやら Closure Compiler が既に知ってゐます。しかし Closure Compiler は Apps Script の事は知りません。それでも Closure Compiler はたいていうまくやりますが、一部見逃してしまふやうです。私の場合はCacheServiceの事は教えてやらねばなりませんでした (他にも色々呼び出したのですが、これ以外はうまく、圧縮せずにゐてくれました) (Closure Script の實裝を読んでゐないのが惡いのですが)。

// extern.js
var CacheService = {};
CacheService.getScriptCache = function () {};

これで ClojureScript で Apps Script を開發できると思ひます。

The Best of Access @ Elixir

Immutable data is a foundation of abstraction - it localizes program computation. So immutable data makes concurrency easy & decreases bugs. All data in Erlang/Elixir is immutable. We are happy to use functional programming techniques, worrying about nothing on parallel programming.

To lookup in a nested data is easy. When there is a map below :

v = %{
  x: [
    {:ok, a: 1, b: 2},
    {:ok, a: 3, b: 4},
  ]
}

Following codes are same.

1 = elem(Enum.at(v.x, 0), 1)[:a]

1 = elem(Enum.at(v[:x], 0), 1)[:a]

1 = (v.x |> Enum.at(0) |> elem(1))[:a]

# We can pipe all.
1 = v |> Map.get(:x) |> Enum.at(0) |> elem(1) |> Keyword.get(:a)

1 = v |> Access.get(:x) |> Enum.at(0) |> elem(1) |> Access.get(:a)

1 = get_in(v.x, [Access.at(0), Access.elem(1)])[:a]

1 = get_in(v, [:x, Access.at(0), Access.elem(1), :a])

We can use pattern matching too.

1 = with %{x: [{:ok, a: a, b: _} | _]} = v, do: a

1 = case v, do: (%{x: [{:ok, a: a, b: _} | _]} -> a)

true = match?(%{x: [{:ok, a: 1, b: _} | _]}, v)

But it's hard to UPDATE a nested data.

v_2 = Map.update!(v, :x, fn x ->
  List.update_at(x, 0, fn {:ok, k} ->
    {:ok, Keyword.update!(k, :a, &(&1 + 1))}
  end)
end)

1 = elem(Enum.at(v.x, 0), 1)[:a]
2 = elem(Enum.at(v_2.x, 0), 1)[:a]

Why this's hard? The nested callback functions is complecated. It's not composable, so we can't split this into simple parts. Is there no way?

JavaScript - Immer

Let's see some examples in other languages.

Data in JavaScript isn't immutable. But after React & Redux comes, JavaScript loves immutability. Immer is a nice library to update nested data with immutability.

f:id:Kureduki_Maari:20181205120623p:plain

mweststrate/immer: Create the next immutable state by mutating the current one

import produce from "immer";

const v = {
  x: [
    ["ok", { a: 1, b: 2 }],
    ["ok", { a: 3, b: 4 }],
  ],
};

const v2 = produce(v, (v) => {
  v.x[0][1].a += 1;
});

assert.equal(v.x[0][1].a, 1);
assert.equal(v2.x[0][1].a, 2);

Data in JavaScript is mutable, so it has short syntax to update data. Immer use it. It Proxy the data, listen all mutation operations & create a new data. This's the way in a mutable language to update nested data with immutability.

Lens @ Haskell

f:id:Kureduki_Maari:20181205121329p:plain

Haskell is close to Elixir because data in Haskell is immutable. Haskell has a nice mechanism - Lens.

lens: Lenses, Folds and Traversals

microlens: A tiny lens library with no dependencies. If you're writing an app, you probably want microlens-platform, not this.

Lens is a set of functions of composable getter & setter.

{-# LANGUAGE TemplateHaskell #-}
import Lens.Micro
import Lens.Micro.TH

data Item = Item { _a :: Integer, _b :: Integer } deriving (Show)
makeLenses ''Item

data V = V { _x :: [(String, Item)] } deriving (Show)
makeLenses ''V

v = V {
  _x = [
      ("ok", Item { _a = 1, _b = 2 }),
      ("ok", Item { _a = 3, _b = 4 })
    ]
  }

main = do
  print $ v ^?! to _x . ix 0 . _2 . to _a
  -- 1

  print $ v ^?! x . ix 0 . _2 . a
  -- 1

  print $ v & x . ix 0 . _2 . a .~ 2
  -- V {_x = [("ok",Item {_a = 2, _b = 2}),("ok",Item {_a = 3, _b = 4})]}

  print $ v & x . ix 0 . _2 . a %~ (+ 1)
  -- V {_x = [("ok",Item {_a = 2, _b = 2}),("ok",Item {_a = 3, _b = 4})]}

  print $ v & (x . ix 0 . _2 . a) +~ 1
  -- V {_x = [("ok",Item {_a = 2, _b = 2}),("ok",Item {_a = 3, _b = 4})]}

Lens is just a function, so we can compose them & create custom Lens (makeLenses dose).

Clojure - a neighbor of Elixir

f:id:Kureduki_Maari:20181205121313p:plain

In ancient days Elixir was born from Clojure. So we look at that. Clojure is a functional language which is based on immutable data.

get-in, assoc-in & update-in are the basic tools to manipulate nested data.

(def v
  {:x [["ok" {:a 1, :b 2}] ["ok" {:a 3, :b 4}]]})

; get
; => 1
((((v :x) 0) 1) :a)
(:a (((:x v) 0) 1))
(:a (get (get (:x v) 0) 1))
(-> v :x (get 0) (get 1) :a)
(get-in v [:x 0 1 :a])
(let [{:keys [x]} v [[_ {:keys [a]}]] x] a)

; assoc
; => {:x [["ok" {:a 2, :b 2}] ["ok" {:a 3, :b 4}]]}
(update v :x (fn [x] (update x 0 (fn [i] (update i 1 (fn [j] (assoc j :a 2)))))))
(assoc-in v [:x 0 1 :a] 2)

; update
; => {:x [["ok" {:a 2, :b 2}] ["ok" {:a 3, :b 4}]]}
(update v :x (fn [x] (update x 0 (fn [i] (update i 1 (fn [j] (update j :a #(+ 1 %))))))))
(update-in v [:x 0 1 :a] #(+ 1 %))

Note assoc & update. Nested callbacks are transformed into a sequence of keys [:x 0 1 :a]. The sequence is just a data, so we can compose them.

Elixir has Access!

Like get-in, assoc-in & update-in in Clojure, Elixir has get_in/2, put_in/3, update_in/3 & pop_in/2.

v = %{x: [{:ok, a: 1, b: 2}, {:ok, a: 3, b: 4}]}

1 = get_in(v, [:x, Access.at(0), Access.elem(1), :a])

%{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} =
  put_in(v, [:x, Access.at(0), Access.elem(1), :a], 2)

%{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} =
  update_in(v, [:x, Access.at(0), Access.elem(1), :a], &(&1 + 1))

{1, %{x: [{:ok, b: 2}, {:ok, a: 3, b: 4}]}} =
  pop_in(v, [:x, Access.at(0), Access.elem(1), :a])

These are family of Access module. These take a "path", a sequence of keys & functions. Map & Keyword takes a key. List needs Access.at/1. Tuple needs Access.elem/1.

%{x: 2} = put_in(%{x: 1}, [:x], 2)
%{x: 2} = put_in(%{x: 1}[:x], 2)
%{x: 2} = put_in(%{x: 1}.x, 2)

[x: 2] = put_in([x: 1], [:x], 2)
[x: 2] = put_in([x: 1][:x], 2)

%{x: 1, y: 2} = put_in(%{x: 1}[:y], 2)

[y: 2, x: 1] = put_in([x: 1][:y], 2)

# `Access.key!/1` is also available for Map.
%{x: 2} = put_in(%{x: 1}, [Access.key!(:x)], 2)

try do
  put_in(%{x: 1}, [Access.key!(:y)], 2)
rescue
  err in KeyError -> err
end

try do
  put_in(%{x: 1}.y, 2)
rescue
  err in KeyError -> err
end

# `Access.key/2` provides a default value. This is useful for put_in & update_in.
%{x: 1, y: %{z: 2}} = put_in(%{x: 1}, [Access.key(:y, %{}), :z], 2)

# List
[2] = put_in([1], [Access.at(0)], 2)

nil = get_in([1], [Access.at(1)])

# Tuple
{2} = put_in({1}, [Access.elem(0)], 2)

You'll find that accessing Struct causes an error. Struct isn't accessible in default. But we can use Access.key!/1 & Access.key/2.

defmodule Example do
  defstruct x: 1
end

defmodule Main do
  def main do
    %Example{x: 2} = put_in(%Example{}, [Access.key!(:x)], 2)
    %Example{x: 2} = put_in(%Example{}, [Access.key(:x)], 2)
  end
end
Main.main

One of the best feature of Access is Access.all/0 & Access.filter/1. These reduce nested Enum calls. Access.all/0 traverses a List. Access.filter/1 traverses a List & filter them.

%{x: [%{a: 2}, %{a: 3}]} =
  update_in(%{x: [%{a: 1}, %{a: 2}]}.x, fn x -> for %{a: a} <- x, do: %{a: a + 1} end)

%{x: [%{a: 2}, %{a: 3}]} =
  update_in(%{x: [%{a: 1}, %{a: 2}]}, [:x, Access.all(), :a], &(&1 + 1))

require Integer

%{x: [%{a: 1}, %{a: 1}]} = update_in(
  %{x: [%{a: 1}, %{a: 2}]}.x,
  fn x ->
    x
    |> Enum.reduce(
      [],
      fn
        %{a: a}, accm when rem(a, 2) == 0 -> [%{a: div(a, 2)} | accm]
        i, accm -> [i | accm]
      end
    )
    |> Enum.reverse
  end
)

%{x: [%{a: 1}, %{a: 1}]} = update_in(
  %{x: [%{a: 1}, %{a: 2}]},
  [:x, Access.filter(&Integer.is_even(&1.a)), :a],
  &div(&1, 2)
)

Extend Access

There are 2 ways to extend Access.

  1. Create an Access fun
  2. @impl Access

Create an Access fun

We can create functions like Access.*/* by ourselves. It's called "access_fun".

access_fun(data, get_value) ::
  get_fun(data, get_value) | get_and_update_fun(data, get_value)

access_fun is an union of get_fun & get_and_update_fun.

get_fun(data, get_value) ::
  (:get, data, (term() -> term()) -> {get_value, new_data :: container()})

get_and_update_fun(data, get_value) ::
  (:get_and_update, data, (term() -> term()) ->
     {get_value, new_data :: container()} | :pop)

Let's create one. In a following situation,

v = %{x: [{:ok, a: 1, b: 2}, {:ok, a: 3, b: 4}]}

1 = get_in(v, [V.a(1), :a])

%{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} = put_in(v, [V.a(1), :a], 2)

%{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} = update_in(v, [V.a(1), :a], &(&1 + 1))

the access_fun will be this.

defmodule V do
  def a(a), do: fn command, data, next -> a(command, data, a, next) end

  defp a(:get, data, a, next) do
    data.x
    |> Enum.find(fn
      {:ok, x} -> x[:a] == a
      _ -> false
    end)
    |> elem(1)
    |> next.()
  end

  defp a(:get_and_update, data, a, next) do
    {{:ok, x}, i} =
      data.x
      |> Enum.with_index
      |> Enum.find({{:ok, nil}, nil}, fn
        {{:ok, x}, _} -> x[:a] == a
        _ -> false
      end)

    case next.(x) do
      {get, update} ->
        data = put_in(data, [:x, Access.at(i), Access.elem(1)], update)
        {get, data}

      :pop ->
        if is_nil(i) do
          data
        else
          {_, data} = pop_in(data, [:x, Access.at(i), Access.elem(1)])
          data
        end
    end
  end
end

access_fun pros :

  • It can traverse all data.
  • Separate from other Access behaviour.
  • You can write complex behaviour.

access_fun cons :

  • It has many boilerplate.
  • Too complex.

When you just want a short cut of Access path, you can implement a Access behaviour.

@impl Access

Access behaviour provides more fluent syntax for users. When a struct is, you should implement Access like this.

defmodule V do
  defstruct x: []

  @behaviour Access

  @impl Access
  def fetch(v, key) do
    case path_to(v, key) do
      nil -> :error
      path -> {:ok, get_in(v, path)}
    end
  end

  @impl Access
  def get_and_update(v, key, fun) do
    case path_to(v, key) do
      nil -> {nil, v}
      path -> get_and_update_in(v, path, &fun.(&1))
    end
  end

  @impl Access
  def pop(v, {_, _} = key) do
    case path_to(v, key) do
      nil -> {nil, v}
      path -> pop_in(v, path)
    end
  end

  defp path_to(v, {:a, a}) do
    with i when not is_nil(i) <-
      Enum.find_index(v.x, fn
        {:ok, x} -> x[:a] == a
        _ -> false
      end) do
      [Access.key!(:x), Access.at(i), Access.elem(1)]
    else
      _ -> nil
    end
  end
end

Easy. Then you can call this like natively supported syntax.

defmodule Main do
  def main do
    v = %V{x: [{:ok, a: 1, b: 2}, {:ok, a: 3, b: 4}]}

    1 = v[{:a, 1}][:a]

    %V{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} =
      put_in(v[{:a, 1}][:a], 2)

    %V{x: [{:ok, a: 2, b: 2}, {:ok, a: 3, b: 4}]} =
      update_in(v[{:a, 1}][:a], &(&1 + 1))

    {1, %V{x: [{:ok, b: 2}, {:ok, a: 3, b: 4}]}} =
      pop_in(v, [{:a, 1}, :a])
  end
end
Main.main

Access is composable & extensible abstraction to get & update deep nested data. Let's enjoy functional programming on Elixir!