やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【golang/Elixirのサンプル有り】何度も同じようなテストを書くのがつらいので無名関数を使って楽しくしよう

反応が良かったツイート

テストを無名関数で書くと楽しいよという旨のツイートの反応が良かった。ただ言葉だけだと正確に情報が伝わらないので実際にどうやっているのかをコードに落としてみた

せっかくなのでgolangElixirで書いてみた

実際に無名関数を使ってテストを書いてみる

リストに含まれている値を集計し、登場回数をカウントしてmapにして返す

  • go version: go1.12.4 darwin/amd64
  • Elixir version: Erlang/OTP 22 & Interactive Elixir (1.9.4)

golang

動作の確認

func CountAggregeter(lst []string) map[string]int {
  // 集計用のmap
  aggregater := make(map[string]int, 0)
  for _, val := range lst {
    if _, ok := aggregater[val]; ok {
      aggregater[val] += 1
    } else {
      aggregater[val] = 1
    }
  }
  return aggregater
}
// 実行結果
package main
import "fmt"
func main(){
    // Your code here!
    
    lst := []string{"AAA", "BBB", "AAA", "CCC", "DDD", "BBB", "EEE"}
    res := CountAggregeter(lst)
    fmt.Println(res)
}

// map[AAA:2 BBB:2 CCC:1 DDD:1 EEE:1]

こちらからも確認可能です
play.golang.org

プロジェクト化

$GOPATH以下にディレクトリを作成してファイルを用意する(今回はテストだけを試すのでmain.goはなし)。テストのパッケージはgolangに標準組み込みのtestingを使用する。業務で3ヶ月使っているがシンプルなテストをする上でパフォーマンスは十分で覚えることもほとんどない。

ちなみに$GOPATHは簡単に確認することが出来る

$ echo $GOPATH
/Users/okb/go

/nameless_func_test  
|── /utils  
    |── list.go  
    |── list_test.go  

下記に先ほど作成したリストの値をカウントする関数を記述する
./nameless_func_test/utils/list.go

package utils

// リストを受け取り、値の登場回数をカウント
func CountAggregeter(lst []string) map[string]int {
    // 集計用のmap
    aggregater := make(map[string]int, 0)
    for _, val := range lst {
        // 既出であれば+1
        if _, ok := aggregater[val]; ok {
            aggregater[val] += 1
        } else {
            // 初登場であれば1を格納
            aggregater[val] = 1
        }
    }
    return aggregater
}

testingの仕様に従って、_test.goをテストを行いたいファイルの語尾に付ける。今回はlist.goに対してテストを行うのでlist_test.goを作成。そしてお待たせ。ここで無名関数が登場する
./nameless_func_test/utils/list_test.go

// Testという名前をテストを行いたい関数の頭に付けて関数を作成
func TestCountAggregeter(t *testing.T) {
        // 何度も同じ処理を行うため無名関数化
    debuger := func(lst []string, expectMap map[string]int) error {
        // 集計処理を実行
        res := CountAggregeter(lst)

        // 期待する値を用いてloop処理
        for key, val := range expectMap {
            // 値が集計結果に含まれているかどうか
            if cnt, ok := res[key]; ok {
                // カウント数が一致しないのであればerror
                if val != cnt {
                    return errors.New("[Error] カウント数が一致しません")
                }
            } else {
                // そもそも結果に含まれていなければerror
                return errors.New("[Error] 想定値か集計値が間違っています")
            }
        }
        return nil
    }
}

これがテストに使用する無名関数。引数にCountAggregeterに渡すリストと、期待する結果(assertテストとほとんど同じ)を与えて、一致するかをrangeを使って確認するというシンプルな仕上がり。退屈なテストの記述作業が少しは面白くなるはず。
あとはこの無名関数を使ってテストをゴリゴリ書くだけ(ちなみにtestingではエラー終了をさせる時にt.Error()とする)

./nameless_func_test/utils/list_test.go

func TestCountAggregeter(t *testing.T) {
    // シンプルなケース
    if err := debuger([]string{"A", "B", "C", "D"}, map[string]int{"A": 1, "B": 1, "C": 1, "D": 1}); err != nil {
        t.Error(err)
    }

    // 重複した値がうまくカウントされているか
    if err := debuger([]string{"A", "B", "A", "C"}, map[string]int{"A": 2, "B": 1, "C": 1}); err != nil {
        t.Error(err)
    }

    // リストが空の場合
    if err := debuger([]string{}, map[string]int{}); err != nil {
        t.Error(err)
    }

    // 複数の値が重複する場合
    if err := debuger(
        []string{"A", "B", "A", "C", "D", "C", "E", "F", "D", "F", "G"},
        map[string]int{"A": 2, "B": 1, "C": 2, "D": 2, "E": 1, "F": 2, "G": 1},
    ); err != nil {
        t.Error(err)
    }

    // 重複の発生が3回以上
    if err := debuger([]string{"A", "A", "A"}, map[string]int{"A": 3}); err != nil {
        t.Error(err)
    }
}

テストの実行と結果の確認。いいね

$ pwd
/Users/okb/go/src/nameless_func_test

$ go test -v nameless_func_test/utils
=== RUN TestCountAggregeter
--- PASS: TestCountAggregeter (0.00s)
PASS
ok nameless_func_test/utils 0.007s

Elixir編

実はElixirでテストを扱うのは初めて。今まではやってもdoctestでしか記述をしたことが無かった。まずはCountAggregeterと同様の機能を持つ関数の実装を行なった

defmodule Aggregater do
  def counter(lst) do
    Enum.reduce(lst, %{}, fn val, acc ->
      if Map.has_key?(acc, val) do
        Map.put(acc, val, Map.get(acc, val)+1)
      else
        Map.put(acc, val, 1)
      end
    end)
  end
end

Aggregater.counter(["AAA", "BBB", "AAA", "CCC", "DDD", "BBB", "EEE"]) |> IO.inspect

実行結果

> $ iex list.ex  
Erlang/OTP 22 [erts-10.6.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

%{"AAA" => 2, "BBB" => 2, "CCC" => 1, "DDD" => 1, "EEE" => 1}
Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)

プロジェクト化 & テスト書く

Elixirでのテストの記述に関してはmoduleレベルで扱うのは初めてなので以下の記事を参考にテストを書いてみる

elixirschool.com

ExUnitを扱いたいのでmixの新規プロジェクトを立ち上げる

$ mix new aggregater

実行後色々と生成される。今回、扱うのはlib/aggregater.extest/aggregater_test.exの2ファイルのみ。テストとして実行するファイルには.exs形式を用いる必要がある模様。

まずはlib/aggregater.exに先ほど実装した関数を記述
./lib/aggregater.ex

defmodule Aggregater do
  def counter(lst) do
    Enum.reduce(lst, %{}, fn val, acc ->
      if Map.has_key?(acc, val) do
        Map.put(acc, val, Map.get(acc, val)+1)
      else
        Map.put(acc, val, 1)
      end
    end)
  end
end

続いてテストのための無名関数を記述していく
./test/aggregater.ex

defmodule AggregaterTest do
  use ExUnit.Case
  doctest Aggregater

  # テストの命名
  test "test for Aggregater.counter" do
    # lst -> []string
    # expects -> map[string]int
    # 無名関数の作成
    debuger = fn lst, expects ->
      res = Aggregater.counter(lst)
      Enum.each(expects, fn {key, val} ->
        if Map.has_key?(res, key) do
          if val != Map.get(res, key) do
            IO.puts("#{val} == #{Map.get(res, key)}")
            # 想定外の状態が発生した場合に即return
            raise :error
          else
            :ok
          end
        else
          raise :error
        end
      end)
    end
  end
end

無名関数を使って複数のテストを書いてみる

defmodule AggregaterTest do
  use ExUnit.Case
  doctest Aggregater

  test "test for Aggregater.counter" do
    # シンプルなケース
    assert debuger.(["A", "B", "C", "D"], %{"A" => 1, "B" => 1, "C" => 1, "D" => 1}) == :ok

    # 重複した値がうまくカウントされているか
    assert debuger.(["A", "B", "A", "C"], %{"A" => 2, "B" => 1, "C" => 1}) == :ok

    # リストが空の場合
    assert debuger.([], %{}) == :ok

    # 複数の値が重複する場合
    assert debuger.(["A", "B", "A", "C", "D", "C", "E", "F", "D", "F", "G"], %{"A" => 2, "B" => 1, "C" => 2, "D" => 2, "E" => 1, "F" => 2, "G" => 1}) == :ok

    # 重複の発生が3回以上
    assert debuger.(["A", "A", "A"], %{"A" => 3}) == :ok
  end
end

実行してみる

$ mix test Finished in 0.06 seconds
1 test, 0 failures

Elixirで無事に無名関数を使ってテストを実行することが出来た。mixで作成したプロジェクトであればゴリゴリとテストが書いていけそうだ。人によるが自分にとっては退屈なテストケースの記述処理が無名関数を使うことで楽しく効率化することが出来る。ご参考に。全体のコードはこちらから
github.com

追記

いつもお世話になっているfukuoka.exさんの古賀さんより、一筆頂きました

サンプルコードまで書いて頂きました。確かにElixirでテストを書くのであればブロックを分けてシンプルなassertを一文記述する方式の方がどこでErrorになるのかも一目瞭然でクリーンになる。さらにPowerAssertというライブラリがかなりいい感じですね gist.github.com

PowerAssert

t.co

こういう反応がもらえるのが何よりありがたいし、自分のレベルアップが出来る。ありがとうございます🙇‍♂️

参考文献

数値をASCIIを用いてaからzまで(半角英字)の文字列に変換

何をしたいのか

業務で書いたコードなのだが、作成する元になったアイディアがボツになったため、お蔵入り。需要は無いだろうけど、せっかくなので当時、ググっても出てこなかったので公開しておこうと思う。1から始まる任意の数字をASCIIで定義されている数値と文字の紐付け表からaからzの適切な小文字アルファベット(半角英字)に変換するという処理だ。forで生成されるiterationのカウントを使ってa-zの文字列を生成するために使用していた。言葉で伝えても分かりづらいので動作の例を下記に記す

www.asciitable.com

i = 1 -> a
i = 2 -> b
:
i = 26 -> z
i = 27 -> a
i = 28 -> b
:
:

業務ではgolangを使って記述したのだが、せっかくなのでElixirで書き直した

予備知識

アルファベットとはご存知の通り、a-zで合計26文字が定義されている。またASCIIでは小文字のa97、小文字のz122として定義している。つまりは、26回周期でまた同じ半角英字が現れるということになる

アルファベット番号 ASCII番号
a 97
b 98
z 122

実際のコード

golang

// 数値をasillに変換する関数(eg: 97 -> a, 122 -> z)
func IntToASCII(num int) int {
    // 26 -> アルファベットの数
    if num > 26 {
        div := num / 26
        // 割り切れる際に整合性を取るため-1
        if num%26 == 0 {
            div -= 1
        }
        num -= 26 * div
    }
    // aが97で始まるため+97
    return num + 96
}

実行結果

package main

import (
    "fmt"
)

func main() {
    for i := 1; i < 100; i++ {
        res := IntToASCII(i)
        fmt.Println("i = ", i, ", res = ", res, ", str = ", string(res))
    }
}
i =  1 , res =  97 , str =  a
i =  2 , res =  98 , str =  b
i =  3 , res =  99 , str =  c
:
i =  24 , res =  120 , str =  x
i =  25 , res =  121 , str =  y
i =  26 , res =  122 , str =  z
i =  27 , res =  97 , str =  a
i =  28 , res =  98 , str =  b
:
i =  51 , res =  121 , str =  y
i =  52 , res =  122 , str =  z
i =  53 , res =  97 , str =  a
i =  54 , res =  98 , str =  b
:
i =  95 , res =  113 , str =  q
i =  96 , res =  114 , str =  r
i =  97 , res =  115 , str =  s
i =  98 , res =  116 , str =  t
i =  99 , res =  117 , str =  u

全文のコード詳細な結果はこちら
play.golang.org

Elixir

ゴリ押し感はいなめない。こういう計算処理を関数型でゴリゴリ書こうとするとどうなるんだろうと考えさせられた

# 無名関数を定義
int_to_ASCII = fn num -> 
  if num > 26 do
    num - (div(num, 26) + if(rem(num, 26)==0, do: -1, else: 0)) * 26 + 96
  else
    num + 96
  end
end

実行結果

Enum.each(1..100, fn n -> 
  res = int_to_ASCII.(n)
  IO.puts("i = #{n}, res = #{res}, str = #{[res]}")
end)
i = 1, res = 97, str = a
i = 2, res = 98, str = b
i = 3, res = 99, str = c
i = 4, res = 100, str = d
:
:
i = 97, res = 115, str = s
i = 98, res = 116, str = t
i = 99, res = 117, str = u
i = 100, res = 118, str = v

無名関数を使わないver
こっちの方がElixirっぽいかな

defmodule ASCII do
  def from_int(num) when num > 26 do
    num - calc_helper(num) * 26 + 96
  end
  def from_int(num), do: num + 96
  def calc_helper(num) do
    if rem(num, 26) == 0 do
      div(num, 26) -1
    else
      div(num, 26)
    end
  end
end

おまけ

Elixirのコードを後日さらにコンパクトに改良

defmodule ASCII do
  def from_int(num) when num > 26 do
    num - calc_helper(num) * 26 + 96
  end
  def from_int(num), do: num + 96
  def calc_helper(num) when rem(num, 26) == 0, do: div(num, 26) -1
  def calc_helper(num), do: div(num, 26)
end

参考文献

【サンプルコード有り】golangで複数条件のソートを無名関数を使っていい感じに実装してみた

何をしようとしているのか

struct(以降、構造体と表記)を要素に持つ、配列をソートする必要がある場面に出くわした。通常というか一般的な数値や文字列のソートと異なり、構造体のAフィールドの値が大きい順番かつ、Bフィールドの値が小さい順かつ...のようなソートを行う必要があるため、結構メンドくさい

一応、調べているとgolangにはパッケージ(組み込みではない)としてsortというものが用意されている。なお、sortの詳しい話は今回扱わないし、すでに素晴らしい情報が数多く公開されているのでそちらを参照して頂きたい。(参考文献にまとめています)

そんな中、こちらのブログで実装されている、構造体の複数fieldでのソート実装を見て、「なるほど、こんな感じでやればええんやな」と思い、ほとんどコピペで類似の処理を実装したものの、ソートする条件を関数化出来ない(ソートしたい各所でソート条件を持つ関数群を定義しなければならない)煩わしさに何とかならないかと思い試行錯誤した

or3.hatenablog.com

以下は引用のコードになります。非常に参考になりましたm( )m

func main(){
    // lessfuncを実装
    byBast := func(p1, p2 *profile)bool{
        return p1.threesize[0]<p2.threesize[0]
    }
    byClass := func(p1, p2 *profile)bool{
        return p1.class<p2.class
    }
    // 逆順も同じようにここで実装する
    byBastDescending := func(p1, p2 *profile)bool{
        return p1.threesize[0]>p2.threesize[0]
    }
    // 誕生日 time.TimeもBefore/Afterで比較してboolを返せる
    byDate := func(p1, p2 *profile)bool{
        return p1.data.Before(p2.data)
    }

    fmt.Println("学年昇順でおっぱい降順")
    sort.Sort(idleSorter{idle: aqours, lessfunc: []lessFunc{byClass, byBastDescending}})
    for i, v := range aqours{
        fmt.Printf("[%d] name: %-7s class: %d bast: %d\n", i+1, v.name, v.class, v.threesize[0])
    }
}

完成物としては条件となる文字列なりを渡してsplitし、lessFuncの配列に該当するソート条件を持つ無名関数を返すという処理の実装になる

出来たやつ

このブログ投稿を仮定した構造体を対象にソートを実行する無名関数を作成した

type Post struct {
    // データの作成者
    Name string
    // 投稿タイトル
    Title string
    // 投稿本文
    Body string
    // 作成日
    CreatedAt time.Time
}

ここまでは参考文献の実装と全く同じで、対象の構造体を変えただけ

// for sort
type lessFunc func(i, j *Post) bool
type PostSorter struct {
    Post []*Post
    lessFunc  []lessFunc
}

func (is PostSorter) Len() int {
    return len(is.Post)
}
func (is PostSorter) Swap(i, j int) {
    is.Post[i], is.Post[j] = is.Post[j], is.Post[i]
}
func (is PostSorter) Less(i, j int) bool {
    k := 0
    p, q := is.Post[i], is.Post[j]
    for k = 0; k < len(is.lessFunc)-1; k++ {
        less := is.lessFunc[k]
        switch {
        case less(p, q):
            return true
        case less(q, p):
            return false
        }
    }
    return is.lessFunc[k](p, q)
}

ここからがメイン。この関数で受け取った文字列からヒットする対象のソートを行うための無名関数を返す。なぜこのような形式になっているかというと、元々の実装がhttp経由でのGETquery stringを用いてsortすることを考えていたからだ

// 受け取ったstringからソートを行うための無名関数を返す
func SortRelationConverter(query string) func(*Post, *Post) bool {
    switch query {
    // Nameの昇順
    case "name":
        return func(p1, p2 *Post) bool {
            return p1.Name > p2.Name
        }
    // Nameの降順
    case "-name":
        return func(p1, p2 *Post) bool {
            return p1.Name < p2.Name
        }
    // Titleの昇順
    case "title":
        return func(p1, p2 *Post) bool {
            return p1.Title > p2.Title
        }
    // Titleの降順
    case "-title":
        return func(p1, p2 *Post) bool {
            return p1.Title < p2.Title
        }
    // Bodyの昇順
    case "body":
        return func(p1, p2 *Post) bool {
            return p1.Body > p2.Body
        }
    // Bodyの降順
    case "-body":
        return func(p1, p2 *Post) bool {
            return p1.Body < p2.Body
        }
    // CreatedAtの昇順
    case "created_at":
        return func(p1, p2 *Post) bool {
            return p1.CreatedAt > p2.CreatedAt
        }
    // CreatedAtの降順
    case "-created_at":
        return func(p1, p2 *Post) bool {
            return p1.CreatedAt < p2.CreatedAt
        }
    }
    return nil
}

最後にソート条件を渡しただけでソートの実行が出来るように上記の処理をラップする関数を用意。この関数にquery stringで受け取ったような形式で文字列を渡すだけでソートが実行される

// ソートを実行するためのラップ関数
// sortStr -> ソート条件(eg: name:-body:-created_at)
// splitSymbol -> ソート条件で区切り記号に用いる記号(eg: ':')
func PostSort(data []*Post, sortsStr, splitSymbol string) []*Post {
    // ソートを行うための無名関数を格納する配列
    sortCond := make([]lessFunc, 0)
    // ソート条件を対象の記号をsplitして作成
    sorts := strings.Split(sortsStr, splitSymbol)
    if len(sorts) > 0 {
        for _, sort := range sorts {
            // 条件から無名関数を作成して配列に格納
            sortFunc := SortRelationConverter(sort)
            sortCond = append(sortCond, sortFunc)
        }

        // ソートのために構造体を作成
        cond := PostSorter{
            Post: data,
            lessFunc:  sortCond,
        }
        // ソートを実行
        sort.Sort(cond)
    }
    return data
}

実行結果

func main(){
    // 検証用のデータを作成
    lst := []*Post{
        &Post{
            Name: "a",
            Title: "A-title",
            Body: "A-body",
        },
        &Post{
            Name: "b",
            Title: "B-title",
            Body: "B-body",
        },
        &Post{
            Name: "c",
            Title: "C-title",
            Body: "C-body",
        },
        &Post{
            Name: "d",
            Title: "D-title",
            Body: "D-body",
        },
        &Post{
            Name: "e",
            Title: "E-title",
            Body: "E-body",
        },
    }
    
    // 結果を確認するための無名関数
    debuger := func(lst []*Post) {
        fmt.Println("[debug] result")
        for _, val := range lst {
            fmt.Println("Name:", val.Name)
            fmt.Println("Title:", val.Title)
            fmt.Println("Body:", val.Body)
        }
        fmt.Println("------------------")
    }
    debuger(PostSort(lst, "name", ":"))
    debuger(PostSort(lst, "title:name", ":"))
    debuger(PostSort(lst, "name:-title", ":"))
}
[debug] result
Name: e
Title: E-title
Body: E-body
Name: d
Title: D-title
Body: D-body
Name: c
Title: C-title
Body: C-body
Name: b
Title: B-title
Body: B-body
Name: a
Title: A-title
Body: A-body
------------------
[debug] result
Name: e
Title: E-title
Body: E-body
Name: d
Title: D-title
Body: D-body
Name: c
Title: C-title
Body: C-body
Name: b
Title: B-title
Body: B-body
Name: a
Title: A-title
Body: A-body
------------------
[debug] result
Name: e
Title: E-title
Body: E-body
Name: d
Title: D-title
Body: D-body
Name: c
Title: C-title
Body: C-body
Name: b
Title: B-title
Body: B-body
Name: a
Title: A-title
Body: A-body
------------------

こちらから実際に結果を確認できます
play.golang.org

後日談

どうやら現在はもっと簡単に構造体を要素に持つ配列をソートできる模様。もっと簡単になりそう
mattn.kaoriya.net

参考文献

【第16回清流elixir勉強会】今年のElixirに関する学習を振り返る会

トピック

elixir-sr.connpass.com

早いもので今年ももう終わろうとしています。今年度より清流elixirというコミュニティを立ち上げて勉強会を行い今回を含めて合計16回の勉強会を開催することが出来た。幅広い方に参加して頂き、多くの方と知り合うことが出来た。それに見える世界観も大きく変わった

ここまで、この勉強会を継続出来たのは多くの方のご助力のおかげです。いつも有難うございます。今年度の勉強会はこれで最後になりますが、また来年からドシドシ活動をしていきますので、よしなにお願い致します🙇‍♂️

elixir-sr.connpass.com

清流elixir-infomation
開催場所: 丸の内(愛知)
参加人数: 4 -> 3 コミュニティ参加人数 : 37 -> 37
2019/12/21現在

第16回の勉強会について

今回は参加者の方と共に、今年のElixirに関する学習を振り返って共有した。やり方はシンプルかつノープランで各々、5~10分程度で軽く今年のElixirに関する学びを話す。それを順に回していくというスタイルになる
自分が何を話していたのかを正確には覚えていないが、参加者のtorifukukaiouさんがメモをとって下さりました。有難うございます。非常に助かります

自分が話したこと。今年の学び

  • 3月から清流elixirを開始
  • 知人がPythonの勉強会を全然知らない頃からはじめていまは約300名。その話を聞いたことで開催を決意
  • 当時は関数型なんぞやから。Elixirを知らなかった。パイプラインとか 分散とかやってみたい。面白そう
  • 基本的にはプログラミングElixirで勉強
  • 今年の一番の学びは基礎文法、バックグラウンド(用途、得意なこと)
  • 清流elixirの今年度の目標 -> 「Elixirとはなにができるものか、用途が向いているか」は説明できるようになったので目標達成
  • アプリケーションを作ったりはまだできていない
  • 来年はElixirで何か作る悪だくみをする
  • 文法わかりました。仲間が増えた。Elixirという好きな物の話しが出来て楽しい

当初、清流elixirを立ち上げた際は何度も話しているが、Elixirについてはほとんど知らなかった。そんな状態から今年はスタートした。torifukukaiouさんElixirの学びをまとめていらっしゃるスプレッドシートが素晴らしかったので、パクリつつ今年の学習を後日談として細かく振り返ってみる。ブログにまとめていて良かったなぁとしみじみ思う

2019年のOKBとElixir

清流elixirを発足する

今年の3月22日のこと。発足の理由は何度も話した通り。当時からその気持ちはぶれていないつもりだ

www.okb-shelf.work

とりあえずアウトプットのためにブログをスタートする

何だこの痛い記事は...1000文字しか書いてねぇ...。この時点で「まつもとゆきひろ」さんの考えを大切にしている

www.okb-shelf.work

怒涛の勢いでElixirの勉強をしてアウトプット

関数型言語なるものを触るのがはじめて、再帰関数であったり、ElixirEnum、特にEnum.reduceなんかが面白くて、色々試してはコードを書いてブログにまとめていた。この辺りの時期がもっともElixirを書いていただろうか

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

第2回清流elixir勉強会を開催

パイプライン演算子にまつわる勉強会。引数が流れて気持ちいんじゃあ^〜と喜びあったのを覚えている
www.okb-shelf.work

ちょっと凝ったことを試し始める

ElixirからPythonで記述した関数を呼び出したり、http clientを扱ったりと、少し玄人感が出てきたなと思っていたはずだ

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

第3回清流elixir勉強会を開催

パイプライン演算子に続き、Elixirと言えば、パターンマッチでしょということでパターンマッチをしまくる勉強会。map型のパターンマッチに苦戦したのを思い出す

www.okb-shelf.work

少し並行処理を意識しだす

以前行なったmessage passingを発展させたTaskがかなりしっくりきて、並行処理が出来たことに喜びを感じた

www.okb-shelf.work

Elixirでアルゴリズムを実装する

ゴールデンウィークに入ったこともあり、購入した「なっとく!アルゴリズム」という書籍を元にソートを始めとした様々なアルゴリズムElixirで実装していった。Elixirの勉強にもなったし、計算量という概念も身につき、コードを書く際の視点が増えて、業務で非常に役にやっている

www.okb-shelf.work

www.okb-shelf.work

www.okb-shelf.work

www.okb-shelf.work

www.okb-shelf.work

www.okb-shelf.work

www.okb-shelf.work

第4回清流elixir勉強会を開催

前回に引き続き、パターンマッチの学習を行なった。第4回の勉強会は常連参加者のりき さんが進行をしてくれた。難しいことは考えないようにしたきたが、「値の束縛」という概念など非常に分かりやすく説明してくれた。さらに第4回の勉強会の内容をいつも通り、はてなブログにまとめてtwitterに投稿したところ、fukuoka.exさんを主催されており、日本のElixir先駆者である、piacere さんにRTを頂き、多くの方に認知して頂けた。第4回の勉強会をきっかけに全国各地の方とつながりを持つことが出来た

www.okb-shelf.work

はじめてWebFrameworkを触る

なぜかPhoenixではなく、trotというマイクロなFrameWorkを使ってAPIを作ってみた。確か、git hubexploreで見つけた記憶がある

www.okb-shelf.work

第5回清流elixir勉強会を開催

再帰関数について取り組んだ。個人的に再帰関数という考えがお気に入りで、説明していて楽しかった記憶がある
www.okb-shelf.work

Elixirで学ぶコンピューターサイエンス

自分がコンピューターサイエンスを学んでいないことでマウントを取られることが多くて、悔しい思いをしたので書籍を購入して誰も挑戦していないElixirで各章の課題を記述した。パターンマッチが論理ゲートを実装する上で非常に役に立った

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

Elixir/Erlang Fest2019に行ってきた

トップの人たちが集まる環境に行けることは滅多にないので、東京に行ったことが2度しかなく、初めての1人東京だったが参加してきた。当日のLTはレベルが高すぎて自分にはよく分からなくて、いい刺激を貰えた。参加した知見を清流elixirに持ち帰ろうと思っていたが、常連参加者の全員がElixir/Erlang Fest2019に参加してため、世間の狭さを感じた

www.okb-shelf.work

第6回清流elixir勉強会を開催

今まで使用していた4畳程のレンタル会議室を抜け出し、Misoca様のオフィスにお邪魔させて頂き勉強会を開催した。今まで競技プログラミングの経験は無かった為、かなり苦戦したが、Elixirのパイプラインが非常に役になった。参加者のthara さんが書くコードがスタイリッシュすぎて感動したことを覚えている

www.okb-shelf.work

第7回清流elixir勉強会を開催

初めてのリモート勉強会。fukuoka.exさんで勉強会を主催されているkoga さんに準備をして頂き人生初のリモートでの勉強会参加をした。この頃から福岡のエンジニアのレベルの高さには驚かされた

www.okb-shelf.work

第8回清流elixir勉強会を開催

以前行なった、競技プログラミングの問題をElixirを使って解くというのが面白かったので、再度同じテーマで開催。ちょうど、この頃から新たなレンタルオフィスを見つけてそちらへ移動した。以前より広くモニターも使えて、安く使い勝手が良い

www.okb-shelf.work

第9回清流elixir勉強会を開催

ここまでElixirについて自身が学習をするということを行なってきたが、新規の参加者を増やす、Elixirを東海で布教するという目的に挑戦するために、Elixirに興味がある、やってみたいという方向けにElixirの入門会を初めて開催した。当日は新たに3名の方に参加して頂き、Elixirの魅力を伝えることが出来たのではと思う

www.okb-shelf.work

色々、挑戦する

Enum.map再帰関数どちらが早いのかを調べたり、今更ながらPhoenixに取り組んだりと、的を絞らず様々なことをやっていた。基本的な文法を触れ終えて実践的なことを始めようと動き出していたはずだ。OTPがかなりしっくりきた

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

第10回清流elixir勉強会を開催

ブログで行なったEnum vs 再帰関数の知見を元に、条件分岐構文とパターンマッチのどちらが早いのかを調べた。ちょうど、この辺りから業務が忙しくなり、Elixirを触る時間が確保出来なくなってしまった

www.okb-shelf.work

第11回清流elixir勉強会を開催

ついに勉強会で並行処理に挑戦。内容はTaskを使ったシンプルなものだが、mixを使ってプロジェクトを作成するところまでを完遂することが出来てかなり内容も充実させられるようになってきた

www.okb-shelf.work

第12回清流elixir勉強会を開催

以前よりずっと、行いたかったCode ReTreatというテーマを決めて、各チームでそれに取り組むというミニハッカソンのようなイベントを開催した。テーマはソートアルゴリズムを採用した。30分のセクションを2回行なったが、とても面白かった。もっと時間と良いテーマを決めて、再度取り組みたいイベントだ

www.okb-shelf.work

尖りだす

Erlangの知見を習得し始めたり、環境を構築したりと少し尖ったことを学習し始める。しかし、個人的にはこんな感じの学習が結構好きだったりする。業務は忙しいが何とか時間を作って、自分の取り組みたいことをするようにした。以前のような学習に義務感はなく好きなテーマを好きに取り組むようになった

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

第13回清流elixir勉強会を開催

Elixirのことをそこそこ理解しているつもりになっていたが、この勉強会で自分はまだまだだと思い知らされた。随所で使われているマクロや、見たこともない構文に手も足も出なかった。fukuoka.exさんのpiacereさんの解説がなければ詰んでいた

www.okb-shelf.work

人生で初めてLTをする。しかもリモートで

業務で登壇したり、セミナーで答弁したりすることはあったが、それは自分の意志ではない。自分からLTをしようと気持ちはあまりなかったが、piacereさんからお誘い頂き、これまでの清流elixirの活動をfukuoka.ex秋のLT大会にてお話させて頂いた。ブログとはまた違うアウトプットの方法に刺激を受けた

www.okb-shelf.work

マクロの学習を始める

前回の勉強会で非常に悔しい思いをしたのでマクロの勉強を始める。構文に関してはこのアウトプットで何とかなったはずだ

www.okb-shelf.work

第14回清流elixir勉強会を開催

以前、悔しい思いをしたマクロに挑戦する勉強会を開催した。今回はfukuoka.exさんと合同での勉強会を開催して、計全国3カ所を繋いだ壮大な勉強会となった。マクロを学習する中で、今までのプログラミング言語の概念がぶっ壊されて非常に勉強になった。まだまだ知らないことが多すぎて驚くばかりだ

www.okb-shelf.work

少しレイヤーの低い知識をインプット

Elixirの仕様を理解するためにErlangの理解が必要になることがあり、Erlangの知見をインプットした。Elixirに比べて日本語情報が少なく、理解がかなり難しかった

www.okb-shelf.work
www.okb-shelf.work
www.okb-shelf.work

qiitaに初投稿

fukuoka.exさんのアドベントカレンダーに参加さえて頂き、LTした内容をさらに詳細にまとめたものを投稿した。qiitaに投稿することで、また普段とは違い方に見て頂けるチャンスが得られることが分かった。自身の登校日のPVが普段の3倍ほどになっていた

qiita.com

第15回清流elixir勉強会を開催

fukuoka.exさんのもくもく会に並行開催させて頂き、qiitaのアドベントカレンダーに記載する記事を書くために色々触ってみた。もとより、分散処理を華麗にこなしたいという気持ちがあったので、Node間での通信について、取り組んだが時間が足りず途中で終わった。

www.okb-shelf.work

そして伝説へ

ここまで今年度の主な自身のElixirに関する活動になる。実はいろいろとgit hubのprivateのレポジトリで色々と作ったりはしていたが、どれも上手くいかずに途中で投げ出している。Phoenixで作りたいアプリの構想もいくらかあるし、Nervesを触るために購入したラズベリーパイも触れていない。ある程度、基礎的な知識は身についているはずので、来年度は形があるものを作って、世の中に出せたらと思う

今回の参加者の方々の「ものを作る」という活動にとても心打たれた。自分も作ってみようと思う

結論として、Elixirの学習を始めてよかった。言語仕様の他にも色んな知見やつながりを得ることが出来た。来年度も清流elixirの活動は積極的に行なっていきますので、また宜しくお願い致します。

【サンプルコード有り】golangとclosureで作ったクールなカウンターをElixirで書き直した

closure(クロージャー)とは何か

難しい概念の説明は強いエンジニアや大学の賢い先生方にお任せするため、深い説明は行わない。ざっくりと言うと、関数の中である値を保持させておいて、その値を変化させる(操作する)ための変数を保持している関数の内部に用意された無名関数のことだ。変数にアクセスするためにはclosure経由でのみしか許可させない、変数の値を変化させるためにはclosure経由でしか行わせないとすることで、変数のスコープを上手く隠すことが可能になる。実際にgolangで書いたclosureと動作をお見せする

golangで作ったclosureを利用したカウンター

実装はめちゃくちゃシンプル。go tourで解説されているclosureのサンプルを引数のコマンド経由で色んな処理が行える様にしただけだ。なぜ、このカウンターを作成したかというと、業務でgolangを使って、jsonからマーシャルしたデータを集計して返すみたいな処理が多発しており、大量のカウント用の変数の用意と管理がめんどくさくて、何かオシャレにしたろと思い出来上がったのがこれ。コマンドで操作できるので、割と気に入ってるし、コードの改修も行いやすそうだと思っている

package main
import "fmt"

// カウンターをclosureで作成。コマンドによって処理を切り分ける
func ClosureCounter(init, increment int) func(string) int {
    sum := init
    return func(command string) int {
        // commandを用いて行いたい処理を切り替える
        switch command {
        // 引数で指定した値分、sumに加算する
        case "ADD":
            sum += increment
            return sum
        // sumの値を返す
        case "GET":
            return sum
        // sumの値をリセットする
        case "RESET":
            sum = 0
            return sum
        }
        return sum
    }
}

// 実行部分
func main() {
  counter := ClosureCounter(0, 1)
    for i := 0; i < 10; i++ {
        counter("ADD")
        fmt.Println(counter("GET"))
    }
    fmt.Println("最終結果: ", counter("GET"))
 
    // 値をリセット
    counter("RESET")
    fmt.Println("RESET後: ", counter("GET"))
}

結果

1
2
3
4
5
6
7
8
9
10
最終結果:  10
RESET後:  0

このように動作する。先ほどの説明の通り、sumという変数の操作には、returnで返された無名関数とコマンド(引数)経由でのみしか行うことが出来ない

では本命のElixirで

無名関数と聞いてElixirで実装しないわけにはいかない。さくっと作れそうなので作ってみた。と、思ったがこれがかなり難しい。関数型言語で値が変化されないことが約されるので、先ほどのように元の変数に対して操作を行うという処理をするのは想像以上に難しい。というか出来ない。なので、あれこれ工夫した結果、以下の様な形に追いついた。一言で言えば、プロセスを立ち上げてメッセージを送る方法になる

./counter.ex

defmodule LikeClosure do
  # 受け取ったメッセージを元に処理を分岐
  def loop(sum, increment) do
    receive do
      # 引数で受け渡し分、加算を行う
      {:add, pid} ->
        send(pid, {:ok, sum+increment})
        loop(sum+increment, increment)
      # 値をリセット
      {:reset, pid} ->
        send(pid, {:ok, 0})
        loop(0, increment)
      # 現在の値を返す
      {:get, pid} ->
        send(pid, {:ok, sum})
        loop(sum, increment)
      # プロセスを安全に停止させる
      {:exit, pid} ->
        send(pid, {:exit})
        exit(:safety)
    end
  end

  # コマンドの送信と受け取り
  def messenger(pid) do
    # カリー化した無名関数を返す
    fn command ->
      send(pid, {command, self()})
      receive do
        # 返答を受け取る
        {:ok, sum} -> sum
        {:exit} -> IO.puts("stop counter and counter process is exit")
        _ -> IO.pus(":error")
      end
    end
  end

  # 処理を内部化して使用者に意識させない
  def counter(sum, increment) do
    counter_pid = spawn(__MODULE__, :loop, [sum, increment])
    messenger(counter_pid)
  end
end

流れとしては、カウント用のプロセスを立ち上げて、そのプロセスに対してメッセージという形でコマンドを送り、プロセスが保持している値(カウンター)を操作するという形になる。また、その一連の処理を簡潔に行いたいので、counterという関数にプロセスの生成から、メッセージのパッシングまでを内部化させて使用者に見えない様にしている。動作を見てみよう

# ファイルをコンパイル
iex(1) > c("counter.ex")
[LikeClosure] 

# 無名関数をreturnで受け取る
iex(3)> counter = Closure.counter(0,1)
#Function<0.133727158/1 in Closure.messenger/1>

# コマンドを送って指定の操作をさせる
iex(4)> counter.(:add)
1
iex(5)> counter.(:add)
2
iex(6)> counter.(:add)
3
:
:
iex(16)> counter.(:add)
13
iex(17)> counter.(:add)
14

# 値の確認
iex(18)> counter.(:get)
14

# 値のリセット
iex(19)> counter.(:reset)
0
iex(20)> counter.(:get)
0

# プロセスの停止
iex(21)> counter.(:exit)
stop counter and counter process is exit
:ok

思ったことが出来ている。使用者には内部の実装を意識させないようにすることも出来た。しかしながら、カウントさせるだけの処理にこれだけコード量を記述するのは正直なところ「うーん」という感じなので、何か良い方法はないかと模索していたところ、以下の回答を発見した

elixirforum.com

なるほど、Agentを使えば、確かに簡単に実装できそう。なので次はAgentで同様の処理をさせてみた

iex(6)> {:ok, pid} = Agent.start_link(fn -> 0 end)
{:ok, #PID<0.113.0>}
iex(7)> fun = fn -> Agent.get_and_update(pid, fn i -> {i, i + 1} end) end
_#Function<20.127694169/0 in :erl_eval.expr/5>
iex(8)> fun.()
0
iex(9)> fun.()
1
iex(10)> fun.()
2
iex(11)> fun.()
3
iex(12)> fun.()
4

かなり、短くなった上に実装がシンプルに出来た
./counter.ex

defmodule AgentClosure do
  use Agent
  def start_link(init, increment) do
    # Agentプロセスを立ち上げ
    Agent.start_link(fn -> init end, name: __MODULE__)
    IO.puts("start counter agent")

    # パターンマッチを使用するためと内部化のため、無名関数を返す
    fn command ->
      counter(command, increment)
    end
  end

  # 加算
  def counter(:add, increment) do
    Agent.update(__MODULE__, &(&1 + increment))
  end

  # 値の確認
  def counter(:get, _) do
    Agent.get(__MODULE__, & &1)
  end

  # 値のリセット
  def counter(:reset, _) do
    Agent.update(__MODULE__, fn _ -> 0 end)
  end
end

結果

iex(1)> c("counter.ex")
[AgentClosure, LikeClosure]
iex(2)> counter = AgentClosure.start_link(0, 1)
start counter agent
#Function<4.81008950/1 in AgentClosure.start_link/2>
iex(3)> counter.(:add)
:ok
iex(4)> counter.(:add)
:ok
iex(5)> counter.(:add)
:ok
iex(6)> counter.(:get)
3
iex(7)> counter.(:reset)
:ok
iex(8)> counter.(:get)
0

同様の使用感で使うことが出来る。というか、これ関数のみで外部からの変化を許可していないので普通にclosureになっていることに今更気づいた

おまけ

上手くいかなかった時のコードその1

失敗: golangで書いた感じでそのまま移植しようとする

defmodule Closure do
  def counter() do
    sum = 0
    fn command ->
      case command do
        "ADD" -> 
          sum = sum + 1
          IO.puts(sum)
          sum
        "RESET" -> 
          sum = 0
          sum
        "GET" -> sum
      end
    end
  end
end

counter = Closure.counter()
IO.puts(counter.("GET"))

Enum.map(1..10, fn _ -> 
  counter.("ADD")
end)

IO.puts(counter.("GET"))

結果

0
1
1
1
1
1
1
1
1
1
1
0

そりゃ、そうなる

上手くいかなかった時のコードその2

失敗: 関数型で値を保持させるなら再帰関数使ってアキュムレーターやんけと思ってカリー化して値の保持までは出来たものの、結局、sumの値は不変なので「無理ゲーでは?」と気づく

defmodule Closure do
  def counter(init, increment) do
    sum = init
    fn command -> closure(command, sum, increment) end
  end
  
  def closure("ADD", sum, increment) do
    sum + increment
    # 結局sumに値がreturnされないので値を保持できない...
    # プロセス用意してmessage passingさせれば無理やりカウント出来るが...
  end
  
  def closure("GET", sum, _) do
    sum
  end
  
  def closure("RESET", _, _) do
    0
  end
end

# 変数のBINDまでは出来た
counter = Closure.counter(0, 2)
IO.inspect(counter)
counter.("GET") |> IO.puts()
counter.("ADD") |> IO.puts()
counter.("ADD") |> IO.puts()

結果

#Function<0.30015443/1 in Closure.counter/2>
0
2
2

惜しいけど、カリー化という知見が身についた

参考文献