Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active December 23, 2023 11:19
Show Gist options
  • Select an option

  • Save lagenorhynque/526d71ecccd5969223251723c90155ff to your computer and use it in GitHub Desktop.

Select an option

Save lagenorhynque/526d71ecccd5969223251723c90155ff to your computer and use it in GitHub Desktop.
ClojurianからみたElixir

ClojurianからみたElixir


(defprofile lagénorhynque
  :id           @lagenorhynque
  :reading      "/laʒenɔʁɛ̃k/"
  :aliases      ["カマイルカ🐬"]

  :languages    [Clojure Haskell English français]

  :interests    [programming language-learning law mathematics]

  :commits      ["github.com/lagenorhynque/duct.module.pedestal"]

  :contributes  ["github.com/japan-clojurians/clojure-site-ja"])

twitter icon


技術書典6でLisp本を出しました (*> ᴗ •*)ゞ

Three Lisps in Three Weeks

Clojure編: データ指向なREST API開発について

cf. BOOTH https://booth.pm/ja/items/1317263


  1. ClojureとElixir

  2. 私のElixir入門

  3. Clojureとよく似ているところ

  4. Clojureと大きく違うところ


ClojureとElixir


Clojure

  • 作者: Rich Hickey

  • 登場時期: 2007年

  • 最新安定版: 1.10.0

  • 主な特徴:

    • Java VM (JVM)で動作する
    • Lisp の伝統を継承/克服している
    • 並行プログラミング を強力に支援する
    • 動的型付けの 関数型言語

Elixir

  • 作者: José Valim

  • 登場時期: 2011年

  • 最新安定版: 1.8.2

  • 主な特徴:

    • Erlang VM (EVM; BEAM)で動作する
    • Ruby に似たシンタックス
    • Clojure の影響が見られる
    • 動的型付けの 関数型言語

e.g. フィボナッチ数列の最初の20項

Clojure

;; REPL (read-eval-print loop)

user> (->> (iterate (fn [[a b]] [b (+ a b)]) [0 1])
           (map first)
           (take 20))
(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)

Elixir

# IEx (Interactive Elixir)

iex(1)> Stream.iterate({0, 1}, fn {a, b} -> {b, a+b} end) |>
...(1)>   Stream.map(&elem(&1, 0)) |>
...(1)>   Enum.take(20)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
 987, 1597, 2584, 4181]
# または
iex(2)> Stream.unfold({0, 1}, fn {a, b} -> {a, {b, a+b}} end) |>
...(2)>   Enum.take(20)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
 987, 1597, 2584, 4181]

私のElixir入門

à la Clojure?


Programming Clojure (2nd edition)


Programming Clojure, Third Edition


Programming Elixir 1.2


Programming Elixir ≥ 1.6


Seven Languages in Seven Weeks

  • Ruby, Io, Prolog, Scala, Erlang, Clojure, Haskell

Seven More Languages in Seven Weeks

  • Lua, Factor, Elixir, Elm, Julia, MiniKanren, Idris

Seven Concurrency Models in Seven Weeks

  • Chapter 3, 4, 6: Clojure
  • Chapter 5: Elixir

Mastering Clojure Macros


Metaprogramming Elixir


Engineer Hub article on Elixir (1)


Engineer Hub article on Elixir (2)


Elixir公式ドキュメント Getting Started

Elixir Getting Started


Erlang/Elixir Syntax


Clojureとよく似ているところ


  • 基本的なプログラミングスタイル

  • プロトコルによるポリモーフィズム

  • マクロによるメタプログラミング


基本的なプログラミングスタイル


コレクションのリテラル

Clojure

user> '(1 2 3)          ; リスト ※クォートにより、要素は評価されない
(1 2 3)
user> [1 2 3]           ; ベクター
[1 2 3]
user> {:a 1 :b 2 :c 3}  ; マップ
{:a 1, :b 2, :c 3}

Elixir

iex(2)> [1, 2, 3]            # リスト
[1, 2, 3]
iex(3)> {1, 2, 3}            # タプル
{1, 2, 3}
iex(4)> %{a: 1, b: 2, c: 3}  # マップ
%{a: 1, b: 2, c: 3}

cf. 括弧の使い分け、カンマ (Clojureでは 空白 扱い)


名前空間/モジュールと関数の定義

Clojure

user> (ns example)         ; 名前空間 `example`
nil
example> (defn square [x]  ; 関数 `square`
           (* x x))
#'example/square
example> (in-ns 'user)     ; デフォルトの名前空間 `user` に戻る
#namespace[user]
user>

Elixir

iex(5)> defmodule Example do  # モジュール `Example`
...(5)>   def square(x) do    # 関数 `square`
...(5)>     x * x
...(5)>   end
...(5)> end
{:module, Example,
 <<70, 79, 82, 49, 0, 0, 4, 100, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 130,
   0, 0, 0, 14, 14, 69, 108, 105, 120, 105, 114, 46, 69, 120, 97, 109, 112, 108,
   101, 8, 95, 95, 105, 110, 102, 111, 95, ...>>, {:square, 1}}

関数の適用

Clojure

user> (example/square 3)  ; `example` の `square` 関数を適用
9
user> (map example/square (range 1 (inc 10)))
(1 4 9 16 25 36 49 64 81 100)

Elixir

iex(6)> Example.square(3)  # `Example` の `square` 関数を適用
9
iex(7)> Enum.map(1..10, &Example.square/1)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

ラムダ式/無名関数と略記法

Clojure

user> (map (fn [x] (* x x)) (range 1 (inc 10)))
(1 4 9 16 25 36 49 64 81 100)
user> (map #(* % %) (range 1 (inc 10)))
(1 4 9 16 25 36 49 64 81 100)

Elixir

iex(8)> Enum.map(1..10, fn(x) -> x * x end)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
iex(8)> Enum.map(1..10, &(&1 * &1))
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

->> (threading macro) vs |> (pipe operator)

Clojure

user> (->> (range 1 (inc 10))
           (map #(* % %))
           (filter odd?)
           (apply +))
165

Elixir

iex(9)> require Integer
Integer
iex(10)> 1..10 |>
...(10)>   Enum.map(&(&1 * &1)) |>
...(10)>   Enum.filter(&Integer.is_odd/1) |>
...(10)>   Enum.sum
165

cf. 関数の引数の順序


プロトコルによるポリモーフィズム


プロトコルの定義

Clojure

user> (defprotocol Shape  ; プロトコル `Shape`
        (area [x]))       ; プロトコルのメソッド `area`
Shape

Elixir

iex(1)> defprotocol Shape do  # プロトコル `Shape`
...(1)>   def area(x)         # プロトコルのメソッド `area`
...(1)> end
{:module, Shape, ..., {:__protocol__, 1}}

レコード/構造体の定義とプロトコルの実装

Clojure

user> (defrecord Triangle [x h]  ; レコード `Triangle`
        Shape                    ; プロトコル実装
        (area [{:keys [x h]}] (/ (* x h) 2)))
user.Triangle
user> (area (map->Triangle {:x 3 :h 2}))
3
user> (defrecord Rectangle [x y]  ; レコード `Rectangle`
        Shape                     ; プロトコル実装
        (area [{:keys [x y]}] (* x y)))
user.Rectangle
user> (area (map->Rectangle {:x 2 :y 3}))
6

Elixir

iex(2)> defmodule Triangle do  # 構造体 `Triangle`
...(2)>   defstruct [:x, :h]
...(2)> end
{:module, Triangle, ..., %Triangle{h: nil, x: nil}}
iex(3)> defimpl Shape, for: Triangle do  # プロトコル実装
...(3)>   def area(%Triangle{x: x, h: h}), do: x * h / 2
...(3)> end
{:module, Shape.Triangle, ..., {:__impl__, 1}}
iex(4)> Shape.area(%Triangle{x: 3, h: 2})
3.0
iex(5)> defmodule Rectangle do  # 構造体 `Rectangle`
...(5)>   defstruct [:x, :y]
...(5)> end
{:module, Rectangle, ..., %Rectangle{x: nil, y: nil}}
iex(6)> defimpl Shape, for: Rectangle do  # プロトコル実装
...(6)>   def area(%Rectangle{x: x, y: y}), do: x * y
...(6)> end
{:module, Shape.Rectangle, ..., {:__impl__, 1}}
iex(7)> Shape.area(%Rectangle{x: 2, y: 3})
6

プロトコル未実装の場合、エラー

Clojure

user> (defrecord Circle [r])  ; レコード `Circle`
user.Circle
user> (area (map->Circle {:r 3}))
Execution error (IllegalArgumentException) at user/eval5412$fn$G
(REPL:47).
No implementation of method: :area of protocol: #'user/Shape fou
nd for class: user.Circle

Elixir

iex(8)> defmodule Circle do  # 構造体 `Circle`
...(8)>   defstruct [:r]
...(8)> end
{:module, Circle, ..., %Circle{r: nil}}
iex(9)> Shape.area(%Circle{r: 3})
** (Protocol.UndefinedError) protocol Shape not implemented for
%Circle{r: 3}
    iex:2: Shape.impl_for!/1
    iex:3: Shape.area/1

プロトコルを後から実装する

Clojure

user> (extend-protocol Shape  ; プロトコル実装
        Circle
        (area [{:keys [r]}] (* Math/PI r r)))
nil
user> (area (map->Circle {:r 3}))
28.274333882308138

Elixir

iex(10)> defimpl Shape, for: Circle do  # プロトコル実装
...(10)>   def area(%Circle{r: r}), do: :math.pi() * r * r
...(10)> end
{:module, Shape.Circle, ..., {:__impl__, 1}}
iex(11)> Shape.area(%Circle{r: 3})
28.274333882308138

マクロによるメタプログラミング


クォートとAST

Clojure

user> '(when (= 1 1)
         (println "Truthy!"))
(when (= 1 1) (println "Truthy!"))
;; または
user> (quote
       (when (= 1 1)
         (println "Truthy!")))
(when (= 1 1) (println "Truthy!"))

Elixir

iex(1)> quote do
...(1)>   if 1 == 1 do
...(1)>     IO.puts "Truthy!"
...(1)>   end
...(1)> end
{:if, [context: Elixir, import: Kernel],
 [
   {:==, [context: Elixir, import: Kernel], [1, 1]},
   [
     do: {{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts
]}, [],
      ["Truthy!"]}
   ]
 ]}

マクロの定義

Clojure

user> (ns example)
nil
example> (defmacro my-when-not [test & body]
           `(if ~test
              nil
              (do ~@body)))
#'example/my-when-not
example> (in-ns 'user)
#namespace[user]
user>

Elixir

iex(2)> defmodule Example do
...(2)>   defmacro my_unless(test, do: body) do
...(2)>     quote do
...(2)>       if unquote(test), do: nil, else: unquote(body)
...(2)>     end
...(2)>   end
...(2)> end
{:module, Example, ..., {:my_unless, 2}}

マクロの利用

Clojure

user> (example/my-when-not (= 1 2)
        (println "Falsy!")
        42)
Falsy!
42
user> (example/my-when-not (= 1 1)
        (println "Falsy!")
        42)
nil

Elixir

iex(3)> require Example
Example
iex(4)> Example.my_unless 1 == 2 do
...(4)>   IO.puts "Falsy!"
...(4)>   42
...(4)> end
Falsy!
42
iex(5)> Example.my_unless 1 == 1 do
...(5)>   IO.puts "Falsy!"
...(5)>   42
...(5)> end
nil

マクロの展開

Clojure

user> (macroexpand-1  ; マクロを1段階展開
       '(example/my-when-not (= 1 2)
          (println "Falsy!")
          42))
(if (= 1 2) nil (do (println "Falsy!") 42))

Elixir

iex(6)> quote do
...(6)>   Example.my_unless 1 == 2 do
...(6)>     IO.puts "Falsy!"
...(6)>     42
...(6)>   end
...(6)> end |> Macro.expand_once(__ENV__) |>  # マクロを1段階展開
...(6)>   Macro.to_string |> IO.puts          # ASTを文字列表現に
if(1 == 2) do
  nil
else
  IO.puts("Falsy!")
  42
end
:ok

Clojureと大きく違うところ


  • 分配束縛 vs パターンマッチング

  • 並行プログラミングに対するアプローチ

  • 開発環境としてのREPLの位置付け

    • cf. REPL駆動開発 (REPL-driven development)

Elixirists ⇔ Clojurians (?)

Lisp alien


Further Reading


公式サイト


書籍



記事


学習メモ

#!/usr/bin/env bash
# npm install -g reveal-md
reveal-md elixir-from-a-clojurians-perspective.md --theme night --highlight-theme monokai-sublime -w $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment