やわらかテック

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

【実装コード有り】Elixirでpandasっぽいcsvファイルを触るモジュールを作ってみた

ゆるふわな動機

pythonのライブラリにpandasと呼ばれるものがある
厳密にはC言語で書かれているのでpythonかというとそうでもないが
このpandasが提供しているAPIにread_csvというものがあり
csv形式のファイルをdf形式で読み込みpandasでお気軽にゴリゴリと編集することが可能
(shif-jisで読み込んでいるのは日本語を含むcsvのため。defaultはutf-8だったはず)

df = pd.read_csv("sample_data.csv", encoding = "shift-jis")
print(type(df))

#pandas.core.frame.DataFrame

このdfに対して

  • この条件で~
  • この条件で~
  • こういうカラムを追加して~
  • csvに保存

なんてことをよくやる
テキトーにコードを妄想するとこんな感じになる

df = pd.read_csv("sample_data.csv", encoding = "shift-jis")
df = df[df["code"] == "00"]
df = df.query('year > 2010')
df.to_csv("sample_new.data.csv", header=True, index=False)

もしくはdf.assignを使ってゴリゴリやる方法もある
こちらのqiitaの記事が素晴らしいです
https://qiita.com/piroyoung/items/dd209801ca60a0b00c11qiita.com

で、結局何が言いたいの?って話なんですけど、このデータ処理の流れってまさに
Elixirのパイプ演算じゃんってことを思う訳ですね

csv
|> Aカラムを削除
|> Bカラムに空値があるレコードを除去
|> Bカラムの100以下の数値を持つレコードを除去
|> csv形式で.csvファイルを出力

あ、いい。ここに並列処理が加わると...あとは分かるね?

とりあえず関数作った

全体コードはこちら。レポジトリ名は気分でつけました。
github.com

一番良く行う操作 ==> 特定条件での抽出 なのでこの操作を完結できる関数を用意してみた
csvライブラリを使って.csvファイルを読み込んでいる
読み込んだ時点ではデータはStream形式になっているのでStreamを可能な限り保って値を変換するようにしている
課題は並列処理の実装をしていないこと

パターンマッチを使って3種類の条件渡しに対応した

  • 条件(bool値を返す)を関数を渡すパターン(is_function)
  • 複数の値(リスト)からOR検索を行うパターン(is_list)
  • 完全一致する値を渡すパターン(上記以外)

記述は可能な限り、一般化したつもりだが
補助関数に無名関数を渡してて若干可読性が悪いような気もする
すっきりしててElixirっぽくて良いかなと思ってますが
インデントについてはオレ流ですいません

defmodule CsvEditer do
  require CsvColumn
  
  # 関数を第3引数に受け取った場合
  def filter(cdl, column_name, func) when is_function(func) do
    # 引数で受け取った関数を実行する無名関数を作成
    stream_func = fn enum, index, ope_func -> Stream.filter(enum, &(ope_func.(Enum.at(&1, index)))) end
    _filter_helper(cdl, column_name, stream_func, func)
  end

  # 複数の値(リスト)を第3引数に受け取った場合
  def filter(cdl, column_name, val) when is_list(val) do
    # 引数で受け取ったリストの値を持つかどうかを確認する無名関数を作成
    stream_func = fn enum, index, val -> Stream.filter(enum, &(Enum.member?(val, Enum.at(&1, index)))) end
    _filter_helper(cdl, column_name, stream_func, val)
  end

  # 単純に値を第3引数に受け取った場合
  def filter(cdl, column_name, val) do
    # 引数で受け取った値と一致するかどうかを確認する無名関数を作成
    stream_func = fn enum, index, val -> Stream.filter(enum, &(Enum.at(&1, index) == val)) end
    _filter_helper(cdl, column_name, stream_func, val)
  end

  # 一般化された補助関数
  defp _filter_helper(cdl, column_name, stream_func, val) do
    # 受け取ったカラムがリストのどの位置(index)にあるかを検索
    index_num = CsvColumn.val_index_in_header(cdl, column_name)
    body =
      cdl
      |> Stream.drop(1) #headerを一時除去
      |> stream_func.(index_num, val) #受け取った関数を実行
    Stream.concat(Stream.take(cdl, 1), body) #headerをつけて返す
  end
end

CsvColumnモジュール については特に難しいことはしていない

defmodule CsvColumn
  def cdl_header_stream(cdl, false) do
    # header(リストinリストのhead)をリストで返す
    cdl
    |> Stream.take(1)
    |> Enum.to_list()
    |> List.first()
  end
  # Streamのまま返す
  def cdl_header_stream(cdl, _), do: cdl |> Stream.take(1)

  # header(リスト)から引数の値を検索してindex番号を返す
  def val_index_in_header(cdl, column_name) do
    cdl
    |> cdl_header_stream(false)
    |> Enum.find_index(fn val -> val == column_name end)
  end
end

今回、検証に使用したのはこちら
よくサンプルとして広く扱われるiris(ゆり)のデータです

紹介するのを忘れていましたが、こちらがcsvを読み込む関数

defmodule CsvReader do
  def read_csv(file_path) do
    case File.exists?(file_path) do
      true ->
        file_stream =
          File.stream!(file_path)
          |> CSV.decode
          |> Stream.map(fn row ->
            {_, value_lst} = row
            value_lst
          end)
        {:ok, file_stream}
      false ->
        {:error, "#{file_path}: no such file or directory"}
    end
  end
end

以下を目標にデータをゴリゴリする

  • varietyはSetosaのみを抽出
  • sepal.lengthが6.0以下のみを抽出
  • sepal.widthが3.4もしくは3.5の値のみを抽出

では、さっそく
(ちなみにcdlというのは「csv data list」から命名している)

$ iex -S mix

#成功すればstatusとstreamの値が帰ってくる
iex> {_status, cdl} = CsvReader.read_csv("iris.csv")
{:ok,
 #Stream<[
   enum: #Function<63.126435914/2 in Stream.transform/3>,
   funs: [#Function<49.126435914/1 in Stream.map/2>,
    #Function<49.126435914/1 in Stream.map/2>]
 ]>}

# 一回データの中身を見てみる(リストinリスト)
iex> cdl |> Enum.to_list
[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  ["5.1", "3.5", "1.4", ".2", "Setosa"],
  ["4.9", "3", "1.4", ".2", "Setosa"],
  ["4.7", "3.2", "1.3", ".2", "Setosa"],
  :
  :
  ["4.6", "3.1", "1.5", ".2", "Setosa"]
]

数値(float)への変換はブログ記事の簡略化のため、裏作業で行なった

[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  [5.1, 3.5, 1.4, 0.2, "Setosa"],
  [4.9, 3.0, 1.4, 0.2, "Setosa"],
  [4.7, 3.2, 1.3, 0.2, "Setosa"]
]

準備完了。あとはパイプで遊ぶだけ

iex> csv |> 
        CsvEditer.filter("variety", "Setosa") |>
        CsvEditer.filter("sepal.length", fn x -> x <= 6.0 end) |>
        CsvEditer.filter("sepal.width", [3.4, 3.5]) |>
        Enum.to_list
[
  ["sepal.length", "sepal.width", "petal.length", "petal.width", "variety"],
  [5.1, 3.5, 1.4, 0.2, "Setosa"],
  [4.6, 3.4, 1.4, 0.3, "Setosa"],
  [5.0, 3.4, 1.5, 0.2, "Setosa"],
  [4.8, 3.4, 1.6, 0.2, "Setosa"],
  [5.1, 3.5, 1.4, 0.3, "Setosa"],
  [5.4, 3.4, 1.7, 0.2, "Setosa"],
  [4.8, 3.4, 1.9, 0.2, "Setosa"],
  [5.0, 3.4, 1.6, 0.4, "Setosa"],
  [5.2, 3.5, 1.5, 0.2, "Setosa"],
  [5.2, 3.4, 1.4, 0.2, "Setosa"],
  [5.4, 3.4, 1.5, 0.4, "Setosa"],
  [5.5, 3.5, 1.3, 0.2, "Setosa"],
  [5.1, 3.4, 1.5, 0.2, "Setosa"],
  [5.0, 3.5, 1.3, 0.3, "Setosa"],
  [5.0, 3.5, 1.6, 0.6, "Setosa"]
]

おぉ...ええやんけ
パイプ演算子のおかげで何がしたいのかが一発で分かる
問題は速度。記事の文字数が多くなったため、別記事で検証してみようと思う

ただ、可読性はあきらかに上がった(個人的にね

レッツElixir

岐阜県民だけどErlang&ElixirFest2019のために初ソロ東京に行ってきた

Erlang & ElixirFest2019とは

f:id:takamizawa46:20190602130917j:plain Erlang & Elixirが大好きな人やら興味のある人が集まる万博みたいなお祭り
年に一度開催されていらっしゃるようで今年で3度目になるそうです

Erlang & ElixirFest2019
elixirconf.connpass.com

開催場所はご想像の通り、東京です
Erlang、Elixir界隈ではかなり名のある方々が集まり、かなり技術レベルの高いカンファレンスとなっています

他の方と比べるとElixirとErlangの知見はほぼ皆無に等しいですが 岐阜に住んでて名古屋で仕事していると、こういうハイレベルな空気感に触れられる機会は、ほぼ皆無と言っていいので

  • ウン万円払ってでも!!
  • 恥をかいてでも!!

と割り切って思い切って行ってみました
ただ東京に行くのが人生二度目かつソロで新幹線乗ったのが初めてだったので、めちゃくちゃ迷いました
田舎者丸出しで恥ずかしや〜💦

内容について

以下、connpassのページから引用です

  • Keynote: Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)
  • 超並列高速実行処理系 Hastega 〜 Lonestar ElixirConf 凱旋帰国 (山崎 進)
  • Nervesが開拓する「ElixirでIoT」の新世界 (高瀬 英希)
  • 工場の制御をElixirで 〜ラダー・ロジックを実行する〜 (菊池 豊)
  • Elixir プロセス入門
  • ElixirでやるDDD (ドメイン駆動設計) (ne_Sachirou)
  • Phoenix1.4とVueによるサービス構築のノウハウ (山口 強)
  • ProtocolBuffer(proto3)をElixirで実装する (にく)
  • 4才から24才までプログラミングを教えた結果、高校生にElixirを教えるに至った気付き (YOSUKENAKAO.me)
  • XFLAG × スポーツ × Elixir (村山 寛明)
  • Serverless BEAM with FaaS (大原 常徳)
  • ロマサガRS における Elixir サーバー開発実践 〜生産性を上げてゲームの面白さに注力〜 (梶原 星平/関山 友輝)
  • Erlang/OTP で作るリアルタイムサーバー (清水 佑吾)
  • Erlang/OTP で WebRTC と QUIC (voluntas)
  • Enum.mapから始めるElixirデータサイエンス(piacere)

圧倒的...ッ!!! ボリューム...ッ!!
ラーメンに餃子とチャーハンと天津飯と唐揚げつけて頼んだかってぐらい
一日でこれだけの情報量を摂取できるのやばい
半分ぐらいは自分のレベル感で理解できる内容だったと信じているが
技術レベルの差にただ頷くことしかLTもあり本当に世の中は広いなと感じざるを得なかった

その反面、私の主催している「清流elixir」の常連メンバーが
elixir-sr.connpass.com

普通にErlang&ElixirFest2019に来ていて、世間は狭いなと思った
Fest行きたいなって決定打に思ったのが今回のKeynote
任天堂ErlangかElixirをswitchの通知システムに使ってるらしいよ」って話は風の噂で聞いたことがあった
ただ、その部分の任天堂さんのオープンな話(事実含めて)ってどこにも落ちてなくて
今回公の場に登壇されてお話できる範囲で詳細を初めて語って下さりました
終了後に拍手が鳴り止まず、何と色々と許可が出れば書籍化されるとかされないとか

有志の方がLTのリンクや関連スライドをqiitaの記事にまとめて下さっているので展開しておきます
qiita.com

20190601追記
keynoteでの渡邊さんのLT資料が公開されたので貼っておきます
Keynote: Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)
speakerdeck.com

以下、気になった&理解できているLTの内容についてざっくりとまとめてます
内容は理解できたレベルでまとめているが誤りがあれば教えてください

超並列高速実行処理系 Hastega

Elixirの並列処理ライブラリのFlowにインスパイアを受けてより速いものを作りたいという
研究者ならではの視点から開発を進められているライブラリ
GPU上でも動作し、Flowの10倍, pythonの3倍で動作するようです(化け物か

Elixir界隈では - 再帰関数 - Enum.map

を使うかでしばしば争いが起こりますが
山崎さんは後者を「Elixir Zen Style」と名付られ、こっちの方がいいじゃんと
ただ実は再帰関数の方がEnum.mapよりもパフォーマンスが良いらしいが
Hastegaを使えば、この事実がひっくり返りElixir Zen Styleで速く美しいコードがかけるとのこと

名前はFinalFantasyのヘイスガ(パーティのスピードを上げる魔法)から来ているとのことですが
実はJose(Elixirの作者)はFFをやったことがないことが判明して、会場がドワッと笑いに包まれた

僕もElixirでFFから来ていると思ってたのでワロタ
hypeやnifっていう話が前提知識がなさすぎてついていけなくなってしまったので
別の機会にインプットして記事にでもしてみようと思う

Nervesとラダーロジック

僕のイメージElixirはweb系でサーバーやらネットワークの開発がメインでしょってのがぶち壊された
IoT(ラズパイの規模から発電所)に対してもElixirはガンガン使えるぜっていうLTで
普段自分が目を向けないレイヤーの話で非常に面白かった

会場ではラズパイを動かすデモもあり、起動した際には拍手が沸き起こった(ウオオオオオ

Nervesが海外ではPhoenixと同レベルの立ち位置にいるらしく、どれだけ注目されているかが分かる
なんと2019 09/06(木) ~ 09/07(金)にかけて下呂(岐阜県やで)にてNervesの作者がやってくるそうです
岐阜はんぱないって!!

ラダーロジックというものに関しての知識は業界不一致で皆無だったが
発電所などをクラウドで管理するにはElixirは要件を満たしくれるとのこと
pythonを使って再帰で動かした際には異常があった場合に例外投げてしまって
現状どうなっているのかが分からず、管理に凄く苦労した」

「Elixirのプロセスであれば一回、killして立ち上げ直すだけだからぴったり」

おっしゃる通りです
この2つのLTからElixirの組み込み, IoT業界での伸び代を感じぜざるを得ませんでした

Elixir プロセス入門

spawnから始めり、メッセージを送受信して、Task触って、死活管理して....
個々の知識はあって穏やかな心で聞くことが出来た

Elixirの並列処理についてどの順で学べばいいかということがフロー化されており
自分のほしかったものはこれなんだよな感が半端ない

このまま清流elixirの勉強会に使えそうで非常にありがたいです

Enum.mapから始めるElixirデータサイエンス

僕が最初にElixirを選んだ理由はまさにこれ
データ処理のしやすさ(Enum)と軽量プロセスによる並列処理
並列にゴリゴリとデータ処理ができたらpythonの弱点を克服できるやんけ!!
こんなんデータ処理のためにあるみたいなもんじゃんと調べている内に先駆者であるpiacereさんを発見

LTの内容も好きな物尽くしでElixir愛がビシビシ伝わってきました

Erlang/OTP と ejabberd を活用した Nintendo Switch(TM)向けプッシュ通知システム「NPNS」の開発事例 (任天堂 ネットワークシステム部 渡邉大洋)

凄かった。さすが任天堂やで...

この一言に尽きる
詳しい話はLTを見て頂ければいいので思ったことを書く

本質的に行なっていることは自分と変わらないと思った
問題があってそれを解決するということ(ただ数字がコネクション1億とか笑えた)

1つの課題を達成するのに半年かかったこともあるとのことで
半年間の間、正解も分からない暗い森を歩き続けられるマインドが凄い

あとは会社の技術力にも脱帽
先輩エンジニアたちの知見があるからこそ成せる技。さすが任天堂

あとErlangやりたくなった。正確にはもっとErlangについて知りたくなった

総評

いい空気を吸うことができた
いくらネットで調べたら出るといえども、地方にいると技術の風を肌で感じることができない
とくにElixirっていう日本ではマイナーな部類に入る言語ならなおさら

改めて自分の技術レベルの低さを感じた
少しでも追いつけるようにインプットしなければと思う
強くなるきっかけを得られてErlang&ElixirFest2019に本当に感謝です

おまけのコーナー

休憩時間中に行われたハンズオンのコードを置いておきます

ErlangElixirFestHandsOn2019
github.com

以下を実装

(1) 1000以下のランダムな数を生成し、その時間(ミリ秒)スリープした後に、その数字を返す
(5) (1)の処理を100回、並行に実行する

ランダム数値の作成やsleepの仕方、Taskについては上記のレポジトリに記述がある
Taskについては以下の記事でも触れている

www.okb-shelf.work

defmodule HandsOn do
  def random_and_sleep(sec) do
    r_num = :rand.uniform(sec)
    IO.puts("stop: #{sec} ")
    :timer.sleep(sec)
    r_num
  end
  
  def main(sec, parallel_num) do
    Enum.map(0..parallel_num, fn _ -> Task.async(HandsOn, :random_and_sleep, [sec]) end)
      |> Enum.map(&(Task.await(&1)))
  end
end

sec = 100
parallel_num = 100
IO.inspect(HandsOn.main(sec, parallel_num))

結果

stop: 100
stop: 100
stop: 100
:
:
stop: 100
[84, 52, 86, 8, 23, 79, 29, 67, 28, 5, 17, 69, 98, 18, 98, 39, 94, 73,
 59, 78, 36, 25, 22, 77, 59, 60, 2, 86, 83, 29, 24, 97, 70, 18, 11, 89,
 34, 25, 27, 80, 61, 37, 53, 80, 26, 60, 22, 11, 43, 40, ...]

【Elixirで学ぶCS】Elixirで論理ゲートを実装するまで

なにこれ

僕は一応、理工学部の出身ではありますが建築土木が専攻でした
この業界にいながらcsについての知識が皆無
前からやらねば...やらねば..と思ってはいたが触れる機会がない & 一度挫折済み

しかし、アルゴリズムの勉強を始める中でデータ構造の重要性に気づき
データがコンピューター内部でどのように動いているか非常に気になった

良い機会だと思って「コンピュータシステムの理論と実装」を一読することに...
www.oreilly.co.jp

各説明は本を読んだ方が早いのでざっくりの説明で
実装部分をメインにブログで展開していくつもりです

ブール理論

コンピューターは一番下のレイヤーにいけば全て0と1(bool値)の組み合わせで構築されている
この組合せの数をたくさん増やす + 状態を保存(メモリやら)を使えばいろんな計算が出来るよねって話
このbool値を組み合わせて作るのが論理ゲートと呼ばれるもので

  • NOT
  • OR
  • AND

やらがある
さらにこれらの論理ゲートは全て、ベースとなる「NAND」という論理ゲートから作成することが可能
つまりはNANDさえ作れてしまえば全ての論理ゲートは実装可能となる
小さな世界から大きな世界が構築することが出来る(細胞が集まって生物になるみたいな感じ)

Elixirでの実装

NANDさえ作ってしまえば、他の論理ゲートは作成可能なのでまずはNANDから作成する

NANDゲート
NANDゲートは以下のような値を出力する関数とみなす

x y NAND(x, y)
0 0 1
1 0 1
0 1 1
1 1 0

ちょちょっと実装する
(1, 1)の時だけ0を返すようにしてある

defmodule RogicalGate do
  def nand(1, 1), do: 0
  def nand(_x, _y), do: 1
end

テストライブラリを使わずに簡単なテストを行なって
正しい値を出力しているかをチェックする

nand_test = [
  RogicalGate.nand(0,0) == 1,
  RogicalGate.nand(1,0) == 1,
  RogicalGate.nand(0,1) == 1,
  RogicalGate.nand(1,1) == 0,
]

Enum.map(nand_test, &(IO.puts(&1)))
true
true
true
true

あ、いいっすね

NOT
値を反転(0を1に, 1を0に)するのみ

x NOT(x)
0 1
1 0
defmodule RogicalGate do
  def nand(1, 1), do: 0
  def nand(_x, _y), do: 1
  def not_(x), do: nand(x, x) <-- 追加
end

テストを実行

not_test = [
  RogicalGate.not_(0) == 1,
  RogicalGate.not_(1) == 0,
]

Enum.map(not_test, &(IO.puts(&1)))
true
true

OR

x y OR(x, y)
0 0 0
1 0 1
0 1 1
1 1 1

NANDを3つ組み合わせて実装

defmodule RogicalGate do
  def nand(1, 1), do: 0
  def nand(_x, _y), do: 1
  def or_(x, y), do: nand(nand(x, x), nand(y, y)) <-- 追加
  def not_(x), do: nand(x, x)
end

テスト

or_test = [
  RogicalGate.or_(0, 0) == 0,
  RogicalGate.or_(1, 0) == 1,
  RogicalGate.or_(0, 1) == 1,
  RogicalGate.or_(1, 1) == 1,
]

Enum.map(or_test, &(IO.puts(&1)))
true
true
true
true

AND

x y AND(x, y)
0 0 0
1 0 0
0 1 0
1 1 1

すでに実装したnot_を使ってANDを実装する

defmodule RogicalGate do
  def nand(1, 1), do: 0
  def nand(_x, _y), do: 1
  def or_(x, y), do: nand(nand(x, x), nand(y, y))
  def not_(x), do: nand(x, x)
  def and_(x, y), do: not_(nand(x,y))
end

テスト

or_test = [
  RogicalGate.and_(0, 0) == 0,
  RogicalGate.and_(1, 0) == 0,
  RogicalGate.and_(0, 1) == 0,
  RogicalGate.and_(1, 1) == 1,
]

Enum.map(or_test, &(IO.puts(&1)))
true
true
true
true

いいっすね
次は作成したAND, NOT, ORを組み合わせてXORを作成してみる

XOR
(1, 0)か(0, 1)の際に1を出力させる
ORと異なるのは(1, 1 )の場合に0が返ってくるという点

x y XOR(x, y)
0 0 0
1 0 1
0 1 1
1 1 0

先ほど作成した論理ゲートをフルに使って実装する

defmodule RogicalGate do
  def nand(1, 1), do: 0
  def nand(_x, _y), do: 1
  def or_(x, y), do: nand(nand(x, x), nand(y, y))
  def not_(x), do: nand(x, x)
  def and_(x, y), do: not_(nand(x,y))
  def xor_(x, y), do: or_(and_(x,not_(y)), and_(not_(x),y))
end
xor_test = [
  RogicalGate.xor_(0, 0) == 0,
  RogicalGate.xor_(1, 0) == 1,
  RogicalGate.xor_(0, 1) == 1,
  RogicalGate.xor_(1, 1) == 0,
]

Enum.map(xor_test, &(IO.puts(&1)))
true
true
true
true

nビット論理ゲートへの対応

今まで作成したゲートをnビットの場合にも実行できるようにする
nビットといってもただ入力がリストになって複数入力されるだけで大したことはない

こんな関数を用意した
入力された2次元のリストから値を取り出して、引数から受け取った関数を適宜実行していく
notの場合はリストのサイズが1になるのでcaseで条件を分岐させている

def nbit_converter(inputs, gate_func) do
    Enum.map(inputs, fn row -> 
        case Enum.count(row) == 2 do
          true ->
            [x, y] = row
            gate_func.(x, y)
          false ->
            [x] = row
            gate_func.(x)
        end
      end
    )
  end

この関数を使ってnandなどをnビット対応にラップする
関数を引数で渡す場合には &モジュール名.関数名/引数の数 とすることで可能

defmodule NbitRogicalGate do
  def nbit_converter(inputs, gate_func) do
    Enum.map(inputs, fn row -> 
        case Enum.count(row) == 2 do
          true ->
            [x, y] = row
            gate_func.(x, y)
          false ->
            [x] = row
            gate_func.(x)
        end
      end
    )
  end
  def nand(inputs), do: nbit_converter(inputs, &RogicalGate.nand/2)
  def not_(inputs), do: nbit_converter(inputs, &RogicalGate.not_/1)
  def or_(inputs), do: nbit_converter(inputs, &RogicalGate.or_/2)
  def and_(inputs), do: nbit_converter(inputs, &RogicalGate.and_/2)
  def xor_(inputs), do: nbit_converter(inputs, &RogicalGate.xor_/2)
end

実行結果を見てみる

info = [
  [0, 0],
  [1, 0],
  [0, 1],
  [1, 1],
]

info_for_not = [
  [1],
  [0]
]

res = [
  NbitRogicalGate.nand(info),
  NbitRogicalGate.not_(info_for_not),
  NbitRogicalGate.or_(info),
  NbitRogicalGate.and_(info),
  NbitRogicalGate.xor_(info),
]

Enum.map(res, &(IO.inspect(&1)))

先ほどの表どおり、値が上手く出力された

[1, 1, 1, 0]
[0, 1]
[0, 1, 1, 1]
[0, 0, 0, 1]
[0, 1, 1, 0]

コンピューターで最も基本的なブール理論の論理ゲートの実装が何とか出来た
この論理ゲートの幾多なる組み合わせでコンピューターが動いていると考えると
考えた奴はマジで天才すぎる

【レポート】第5回清流elixir勉強会in丸の内を開催しました【再帰関数】

トピック

今回で第5回目の勉強会を僕の運営しているコミュニティで開催することができました
清流elixir

先週にはfukuoka.exを運営されているpiacereさんと Twitterで繋がらせて頂きまして多くの方に清流elixirの名を知って頂けました
本当にあざます!!!
やる気がめっさ出ました!!

清流elixir-infomation
開催場所: 丸の内(愛知)
参加人数: 3 -> 4 update!!
コミュニティ参加人数 : 5 -> 8 update!!
20190525現在

第5回の活動内容

関数型言語といったらやっぱり再帰関数でしょってことで軽いノリでこのテーマに
【自分的レシピ】elixirでの再帰関数の動かし方でも触れたように
関数型言語には一般的にfor文のような、いわゆるループ処理は用意されていない
じゃあ、どうやって書くのよ?という問いに対する答えは「再帰関数」を使おう

再帰関数ってなんぞ

関数が自分自身を呼び出す処理のこと
5回だけ「"hello"」と出力する再帰関数を作るとする
コードに落とし込むとこんな感じ

defmodule Sample do
  def hello do
    IO.puts("hello")
    hello()
  end
end

Sample.hello()

関数が自分自身を呼び出しているのが分かる
ただし、このままこのコードを実行するとまずい。無限再帰になってしまう
「いつ停止するの?」という問題がある
今回は5回「"hello"」を出力させた時点で再帰を終了させる必要がある
これは「停止性」といい、本当にこのコードが終了するかを保証する必要がある(停止性の議論)

たとえば5回で終了させたいならばこんな風に書き換える

defmodule Sample do
  def hello(counter) do
    if counter == 5 do
      :fin
    else
      IO.puts("hello")
      hello(counter+1)
    end
  end
end

Sample.hello(0)
# hello
# hello
# hello
# hello
# hello

きちんと再帰が停止した
helloの引数に追加したcounterというのはアキュムレーターと呼ばれるもので
貯蔵庫のような意味があり、状態を保管するために使用している
今回の場合は何回呼び出したか?ということをカウントするためのカウンターとして扱っている
アキュムレーターについての詳しく話はこちらで解説しているのでぜひ

ただこのコードはElixirっぽいくない上にダサいので書き換える

defmodule Sample do
  def hello(counter) when counter == 5, do: :fin
  def hello(counter) do
    IO.puts("hello")
    hello(counter+1)
  end
end

Sample.hello(0)
# hello
# hello
# hello
# hello
# hello

これでも上手く動く

defmodule Sample do
  def hello(5), do: :fin
  def hello(counter) do
    IO.puts("hello")
    hello(counter+1)
  end
end

参加者の方が面白い書き方を発見した(凄い
「helloにdefaultの値を持たせたらhelloの呼び出し時に0を渡す必要がなくなるのでは?」 fmfm...

defmodule Sample do
  def hello(5), do: :fin
  def hello(counter \\ 0) do
    IO.puts("hello")
    hello(counter+1)
  end
end

Sample.hello()

実行すると上手く動くが以下のような警告が出る

warning: definitions with multiple clauses and default values require a header. Instead of:
def foo(:first_clause, b \ :default) do ... end
def foo(:second_clause, b) do ... end

errorの意味は分かるが、修正の方法が思いつかず
再帰関数作るときはアキュムレーターにdefault引数渡すの良くなさそうということで一旦落ち着いた
詳しい原因をご存知の方は教えてください

再帰関数で色々作って遊んでみる

f:id:takamizawa46:20190525130609j:plain:w450

配列の先頭の要素を取得して出力する
headとtailを使って実装する
リストが空となった場合に停止するように関数を実装する

sample = [1,2,3,4,5]
[head | tail] = sample
defmodule Sample do
  def fetch([]), do: :fin
  def fetch([head | tail]) do
    IO.puts(head)
    fetch(tail)
  end
end

Sample.fetch([1,2,3,4,5])
# 1
# 2
# 3
# 4
# 5

配列の要素を合計する
アキュムレーターに先頭の要素をどんどん足していく

defmodule Sample do
  def sum([], accum), do: accum
  def sum([head | tail], accum), do: sum(tail, accum+head)
end

IO.puts(Sample.sum([1,2,3,4,5], 0))
#15

このままだと呼び出し時に0を渡すという操作があり
アキュムレーターの知識がない人には煩わしいのでhelper関数というものでラップする

defmodule Sample do
  def sum(lst), do: _sum(lst, 0)
  defp _sum([], accum), do: accum
  defp _sum([head | tail], accum), do: _sum(tail, accum+head)
end

IO.puts(Sample.sum([1,2,3,4,5]))
#15

これならリストを渡すだけでsumをアキュムレーターを意識せずに使用することが可能

配列の大きさをカウントする

defmodule Sample do
  def length([], accum), do: accum
  def length([_head | tail], accum), do: length(tail, accum+1)
end

IO.puts(Sample.length([1,2,3,4,5], 0))
#5

良い感じですね
言語に実装されているEnumの関数は再帰関数を使えば再現することが可能
明日から新たなモジュールを開発できる。やったぜ

悲しいお知らせ

本日の勉強会で作成した再帰関数はお察しの通り、大体はEnumの関数やらの組み合わせで作成することが可能
再帰関数を作成するまえに必ず、この処理はEnumやらを使って実装することが出来ないかを考える必要がある
再帰関数で書いてもいいけどね。スピード感を大事にしたい

嬉しいお知らせ

20190601(土)に東京で開催される
Erlang & Elixir Fest 2019に行って参ります
恥ずかしながら、東京にいくのは人生で2度目で迷わずに行けるかが心配..

清流elixirを代表して参加しようと思っていたら
いつも勉強会に来て頂ける参加者の方が全員参加するようでワロタ

こんなすげー方達の話を聞ける機会は滅多にないので楽しんできます
次回の勉強会はElixirの実装力を上げるために競プロ問題をいくつかElixirで解いてみようと思ってます

【続編】Responderで作成したAPIをheorkuにデプロイするまで

やりたいこと

www.okb-shelf.work

前回の記事でResponderを使って作成したAPIをherokuにアップしたい
野望としてはDockerに乗せてアップしたかったのだが、時間的な都合もあり
とりあえずはDocker無しで動くものを一旦デプロイした

公式のサンプルが当てにならなかったのでまとめておく

herokuへのデプロイ

以前、作成したコードを一部抜粋

import responder

api = responder.API()

@api.route("/")
def first_api(req, resp):
  resp.text = "first success!!"

if __name__ == '__main__':
  api.run(address="0.0.0.0")

localhostを呼び出すと「"first success!!"」が返ってくるのみ

まずはheroku にappを作成する

> heroku create

git経由でherokuにupしたいので

> git init
> git remote add heroku (herokuのappのpath)

をセット

次にheroku用にファイルを変更 & ファイルを追加

#api.runのaddressに0.0.0.0を追記
if __name__ == '__main__':
  api.run(address="0.0.0.0")

Procfileを作成して以下を追加

web: python server.py

このままpushするとこんなようなエラーが出ることがある

error: failed to push some refs to .....

こいつはPipfile.lockが原因で発生していたようだ
Pipfile.lockを先に削除しておく

あとはいつも通り、herokuにpushする

> git add .
> git commit -m "first commit"
> git push heroku master

pipenvを使っているのでインストールに少し時間がかかる
無事にデプロイされたかを確認してみる

> curl https://pacific-temple-90936.herokuapp.com/
first success!!%

あ、いいですね
デプロイに関しては以上です。以下自己満足

docker-composeでのResponder環境の作成

公式ドキュメントに記述があるが
僕の手元ではこのDockerfileは上手くrunできなかった(COPYが上手くいってないっぽい)

他のDocker構成を探してこちらの方のものを参考にした
pythonをベースにpipenvをinstall

FROM python:3.6.4
RUN mkdir /api
WORKDIR /api
ADD Pipfile /api/
RUN pip install --upgrade pip \
    && pip install pipenv \
    && pipenv install
ADD . /api/

次にdocker-compose.ymlを作成
pipenvを使ってpythonファイルを実行する

responder:
  build: .
  command: sh -c "pipenv install && pipenv run python server.py"
  ports:
    - "5042:5042"
  volumes:
    - .:/api

buildしてupして動作を確認してみる

> docker-compose up -build

無事にbuildが完了してサーバーが立ち上がる

Building responder
Step 1/6 : FROM python:3.6.4
 ---> 07d72c0beb99
Step 2/6 : RUN mkdir /api
 ---> Using cache
 ---> 7b7862fffcbf
:
:
Successfully built __________
:
:
responder_1  | INFO: Uvicorn running on http://0.0.0.0:5042 (Press CTRL+C to quit)

お馴染みに試す

> curl http://0.0.0.0:5042/
first success!!

環境を用意することは出来たが...

悩みの種

こいつをherokuにデプロイするところで詰まっている
今回は仕事の関係で時間制限があったため、止む無く上記の方法を選択した
herokuの方ではH=10やらメモリーオーバーのerrorになる
原因が分からず切磋琢磨中....また進展ありましたらブログを更新します