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

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

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

音樂は SoundCloud に公開中です。

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

Programming は GitHub で開發中です。

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!