スポンサーリンク

2016年2月19日

What is OTP?

とある錬金術師の万能薬(Elixir)

Wait a minute

Erlang/OTPについて、そもOTPって何?って言うのがよく分かっていなかった。
なので、OTPについて簡単にまとめた記事。

GOAL

実施する前に設定したことと実際にやってみてどうなったのかを記載します。

Goal & Outcome

設定した目標と出てきた成果について。
(タスクの記載で重複するためGoalとOutcomを統合して記載します)
  • 頭の中にだけある理想
    • Erlang/OTPについてリサーチを行い、OTPを使っている・使っていないの区別ができるようになる
    • Erlang/OTPを自分以外の人へ説明できる知識を習得する
    • Elixirから意識してOTPを使うことができる力を身に付ける
  • 非情なタスク
    • [x] OTPとは何なのかをリサーチしブログの記事としてアップする(4h)
      • [x] プログラミングErlangのOTP入門をリサーチする(30m)
      • [x] Erlangの公式ページにある”What is OTP?”を翻訳する(1h30m)
      • [x] 振返り用としてブログの記事にまとめる(2h)
    • [] Elixirから自分の意思でOTPを使いサンプルプログラムをブログへアップする(2h)
      • [] ElixirからOTPを使うサンプルプログラムの設計を行う(30m)
      • [] ElixirからOTPを使うサンプルプログラムへ着手する(1h30m)
  • 残酷な現実
    • ギャップ
      • OTPを他者へ説明することはできない
    • 実施結果
      • 1442~1523(41m) プログラミングErlangからのリサーチ終了
      • 1544~1710(1h26m) Erlang公式ページからのリサーチ終了
      • 1735~1835(1h) ブログ用の記事として作成完了
  • 特記
    • Elixirのプログラムについては、これから着手のため特に振り返りはなし。

Analyze

成果の分析結果について。
OTPが何なのかについては判別がつくようになった。
OTPを自分が学んだままの知識として伝えることはできると思うが、
それを噛み砕いて説明・疑問への返答などはまだまだ知識が足りないためできないレベルにある。
本からのリサーチで見積もり時間より11m超えている件について。
興味本位にソースコードまで追いかけてしまったのが原因。

Learn

  • OTPが何なのか、どうやって使うのか概要レベルで習得できた
  • GOAL-IPはもう少し、ファジーにしてもよいかもしれない
    • 少し振返りに時間を掛け過ぎてしまった

Improve

改善すべきこと・できることをについて。
おそらく現段階にて、OTPの概要レベルでしか触れることができていない。
OTPを説明できるようになるためには、ドキュメントやプログラミングErlangを読み込むことと、
実際に動くコードを実装しイメージを固めることを何度も行っていく必要がある。
(つまり知識の習得->コードの実装を繰り返し、いつも通りの積み重ねをするだけ)
見積もり時間は守れたはず、自制心が足りない。
興味はあとで、必要なところをピックアップして読み飛ばしていくこと。

Prepare

次の行動に必要な準備について
  • プログラミングErlangはパソコンとセットで置く(ティッピング・ポイントにする)

Content

Dev-Environment

  • OS: Windows8.1
  • Erlang: Eshell V7.2.1, OTP-Version 18.1

Index

What is OTP?
|> Erlang/OTPって何?
|> もっと詳しく!

Erlang/OTPって何?

色々と書いてありました。
氏曰く、OTPはOpen Telecom Platform、フレームワーク、アプリケーション・オペレーティングシステムの一種、ライブラリや手続きの集合体…etc
結局何?
(サーバを構築するための)プラットフォーム。
OTPの中核は?
ビヘイビアと呼ばれるものが中核的な概念。
コールバックモジュールによって変化させられるアプリケーションフレームワーク。
もっと詳しく!
OTPは、Erlangのライブラリと、これらのシステムを開発するためのミドルウェアを提供する設計原理のセット。
これは、他の言語におけるデバッグとリリースハンドリングツールに対してインターフェースするように、
独自の分散データベース、アプリケーションが含まれているとのこと。
(公式サイトの説明を翻訳しただけ…)

Note:

OTPのライセンスは、"Erlang Public License"。
(Mozilla Public Licenseの派生とのこと)

もっと詳しく!

大体、ここら辺のことをまとめてOTPと呼ぶらしい。

Application

Erlang/OTPは、コンポーネントの数に付属する、それぞれがいくつかの特定の機能が実装されている。
コンポーネントはErlang/OTPの用語で呼ばれるアプリケーション。
最小限のシステムは、次の2つのアプリケーションで構成されている。
  • Kernel - アーランを実行するために必要な機能
  • STDLIB - Erlang標準ライブラリ
アプリケーションのコンセプトは、プログラム構造(プロセス)とディレクトリ構造(モジュール)の両方に適用される。
最も単純なアプリケーションは、すべてのプロセスを持っているが機能モジュールの集合体で構成されていない。
このようなアプリケーションは、ライブラリアプリケーションと呼ばれている。
(ライブラリアプリケーションの例はSTDLIB)
プロセスとアプリケーションは、最も簡単な標準的な振る舞いを使用して監督のツリーとして実装されている。

Supervisor

Erlang/OTPでの基本的な考え方は監督の木です。
監督の木は、労働者と監督者の考え方に基づいてプロセスを構造化モデルにする。
  • 労働者とは、実際の作業を行う計算を実行するプロセス
  • 監督者は、労働者の行動を監視するプロセス。(何かがうまくいかない場合、スーパーバイザは労働者を再起動することができる)
  • 監督の木は、フォールトトレラントソフトウェアとしての設計とプログラムになる。(監督者と労働者への階層的な配置のコードにできる)
プログラムがクラッシュしたときのためにスーパーバイザがある。

Behaviour

監視ツリーにおいて、プロセスの多くは同様のパターンに従って、類似した構造になっている。
例えば、監督者は構造が類似している。
構造の間の唯一の違いは、彼らが監督する子プロセスです。
労働者の多くは、サーバ・クライアント関係のサーバー、有限状態マシンまたはエラー・ロガーなどのイベントハンドラ。
ビヘイビアは、これらの一般的なパターンに形式化されている。
汎用部分(ビヘイビア・モジュール)と特定の部分(コールバック・モジュール)で処理するためのコードを分割すること。
ビヘイビア・モジュールはErlang/OTPの一部。
スーパバイザのようなプロセスを実装するには、
ユーザーが機能のみを定義されたコールバック関数のセットをエクスポートし、コールバックモジュールを実装する必要がある。
  • Behaviour Module
    • application
    • supervisor
    • gen_server
    • gen_fsm
    • gen_event

Note:

gen_serverのビヘイビアを実装するときにやること

1. コールバックモジュールの名前を決める
2. インターフェース関数を実装する
3. コールバックモジュールの6つの必須になるコールバック関数を実装する
コールバックは課題の本質的な部分を担い、ビヘイビアはそれ以外の本質的でない部分を担う。
つまり、共通部分はビヘイビアにして、特殊な部分や課題ごとに代わる部分はコールバックにすればいい。

オブジェクト指向で言うなら抽象化と具象化のようなものでしょうか?(かなりざっくりとしてイメージ的には)

Other

リリース関連や、デバッグなんかのツールがある。
OTPアプリケーションのパッケージ化なんかに使われると思う。

Speaking to oneself

ざっくりと得た知識をつらつら書いていっただけになった。
書いているうちに自分の中で理解が進むかと思ったけど、
残念ながらそんなことはなかった。
しばらくは、ドキュメントの読み込みとソースコードの作成・実行が主体となるでしょうね。
というか、やらないと知識が増えないし自分の中に落とし込めないのです。
とりあえず、一般的に使われるパターン(?)があるらしいので、
プログラミングErlangにあるパターンを使ってElixirで練習してみます。
まぁしかし、先に調べておいて良かった。
smtpのプログラム直すのにも使えそうだし、今日から始めるElixirでのOTP利用!!
っというか、すごく基本的な設計思想になるみたいだから、Elixirやるなら必須だわここら辺。
自分が無知であるってことをTwitterでぎゃーぎゃー喚いていた感じだな…恥ずかしい///

Bibliography

2016年2月16日

学習サイクルの整理と再構築

Goal

  • 勉強の仕方が雑になってきたので、ちょっと振り返る
  • 自分の勉強の仕方を整理・再構築する
(既にして良くない目標になっている)

Dev-Environment

  • 紙とペン

Wait a minute

最近、勉強の仕方が雑になってきた。
方向性とかも無秩序にやりたいことをやっているし、しっかりと定まっていない。
例え趣味の領域だったとしても、ちょっとこれはいかんという状態です。
そのため、今まで学んだことを取り入れつつ自分の勉強方法を整理し再構築しようと思います。
回り道になってしまうが、このまま無軌道に進んでいくよりかはましでしょう。
皆さんやっていることを私もやっているだけですが…目標を基準としたゴール指向的な勉強に立ち返ります。

Index

My learning method “GOAL-IP”.
|> Overview
|> G: Goal
|> O: Outcome
|> A: Analyze
|> L: Learn
|> I: Improve
|> P: Prepare

Overview

最近の自分の勉強を振返ってみるとします。
自分の恥を晒している気がするが、気にしない方向で…
  • 最近のやり方(Elixirのプログラムやるとき)
  1. Elixirの記事やライブラリなど情報を収集する
  2. 面白そうなものをピックアップする
  3. 自分でもできそうなものを選択する
  4. 何をやるか決める
  5. どうなっていればいいのか目標を決める
  6. 何も考えずプログラムする
  7. 振返りをするため、アウトプットするためブログ用の記事を書く
  8. ブログにアップする
  9. 1に戻るもしくは、できなかった場合は5.に戻る
※ 但し途中で興味が移っていることがある(一度に一つが理想なのにタスク割り込みをかけている。しかも自分で…)
方向性も分析もくそもない…ただやりたいからやっただけ…
たまにはそれもいいんだが、常にこれだと成長があまりできない。
それに見積もりも抜けている…
定量的かつ分析ができるように計測、測定ができないとコントロールもハンドリングもない。
ダラダラと勉強をしているのと変わらない状況ですね。
1万時間の練習に関して以下のようなことが言われている。
なお、1万時間行う必要のある努力は「deliberate practice(真摯な練習)」と呼ばれる訓練であって、
単にダラダラとおざなりな練習ではダメということには注意が必要です。
練習時間と練習の質を、きちんと区別できていないのです。
漫然と好きなように学び、球を打って走り、鉛筆
を走らせ、歌い奏で、踊るような一万時間は楽しい趣味にすぎない。
方向性を持ち、自己の分析を伴う、集中した一万時間の鍛錬だけが
専門性を成立させると言われているが、誰でもやっていることだ。
さらに時代に合わなければ、才能と積み上げも
検討はずれとなる。それほど無情な世界だ。
ギギナ・ジャーディ・ドルク・メレイオス・アシュレイ・ブフ(されど罪人は龍と踊る)
質が悪い練習の状態 = くそみたいな勉強法を実施している
これでは達人になるのなんて夢の又夢…
(私がそこまでなる必要があるのかはともかく、どうせやるならなりたい)
というわけで、一旦基礎的な部分に立ち返ろうかと思います。
  • GOAL-IP
何をどうすればいいのか、どこに行けばいいのか明確にするために書く。
ネーミングセンスについては自分でも微妙なの知っているので、突っ込まんでください…
語呂を少し合わせましたが、割と自然に出てきました。
GOALの部分は実施する・した内容。
以下の四つで構成されている。
  • Goal: 目的(振返りできるように定量的かつ具体的なもの。見積もりもここに入る)
  • Outcome: 成果(実施した結果出てきたことと達成度。事実が欲しい、自分の気持ちなど不要)
  • Analyze: 分析(できなかったことの原因・要員・ギャップなど。できたことはさらに改善できることなど)
  • Learn: 学び(実施して学んだこと、気付いたこと、自分の気持ちとかも書いていいよ?)
IPの部分は次のために行うこと。
以下の二つで構成されている。
  • Improve: 改善(改善するための内容、次のプランにおける具体的な行動を書く)
  • Prepare: 準備(改善するために必要な次の準備を書く)
全体に言えることは、分析できる内容であることと、具体的な内容を書くこと。

G: Goal

目標・目的とする具体的な内容を書く部分です。
そして、その目標・目的に到達するための計画、方針なども書いていきます。
見積もりもここに入ります。
多少長くなってしまってもいいので、
目標・目的は具体的でかつ定量的に分析できることが望ましいです。
具体的な数値を使うと分かりやすくなる。
目標・目的に関しては、今日中に実施可能、到達可能な内容を書く…これ大事。
できないこと書いても仕方がない。できないことは見通しが立てられない。
例えば、「今日中にゲーデル、エッシャー、バッハ―あるいは不思議の環を読破し理解する」…ヾノ・∀・`)ムリムリ
また、他の人から見て理解され納得される内容だと、他者からのフィードバックが得やすいですね。
一つの視点として意識すると良い目標・目的が書けると思います。
悪い例)
  • 今日中にElixirの文法を覚える
  • ElixirのMailmanライブラリを使ってメールを作成し送信する
最近の目標ですね(笑)
上記の悪い例を元に改善した目標。
良い例)
  • 30mでElixirの文法の基本的な型(整数、少数、文字列)を学習する
    • 10mでElixir Schoolにある基本的な型を学習し、書き方を見なくてもプログラムできるようにする
    • 20mでブログの記事としてまとめアップする(振返りができる状態を作る)
  • 4hでElixirのMailmanライブラリを使い、自分のGmailアカウントでテストメールが受信できている
    • 30mでMailmanライブラリのREADMEを翻訳する
    • 1hでMailmanライブラリのExampleを実行する
    • 30mでテストメールを送信し、MailCatcher(Ruby)で受信を確認する
    • 1hでテストメールを送信し、Gmailから受信を確認する
    • 1hで実施した内容を振返り、ブログの記事としてまとめる(今後のための振り返り)
これが良い例なのかはともかくこれでかなり具体的になったかと。
といっても、ある程度メタな部分や曖昧な部分があってよいと思います。
厳格に進めることが目的ではありませんから。
重要なのは、「いつまでに誰が何をやってどうなっていればいいのか」でしょうから。
目標・目的は行動するための指針を決めるためのものです。
目標・目的はあくまで効率的に進めるためのツールにすぎません。

O: Outcome

実際に実行してみて出てきた成果に関して書く部分です。
隠し立てはいりません。出てきた事実を書けばいいのです。
(ここに気持ちなど不要!)
何をやろうとして、どうなったのかの現状、できあがった成果物、もしくは完了・未完了のタスクを書く。
達成度を書くのもこの項目でおこなう。
例)
完了したタスク、未完了のタスク
  • [] 4hでElixirのMailmanライブラリを使い、自分のGmailアカウントでテストメールが受信できている
    • [x] 30mでMailmanライブラリのREADMEを翻訳する
    • [x] hでMailmanライブラリのExampleを実行する
    • [x] 30mでテストメールを送信し、MailCatcher(Ruby)で受信を確認する
    • [] 1hでテストメールを送信し、Gmailから受信を確認する
    • [] 1hで実施した内容を振返り、ブログの記事としてまとめる(今後のための振り返り)
現状
  • テストメールを送信したが、Gmailまで届いていない
  • WireSharkでパケットの分析を行ったが、Gmailへtls接続する部分で失敗している
  • 振返りのブログ記事は未着手
  • 達成度:60%
達成度が60%の理由。Goalのときに5つのタスクに分割している。
その内、3つのタスクが消化されているため。
かなりざっくりですが、詳しい内容書いていくと短くまとまらない…
そして、時間も考慮にいれていない…この人はたして何時間掛かったんでしょうね(笑)
重要なのは、何が終わって何が終わってないのか分かればいいかと。

A: Analyze

成果に対しての分析結果を書く部分。
できなかったことの原因・要員・ギャップなど。できたことはさらに改善できることなど。
(メトリクス分析なんかはここに入れる…予定)
例)
できたことについて
  • テストメールの送信で設定方法を調べるのに1h掛かった(見積もり+30m)
    • Exampleの部分に設定の記載はあったが、詳しい内容について記載されている部分がどこにあるのか探すのに時間が掛かった
    • Mailmanライブラリで利用しているライブラリのソースコードに記載があったので、次からはソースコードも意識する
できなかったことについて
  • tlsやsmtpについての知識が足りないため、失敗した原因が何故なのか分かっていない
    • opensslを使って送信したメールは受信できていた
    • telnetでは、Gmailへ送信できなかった(STARTTLSで通信が終わってしまう)
OutcomeとAnalyzeの境界に曖昧な部分があるかと思います。
そこら辺はあまり気にせず書いていきましょう。
出てきた成果の内容を何故何故と分析することと覚えておけばいいです。
先に進んで迷うくらいならもう一度サイクルを回して改善すればいいんです。

L: Learn

実際に実行してみて学んだこと、気付いたことを書く部分です。(自分の気持ちとかも書いていいよ?)
やった後とやる前での自分の中で変わったことを書くと言い換えてもいいかもしれない。
そのため、目標・目的として書いてないことでも気づいたことがあれば書いても構いません。
例)
  • Mailmanライブラリからテストメールを送る方法が分かった(ローカル限定)
  • MailCatcher(Ruby)と言うものの存在を知った。また、使い方が分かった。すごく便利!

I: Improve

分析結果から抽出できる課題をどうやったら改善できるのか、そして次のプランにおける具体的な行動を書く部分。
例)
  • ライブラリの仕様を調べるとき、まずソースコードのドキュメントを参照する
  • STARTTLSとは何なのか?インターネットを使いリサーチする。それでも分からなければチームメンバーに聞く。
  • STARTTLSとGmailの関係は何なのかを調査し、STARTTLSでGmailへの接続をできるようにする
  • 過去にドキュメントがないかリサーチする

P: Prepare

改善するための行動に必要な準備を書く部分。
あらかじめ、できる準備を行い、
「~がないからできない」などの言いわけを起こさせないようにするため、
この項目に行っておくことを書いておく。
例)
  • 今日の現状をチームへ共有する(同症例がないか聞いてみる)
  • TLSについて本をパソコンと一緒に置いておく

Note:

What is ティッピング・ポイント?
ティッピング・ポイントとは、行動を起こさない言いわけ、あるいはやる・やらないの分かれ目となる小さな違いのこと。

簡単に言ってしまえば、あらかじめ準備をしておいて言いわけさせないようにする。

Speaking to oneself

こんな感じに進めて使っていくもの。
毎日のことなので、ぶっちゃけ格式ばったフォーマットはいりません。
また、大きな振り返りはまた別にやりますので、
次の日まで残っていて、次の行動が取れればいいだけのものです。
後半息切れしてしまった。
まだ荒が目立ちますが、サイクルを回していく内に改善していきます。
今日の2hほどで作った内容としてはまずまずのできじゃないかと。
メトリクス分析やゲーミフィケーションと結びつけられるといいんだが…
そこら辺は学習が足りないのでまだ無理ですね。
そしたら2hで終わることはなかったですが(笑)
かなり基礎的な部分になりますので、まずここから悪習を直します。

Bibliography

2016年2月15日

ElixirからSSLモジュール(Erlang)を使い、smtp.gmail.comに接続しメールを送る

Goal

  • ElixirからSSLモジュール(Erlang)を使い、smtp.gmail.com:465へ接続する
  • ElixirからSSLモジュール(Erlang)を使い、Gmailのアカウントへメールを送信する

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.2.1, OTP-Version 18.1
Elixir: v1.2.0

Text

前回は、ErlangのSSLモジュールを使ってsmtp.gmail.com:465に接続し、
自分のGmailアカウントへメールを送信することをやりました。
今回は、ElixirからSSLモジュール(Erlang)を使って同じことをやります。
前回、ErlangでSSLモジュールを使うために、
幾つかApplication.start/1をしましたね。
今回は、mixファイルのapplication/0に記述して自動で起動させます。
“:asn1”、”:crypto”、”:public_key”、”:ssl”を追加してください。

File: mix.exs

defmodule SendGmail.Mixfile do
  use Mix.Project

  def project do
    [app: :send_gmail,
     version: "0.0.1",
     elixir: "~> 1.2",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  def application do
    [applications: [:logger, :asn1, :crypto, :public_key, :ssl]]
  end

  defp deps do
    []
  end
end
実際の変換したコードを見る前に前提知識となる部分を記載します。
「知ってるよ!」って人は読み飛ばしてください。
大体がErlang側の文法(?)についてになりますので…
  • Erlangでのダブルクォーテーションの扱い
Erlangでダブルクォーテーションで囲むと文字リストとして扱われます。

Example:

"abc". => [97, 98, 99]

> [97, 98, 99] = "abc".
"abc"
> [97, 98, 99] == "abc".
true
  • 文字リストを++した場合
文字リストは整数のリストになるため、
リストに要素を追加するときに使う++でも追加できる。

Example:

iex> 'aaa' ++ '\r\n'
'aaa\r\n'
iex> "aaa" ++ "\r\n"
** (ArgumentError) argument error
    :erlang.++("aaa", "\r\n")
iex> "aaa" <> "\r\n"
"aaa\r\n"
  • Elixirで標準に用意されている関数名とコンフリクトした場合の回避方法

Example:

Kernelのsend/2と関数名が同じなため衝突した。
対処法としては関数名を別にするか、importで除外してやればいい。
== Compilation error on file lib/send_gmail.ex ==
** (CompileError) lib/send_gmail.ex:1: imported Kernel.send/2 conflicts with local function
    (elixir) src/elixir_locals.erl:140: :elixir_locals."-ensure_no_import_conflict/4-lc$^0/1-0-"/3
    (elixir) src/elixir_locals.erl:139: anonymous fn/4 in :elixir_locals.ensure_no_import_conflict/4
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6

defmodule Example do
  import Kernel, except: [send: 2]

  ...
end
他にも__MODULE__を使った方法があった気がする…詳しく調べてないので知らん
では、本題に入りましょう。
まず、Elixir側に変換したソースコードはこんな感じです。

File: send_gmail.ex

defmodule SendGmail do
  import Kernel, except: [send: 2]

  def connect do
    case :ssl.connect('smtp.gmail.com', 465, [{:active, false}], 1000) do
      {:ok, socket} ->
        recieve(socket)
        send(socket, 'HELO localhost')
        send(socket, 'AUTH LOGIN')
        send(socket, :erlang.binary_to_list(:base64.encode('your_gmail_account@gmail.com')))
        send(socket, :erlang.binary_to_list(:base64.encode('application_specific_password')))
        send(socket, 'MAIL FROM: <from@gmail.com>')
        send(socket, 'RCPT TO:<to@gmail.com>')
        send(socket, 'DATA')
        send_no_receive(socket, 'From: <from@gmail.com>')
        send_no_receive(socket, 'To: <to@gmail.com>')
        send_no_receive(socket, 'Date: Mon, 15 Feb 2016 21:31:00 +0900')
        send_no_receive(socket, 'Subject: Noreply')
        send_no_receive(socket, '')
        send_no_receive(socket, 'This is a test mail message')
        send_no_receive(socket, '')
        send(socket, '.')
        send(socket, 'QUIT')
        :ssl.close(socket)
      {:error, reason} -> {:error, reason}
      _ -> "connection falid."
    end
  end

  defp send_no_receive(socket, data) do
    :ssl.send(socket, data ++ '\r\n')
  end

  defp send(socket, data) do
    :ssl.send(socket, data ++ '\r\n')
    recieve(socket)
  end

  defp recieve(socket) do
    case :ssl.recv(socket, 0, 1000) do
      {:ok, result} -> IO.inspect(result)
      {:error, reason} -> IO.inspect(reason)
    end
  end
end
すごく…Erlangばかり…
前回のコードと変わらん(笑)
実行結果はこんな感じです。

Example:

> iex -S mix

iex> SendGmail.connect
'220 smtp.gmail.com ESMTP x10sm38834564pas.37 - gsmtp\r\n'
'250 smtp.gmail.com at your service\r\n'
'334 VXNlcm5hbWU6\r\n'
'334 UGFzc3dvcmQ6\r\n'
'235 2.7.0 Accepted\r\n'
'250 2.1.0 OK x10sm38834564pas.37 - gsmtp\r\n'
'250 2.1.5 OK x10sm38834564pas.37 - gsmtp\r\n'
'354  Go ahead x10sm38834564pas.37 - gsmtp\r\n'
'250 2.0.0 OK 1455543743 x10sm38834564pas.37 - gsmtp\r\n'
'221 2.0.0 closing connection x10sm38834564pas.37 - gsmtp\r\n'
:ok
実行結果は前回と変わりませんね。(変わったら困りますけど…)
さて、このままだといささか汎用性が足りな…全くありませんので、
gen_smtp_client(Erlang)を参考にして少し汎用性のあるコードに書き換えることと、
Elixir側で同一の機能が提供されているものがあれば、そちらの機能を使うように修正します。
今日はここまで、上記の改善案は明日以降に実施する。
(今のままだと日本語は扱えません…)
しかしこれ、改善していくと自然とMailmanと同じようなものになる気がする…(気のせい?)

Bibliography

人気の投稿