読者です 読者をやめる 読者になる 読者になる

てくのろじーたのしー

Haskellぺろぺろ

Haskellerから見たElixir

なぜこれを書いたか

案件が回ってきたので書きます。時間がないので雑な説明になります。雑すぎて公開するか悩みましたが、せっかくなので公開しておきます。先に謝っておきますごめんなさい。

自己紹介

趣味でプログラミングをやっている(ruby, railsを2年ほど書いていたのは過去の話)人です。

現在の本業は薬学部生です。平日は実習で潰れるのでここ最近全くプログラミングをしていません。

関数型プログラミングが大好きで、時間がある時期には大阪でShinosaka.hsというコミュニティで勉強会を開いています。

はじめに

一言に関数型言語と言っても多くの言語があります。「関数型言語の最右翼」とも言われている(要出典)Haskellから、ユーザーはエイリアンの見た目をしているというLisp系言語まで・・・なんかこう書くと変態しかいなさそうですね関数型プログラマって。

関数型言語が何であるのかはここでは深くは語りません。多分「ラムダ計算をベースにしている」とか「イミュータブルを基本にしている」とかそんな感じです。というか僕もよく分かっていないので誰かマサカリ投げてください。

ともかく、HaskellもElixirも一般的には関数型言語ということになっています。ナカーマ。

そんな仲間な言語であるHaskellとElixirですが、趣きは全く違います。今回はHaskellとElixirを比較したり、Elixirの特徴を挙げてみたりします。

思想の違い

ガッチガチの静的型付け言語であるHaskellに対して、Elixirは動的型付け言語です。

性質やデータを型として表現することでコンパイル時に多くのバグを発見するHaskellに対して、Elixir(Erlang)は「プログラムには必ずバグが出るものだ。全てのバグや実行時例外を把握してコードを書くことはほぼ不可能なので、エラーが出た時にどうするかを考えよう」という思想です。

具体的には処理をプロセスという実行単位に分割し、それぞれのプロセスが自分の処理だけに集中し、互いにメッセージを投げ合います。そしてどこかのプロセスがエラーで落ちるとスーパバイザという機構によって正常系に復帰します。

プロセスローカルな処理は普通にガツガツ関数で処理を書けばいいので、Haskellを書ける人ならドキュメントを見れば普通に書けると思います。

非同期なメッセージパッシングが関わる処理やプロセスデザインが肝であり鬼門です(多分)(僕もよく分かってない)。

パイプライン演算子

パイプライン演算子で処理を繋げるのも楽しいです。

# 特に意味のないコード
1..1000 # 1から1000を
|> Enum.map(fn i -> i * 2 end) # それぞれ2倍して
|> Enum.sum # 合計して
|> Integer.to_string # 文字列にして
|> String.length # 文字列の長さを出す

型システム

ElixirはHaskellほど型にこだわらないちょっと緩い感じの言語です(強い型付けではある)。一応defstructを使えば型っぽいものは作れますが、その実はただのMap型です。

defmodule Dog do
  defstruct name: "", age: 0
end

%Dog{name: "Pochi", age: 10} # %{}はMapリテラルで、%Dog{}は%{__struct__: Dog}のシンタックスシュガー

みたいにStructが作れます。ElixirにはHaskellの直和型にあたるものはありません。動的型付けなので「パターンマッチの時に適宜チェックしてやれば良い」ぐらいの感覚です。

animal = %Dog{name: "Pochi", age: 5}

case animal do
  %Dog{name: name} -> "#{name}: Bowbow,"
  %Catl{name: name} -> "#{name}: Mew."
end

型をゴリゴリ設計するHaskellerからすると物足りないかもしれませんが、データを単純な形で表現することによって難しいことを考えずにシャカシャカ書いていける感じが僕は好きです。

軽量プロセス

Elixir(Erlang)のプロセスは非常に軽量です。作られたばかりのプロセスのサイズは309 words(≒ 2.4kb)で、プロセスの起動にかかる時間はマイクロ秒レベルです。

そのためにElixirではオブジェクト指向言語インスタンスを作るのと同じくらい軽いノリでプロセスを作ります。実際にプロセスを作るコードを書いてみます。

# 1から10を出力する
1..10
|> Enum.each(fn i -># Enum.eachで1から10のそれぞれに、
  spawn(fn -> # spawnでプロセスを立てて、
    IO.puts i # 数字を出力する
  end)
end)

Haskellもスレッドを簡単に立てることができますが何万スレッドも立てるのはなかなか難しいでしょう。

2017/05/24 GHCの場合スレッドは軽量スレッドのため、数万スレッドなら普通に作れるそうです。ご指摘感謝します。 また、単純な実行速度はHaskellの方が上なので、立てることができるスレッドやプロセス数がそのままパフォーマンスの高さになるわけではないことも付記しておきます。

一方Elixirであれば100万プロセスぐらいであれば普通に動きます。5000万プロセスとかイケるみたいです。

Elixirでプロセス5000万くらい作ってみた - Qiita

Elixir(Erlang)が活きるのは高い稼働率や高い並列性(WebSocketを使うサービスやゲームサーバ)が求められるところだと思います。

アクターモデル

ざっくりと言うとオブジェクト指向のメッセージパッシングを全部非同期にしたような感じです。プロセス1つひとつをアクターと考え、互いにメッセージを非同期に投げつけ合いながら独立して動きます。

状態の持たせ方

HaskellではStateやSTやMVarやTVarやIORefなどを使って状態を持たせますが、Elixirではプロセスに持たせます。プロセス内で再帰することで状態を変えながら外部とメッセージパッシングする感じです。

マクロ

衛生的で手軽なマクロがあります。ASTを受け取ってASTを返すものです。ggればunlessの実装とかたくさん出てくると思うのでここでは説明しません(放棄

まとめ

Elixirはですね

基本的には関数型の、Erlangという言語の上で動いていまして、

若干ゃ型が、動的なところなので、

そういったところで扱いやすいように、

Elixir、あのパターンマッチで…

あとマクロも!あるので、柔軟に…構文を…作れるように、

アクターモデルぅ…ですかねぇ…(謎)

プロセスをスッと作るっことができるっ言語でして

けっこう並列処理が好きなので、

軽々と…100万プロセス5000万プロセスは余裕で動かしてくれますね(信頼)

www.nicovideo.jp

チェビシェフの不等式の証明

記事を書けばチェビシェフの不等式の証明の勉強になると思ったので書きます。案の定勉強になりましたが、予想以上に時間がかかった・・・(主にはてなブログTeXの相性の問題で)

証明のスケッチは

1. 確率分布の分散の定義
2. 確率変数の値によって2つのグループに分ける
3.  x_i - \mu >= k\sigmaの部分を置き換えて不等式にする
4.  x_i - \mu < k\sigmaの部分を捨てる
5. あとは天下り的に

今回は離散型確率分布について証明しますが、 \sum_{} \intに変えれば連続型確率分布の証明になります。



証明

前提として \sigma > 0, k > 0とする。

確率分布の分散の定義より
 {\displaystyle
\sigma^2 = \sum_{i = 0}^n (x_i - \mu)^2 P(X=x_i)
}

 x_iのうち、 |x_i - \mu| k\sigmaより大きいものと小さいものを分ける。

 {\displaystyle
\sigma^2 = \sum_{|x_i - \mu| \geq k\sigma} (x_i - \mu)^2 P(X=x_i) + \sum_{|x_i - \mu| < k\sigma} (x_i - \mu)^2) P(X=x_i)
}

ここで \sum_{|x_i - \mu| \geq k\sigma} (x_i - \mu)^2 P(X=x_i)においては |x_i - \mu| \geq k\sigmaなので、 (x_i - \mu)^2 \geq k^2\sigma^2。従って

 {\displaystyle
\sigma^2 \geq \sum_{|x_i - \mu| > k\sigma} k^2\sigma^2 P(X=x_i) + \sum_{|x_i - \mu| \leq k\sigma} (x_i - \mu)^2 P(X=x_i)
}

 \sum_{|x_i - \mu| \leq k\sigma} (x_i - \mu)^2 P(X=x_i)は正なので、引いても不等号の向きは変わらない。

 {\displaystyle
\sigma^2 \geq \sum_{|x_i - \mu| > k\sigma} k^2\sigma^2 P(X=x_i)
}

 {\displaystyle
\sigma^2 \geq k^2\sigma^2 \sum_{|x_i - \mu| > k\sigma} P(X=x_i)
}

 \sigma > 0, k > 0より、両辺を k^2\sigma^2で割って

 {\displaystyle
\frac{1}{k^2} \geq \sum_{|x_i - \mu| > k\sigma} P(X=x_i)
}

 {\displaystyle
\frac{1}{k^2} \geq P(|x_i - \mu| > k\sigma)
}

証明終わり

physmath.main.jp
physnotes.jp

関数型プログラミングに関わる本の書評

この記事はShinosaka.rb Advent Calendar 2016の19日目の記事です。

持っている本一覧

このACではrubyに限らず、どのような記事でも良いらしいので、現在僕が持っている関数型プログラミングに関係する(と思う)本の書評を書いてみようと思います。

現在持っている関数型絡みの本は

以上9冊です。

並べてみると意外と少なかったです。

現在読んでいる本は『簡約!?λカ娘 始まりの総集編』と、学校で借りた「関数型プログラミングの基礎 JavaScriptを使って学ぶ」です。

学校で借りて読んだ本は「Haskellによる並列・並行プログラミング」、立ち読みしただけなら某IQ145本もあります。

書評

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

現時点、日本語でHaskellを入門するならば第一選択です。

Haskellをやってみたい」という声が聞こえてくるとどこからともなくこの本のリンクを投げつけます。

内容はHaskellの言語仕様やHaskellで使われる概念を順番に説明していくというものです。特にファンクター、アプリカティブファンクター、モナドの説明が具体的で良いです。

ネット上であればファンクターを理解する助けになる記事はいくつかあるのですが、ファンクターのイメージををうまく説明している本はこの本ぐらいだと思います。

この本を読めば、関数の定義の仕方、型の宣言の仕方、パターンマッチ、再帰高階関数、モジュール、型クラス、入出力の基本、ファンクター、アプリカティブファンクター、モナドの辺りが分かると思います。

Haskellを始めたいならとりあえずこの本を買えばいいと思うよ!

ここだけの話、関数型界隈では「すごいH本」という愛称で呼ばれています。エッチな本。

Real World Haskell

Real World Haskell―実戦で学ぶ関数型言語プログラミング

Real World Haskell―実戦で学ぶ関数型言語プログラミング

すごいHaskellたのしく学ぼう!の後にに読むべき本だと思います。中級者向け。

特筆すべき点はその分量の多さです。なんと720ページあります。鈍器です。

実践的なコードの書き方を学ぶことができます。

すごいHaskellたのしく学ぼう!では扱っていなかったモナドトランスフォーマー正規表現やSTM、更にはParsecの使い方やデータベースの使い方(この内容は既に古くなっているかも)、果てはバーコード認識なんかも扱います。盛りだくさんです。

ところどころ内容が古くなっていて、そのまま使うのは難しい部分がありますが、現状ここまで多くのことを扱っているHaskellの日本語の本はないので更なるステップアップを望むのであれば読んで損はないと思います。

Haskellによる並列・並行プログラミング

Haskellによる並列・並行プログラミング

Haskellによる並列・並行プログラミング

この本は図書館から借りて読んだので手元にはないのですが、一応読んだ本なので紹介します。

関数型言語の触れ込みとして「並列化が得意」と言われることがありますが、その真髄を見ることができる本だと思います。

プログラミングで一般的には難しいとされる並列・並行化ですが、Haskellでは強力な抽象化能力と強力な型システムによって比較的楽に並列・並行化を行うことができます。

モナドによる抽象化は並列・並行処理でも存分に使われており、モナドの偉大さが感じられます。Evalモナド、Parモナド、STMモナド辺り。モナドなので小さな並行処理同士を組み合わせて大きな並行処理を組み立てることができます。

Evalモナドを使った並行化では、複数の式を並行に評価することで並行化を行います。

ParモナドはParallelモナドの略で名前の通り並列化を行うのですが、例えばリストのmapを並列化したりします。

STMというのはSoftware Transactional Memoryの略で、これを使うとロックフリーに並行処理を行うことができます。

この本ではEvalモナド、Parモナド、STMモナド以外にもGPUプログラミング、スレッディング、分散プログラミングも学ぶことができます。

ふつうのHaskellプログラミング

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

  • 作者: 青木峰郎,山下伸夫
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2014/10/05
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

「すでに他のプログラミング言語を知っている人がサラッとHaskellの概要を覗くための本」と言ったところでしょうか。内容は「すごいHaskellたのしく学ぼう!」よりも少ないです。

基本的な言語仕様、モナドの概要を知るためであればこの本が向いていると思います。しっかりHaskellを知りたいのであれば、「すごいHaskellたのしく学ぼう!」をお勧めします。

終盤の実践的なプログラムの部分ではパーサーの作り方を知ることができるのでその点は良かったと思います。

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

普通に証明が出てくるような本です。Haskellにおけるデータや関数の性質を確かめるために証明をします。ちょっとレベルが高いかも。僕は十分に理解はしていません。そのうち読み直したいです。

プログラミングElixir

プログラミングElixir

プログラミングElixir

現在、一般的な書店で手に入る唯一の日本語のElixir本です。

言語仕様から始まり、Elixir(Erlang)の売りであるアクターモデルの扱いを中心に説明した本です。

説明は分かりやすいと思います。アクターモデルを全く知らなかった僕ですが、この本を読んでなんとなくアクターモデルのイメージを持つことができるようになりました。

Erlangは耐障害性に重点を置いた言語で、ErlangVMの上で動くElixirもその利点を享受しています。

K10C問題やWebSocketで並行性能が注目されるようになり、ErlangVMのプロセス管理の上手さがにわかに注目を浴びましたが、Erlangの言語仕様はPrologライクでパッと見のイメージで敬遠されがちです。ElixirはRubyライクな言語仕様と極力なマクロとPhoenixというRuby on Railsインスパイアのウェブフレームワークを引っさげて登場しました。シンプルな関数型プログラミングが好きなRailsプログラマにお勧めな言語です。

エーフィーのアトリエ - アランビック錬金術

エーフィーのアトリエ - アランビックの錬金術師

Elixirの同人誌です。

内容はRails TutorialのElixir/Phoenix版と言ったところでしょうか。Ruby/RailsプログラマPhoenixを習得する最短の方法はこの本を読むことだと思っています。

エーフィーのアトリエ Plus - アランビック錬金術

エーフィーのアトリエ Plus - アランビックの錬金術師

上記の本の続編です。

内容は大きく分けてE2Eテストの方法と、Ectoの使い方です。

Scalaスケーラブルプログラミング(第2版)

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第2版

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

いわゆる「コップ本」。

現在、第3版が出ているので今から買うのであればそちらを買った方が良いと思います。

Real World Haskellを超えるページ数を誇り、第2版の時点で744ページ、第3版は752ページあります。こちらも立派な鈍器です。

重厚な言語仕様を持つScalaをしっかりと満遍なく説明した本であり、この本を読めばScalaの基本は十分押さえたと言えるでしょう。

関数型プログラミングでよく使う、ケースクラスと型パラメータをしっかり説明してくれたのは個人的に嬉しかったです。コレクションのAPIを詳しく説明されていたのも好印象です。

ところでScalaはなぜXMLリテラルを採用したのか謎です。XMLを扱うことが最近少なくなってきていると思うのですが、Scala開発当初はそこまでXMLが流行っていたのでしょうか?

関数型プログラミングの基礎 JavaScriptを使って学ぶ

関数型プログラミングの基礎 JavaScriptを使って学ぶ

関数型プログラミングの基礎 JavaScriptを使って学ぶ

まだ読書中なので書評を書くのは憚られるのですが、パラパラと見た限りでは扱っている内容が非常に良いです。

[詳しくは読んでから書きます]

出てくるコードは基本的にJavascriptで、「Haskell怖い」という方にも良いかもしれません。

簡約!? λカ娘 - 始まりの総集編

簡約!? λカ娘 - 始まりの総集編

[読んだら書きます]

2016年12月現在、とらのあなでは売り切れており、ネット上では中古も流れていないようなので入手するのは難しいと思います。僕は運良くtwitter上で譲って頂きました。

終わりに

10冊の書評を書きましたが結構時間がかかりました。3時間ぐらい?

公式のドキュメントは英語のことが多く、ネット上にはまとまったまとまった情報が少なく、「すばやく言語を勉強したい」という時は本を読むのがベストだと思います。

特に右も左も分からない時にはまとまった情報があると非常に学習効率が良いでしょう。公式ドキュメント以外のネットの情報は最新のものや細かいものが手に入る一方、体系立った情報は手に入りにくいですし。

Shinosaka.hs #3 Elixir入門をした

やってしまった〜〜〜!!

f:id:techno_tanoC:20161127121734p:plain

やってしまいました大失態です。持ってくるノートパソコンを間違えました。

僕は現在、2台のノートパソコンを持っています。片方は中古で買ってUbuntuを入れたThinkPadで、もう一方はWindowsの入ったLenovo G510です。

普段開発用に使っているのは前者です。やっぱりLinuxでないとなかなか面倒なので。

ここまで言えばもうお察しの通り、Windowsの方を勉強会に持ってきてしまいました。一巻の終わりです。即死です。

家に取りに帰る時間も無いので、Virtualboxを入れて環境を作ろうとしたのですが、Virtualboxがエラー出て入らず詰みました。

そういうわけでちけんさんのノートの画面をプロジェクタで映しながら、口頭とSlackで説明することとなりました。ちけんさん、ありがとうございます。

そんな感じで最初からグダグダの極みだった第三回Shinosaka.hsです。

内容

今回も会場はSOU Co.,Ltd.の代表である八木さんにオフィスを貸して頂きました。ありがとうございます!

Phoenixではuse Hoge.Webを使うのでquoteぐらいのマクロの知識は必要かと思い、軽くマクロの説明をした後に、Phoenixの入門をしました。

マクロのスライドはこちらです。

phoenixの流れは↓の感じです。ソースコードこちら

  • scaffold(phoenix.gen.html)
  • 静的なページの作り方
  • json api(phoenix.jgen.json)
  • webpackとの統合(?)
  • especの導入方法

全体的にサラッとやっただけで、本格的な処理はほとんどやっていません。

みんなでワイワイできて楽しかったです。

反省

用意が足りませんでした。言い訳をすると、学校が非常に急がしかったんです・・・。今週月曜・火曜は定期試験。木曜・金曜は実習。

改善

  • Winノートは封印することにします。最近Winが必要になったのはGoでクロスコンパイルしたバイナリを試す時ぐらいでしたし、正直無くても問題ないです。

次回のShinosaka.hsでは何をしましょうか?Ectoをやりましょうか。今回はPhoenix自体の説明に終始して、Ecto周りはノータッチだったので。

shinosaka.hs #2 Elixir入門を開催した

2016年9月15日にshinosaka.hs #2としてelixir入門勉強会を行いました。

イベントページはこちら

前回のhaskell入門がアレ過ぎて、かなり凹んだのですがめげずに頑張りました。

最近はelixirが熱いということで、haskell入門を行った前回とは打って変わって、今回はelixirの入門を行いました。rubyでも並行処理が熱いですし。

場所は前回同様、SOU Co.,Ltd.の代表である八木さんにオフィスを使わせて頂きました。本当にありがとうございます。八木さんのご厚意がなければ多分shinosaka.hsは#1すら開けなかったと思います。

勉強会の冒頭には@ogomrさんにLTをしていただきました。内容はプログラミングElixirの翻訳者の1人であり、Rubyでも活躍していらっしゃる笹田耕一さんからのelixirを学ぶことについてのコメントです。今勉強している言語の本の翻訳をした方からコメントということで非常に嬉しかったです。RubyKaigi行きたかった・・・

その後は1時間30分ほどスライドを使った勉強会でした。ところどころネタが入るのは仕様です。

おおまかな内容は

です。

前回がアレだったのは内容を詰め込み過ぎたことが主な原因だったので、今回は「Hello World」「パイプ演算子」「パターンマッチ」に内容を絞ってみました。

それぞれの目的としては

elixirの特徴を語る上でerlangの話は外せないと思ったため、
「HelloWorld」はdefmoduledefIO.putsを見るため、
パイプ演算子は関数型 かつ キャッチーな言語機能を紹介したかったため、
パターンマッチはelixirを書く場合至るところで使うことになる かつ 初めて見ると戸惑いがちなものをできるだけ分かりやすく伝えたかったため、

という意図で選びました。

以下前回からの改善点、反省点、次回へ向けての改善点

前回からの改善点

  • タイポを減らすためにスライドからコードを別ファイルとして抜き出し、ちゃんと動くか試した
  • FizzBuzzとパイプ演算子とパターンマッチではrubyのコードとの対比をした
  • 前回はとにかく詰め込みすぎたので、今回はポイントを絞って内容を決めた
  • スライド中に例題を入れた
  • 次のスライドに行くのが速いので今回はスライドの一枚一枚、できるだけ説明するように意識した

反省

  • タイポ3箇所
  • パターンマッチを正規表現との半端な対比で紹介したせいであまり分かりやすくなかった
  • Elixirに対する僕の理解不足(まだあまり勉強していなかった(言い訳
  • 一枚一枚説明するように意識したけどまだ説明不足感が否めない
  • まだ説明不足感がある
  • 相変わらずテンパると早口になる。というか元々が早口

次回へ向けての改善点

  • 分かりやすいな比喩は難しい。下手な比喩は具体例を増やす方向で補う
    • 時には「これはそういうもの」という説明も必要かもしれない
  • Elixirを勉強しておく
  • もう少し例題があっても良かったかも
  • 簡易なカンペみたいなのを用意した方が良さそう。スライドに対する捕捉はカンペに書いておく
  • 落ち着け
  • 写真を撮る

次回はもうちょっと実践的な内容のElixirの勉強会をする予定です!

Elixirの同人誌(エーフィーのアトリエ - アランビックの錬金術師エーフィーのアトリエ Plus - アランビックの錬金術師)を買わなきゃ(使命感

Haskell勉強会をしたことに関する反省会

2016年7月31日にHaskellハンズオン shinosaka.hs #1を開催しました。

場所は大阪本町の株式会社ソウの代表である八木さんにオフィスを使わせて頂きました。場所がなかなか見つからず困っていたので本当にありがたいです。

勉強会の内容はHaskellの基礎ということで、関数型のベースとなっているラムダ計算、Haskellの関数定義、代数的データ型、パターンマッチでした。

以下反省点

  • スライドが少なく、最終的に1時間ほど時間が余った
  • ハンズオンと銘打った割に手を動かす部分が少なかった
  • あらゆる点で説明が少なく、参加者を置いていってしまった
  • スライドにタイポが多い
  • 説明も速く、「説明だけで終わった」という状態
  • 説明する順番が悪く、予備知識が必要な説明になっていた
  • 十分な説明もなく用語を使っていた(例えば代数的データ型でいきなり「直和型」というワードを出した)
  • slackの有効活用ができていない
  • モナド」、「型クラス」というワードを言ってしまった結果、そこの説明が必要になり、さらにグダグダに
  • 自分自身のラムダ計算の知識の不足

全体的にダメダメですね。「ハンズオンになっていない」ということが一番の問題です。最も重要な部分が達成できなかったです。

以上反省点。以下改善法

  • 具体例から入る
  • 具体例 -> 機能の説明 -> 例題という流れがあれば、スムーズかも?
  • 今回であれば(->)型の見方と関数定義の仕方とPreludeの関数の使い方をじっくり説明するぐらいが適切な分量だった
  • 別言語を使った表現でイメージしやすくする
  • 説明の順番はすごいHaskellたのしく学ぼうを参考にする
  • 『型システム入門』を初心者に勧めてはいけない
  • コンパイラを通すことでタイポを無くす

いっそのことすごいHaskellたのしく学ぼうの読書会にするというのも手ですね。下手に自分で説明するよりも本の方が分かりやすいと思います。

人に教えることの難しさを知りました。次回のshinosaka.hsは未定ですが、次回があればもっと良い勉強会にしたいです。

Rubyのcase式のたくさんの使い方(翻訳)

Ruby Case Statements with Examples

英語の練習のための翻訳

以下翻訳

if / elsif式を使うときはいつでも代わりにRubyのcase式を使うことを考慮してください。この投稿ではいくつかのユースケースと実際にどのようになっているか見てみましょう。

ノート: 他のプログラミング言語ではswitch式として知られています

Rub Case & Range

case式は初めて見た時よりももっと柔軟です。値がどの範囲に落ちるかによってメッセージを表示する例を見てみましょう。

case capacity
when 0
  "You ran out of gas."
when 1..20
  "The tank is almost empty. Quickly, find a gas station!"
when 21..70
  "You should be ok for now."
when 71..100
  "The tank is almost full."
else
  "Error: capacity has an invalid value (#{capacity})"
end

このコードはif / elsif版と比べてけっこうエレガントに見えると思います。

Ruby Case & Regex

whenの条件に正規表現を使うこともできます。以下は商品を消費するのがどのくらい危険かを知らせる最初の文字を持つserial_codeの例です。

case serial_code
when /\AC/
  "Low risk"
when /\AL/
  "Medium risk"
when /\AX/
  "High risk"
else
  "Unknown risk"
end

Ruby Caseを使わない時

単純な1対1のマッピングの時はこのように書きたくなるかもしれません。

case country
when "europe"
  "http://eu.example.com"
when "america"
  "http://us.example.com"
end

個人的には代わりにこっちの方が良いと思います。

SITES = {
  "europe"  => "http://eu.example.com",
  "america" => "http://us.example.com"
}
 
SITES[country]

ハッシュによる解決法は使う上でより効率的で簡単だと思いませんか?

===メソッドではどうなるか

あなたはcaseが内部でどう動いているか不思議に思うかもしれません。最初の例に戻ったとして、これが起こっていることです。

(1..20)   === capacity
(21..70)  === capacity
(71..100) === capacity

見ての通り、Rubyはオブジェクトを左側にして===を呼んでいるので、条件は逆転しています。===はどのクラスにも実装されているただのメソッドです。この場合、Rangeはこのメソッドをその範囲に値が見つかった時にのみtrueを返すように実装しています。

これはRubiniumでどのように(Rangeクラスに)===が実装されているかです。

def ===(value)
  include?(value)
end

ソース: https://github.com/rubinius/rubinius/blob/master/kernel/common/range.rb#L178

Procs + Case

もう一つの~===を実装している面白いクラスはProc`クラスです。

procslambdas*については来たる講座: 'Ruby Deep Dive'で学べます。 無料のレッスンを受けるにはここをクリック

この例では2つのprocsを定義し、ひとつはevenな数字か、もうひとつはoddかをチェックします。

odd  = proc(&:odd?)
even = proc(&:even?)
 
case number
when odd
  puts "Odd number"
when even
  puts "Even number"
end

これが本当に起こることです。

odd.===(number)
even.===(number)

procで===を使うことはcallを使うことと同じ効果です。

結論

あなたはRubyのcase式がどのように動き、どれほどそれが柔軟になれるかを学びました。今こそあなた自身のプロジェクトでそのベストな利用を始める番です。この記事があなたの役に立ちますように!

この投稿が多くの人が学べるようにシェアしてください。

翻訳ここまで。

今回は意訳多めにしました。句の修飾の順番が日本語と違ってなかなか正しい訳をするのは難しいです。

いまさらhaskellの状態管理に関して一言いっとくか

今回の記事はパッと出の思いつきで書いたものなのでぶっちゃけ読まないでいいです。例が面白くないです。

タイトルはそろそろFreeモナドに関して一言いっとくかパクリオマージュです。

「そろそろhaskellの状態管理に関して一言いっとくか」ではありません、「いまさら」です。要するに特に目新しいことではありません。もっと言うと状態管理というかほとんどモナドトランスフォーマーの話です。

導入

純粋関数型言語であるhaskellは「状態がないから状態管理が不得意だ」なんて言われることがあったりなかったりします。かく言う私も、昔はそんなことを思っていました。

しかし実際はhaskellはそんじょそこらのプログラミング言語よりも状態管理が得意かもしれません。少なくともある点においては明確に得意と言えるでしょう。

Stateモナド

状態と言えば英語でStatehaskellのStateモナドはその名前の通り、状態を扱うためのモナドです。

準備運動も兼ねて少し遊んでみましょう。後でStateTを使うのでControl.Monad.Trans.State.Lazyに定義してあるStateを使います。

import Control.Monad.Trans.State

addOne :: State Int ()
addOne = modify succ

doubleEven :: State Int ()
doubleEven = do
  n <- get 
  if even n
  then put $ n * 2
  else return ()
  
main :: IO ()
main = do
  print $ execState (addOne >> doubleEven) 5 -- 12
  print $ execState (doubleEven >> addOne) 5 -- 6
  print $ execState (doubleEven >> addOne) 6 -- 13

状態を裏配線して、getputmodifyで状態を更新したり取得しながら計算を行っています。

StateT

ここからが本題です。StateモナドStateモナド以上でも以下でもありません。初期値を元に計算を行って、結果を受け取る以上のことはできませんし、アプリを作る際に広い範囲で使うようなことは難しいでしょう。

そこでモナドトランスフォーマーの出番です。モナドトランスフォーマーを使えば、モナドの軛を逃れ、更なる力を得ることができます。ほぼどこでも好きなところで状態を扱えるにも関わらず、非純粋な言語のグローバル関数のようにどこでそれが使われているか分からない無秩序なものではありません。

骨組み

ある程度具体的な例を使って説明しましょう。

あなたはあるアプリケーションを作りたくなりました。そのアプリでは最初に設定ファイルから設定を読み込み、時折設定を変更しながら様々な処理をします。現時点で設定をどこかで変更したり、また別のどこかで設定を使うことは予想できていますが、プログラムの全容はまだ見えていません。

今、我々が問題としているのは設定の扱い方についてです。このプログラムが何をするかは誰も知りませんが、状態に関わる部分を作っていきましょう。

とりあえず設定を保持するStateTに加えてExceptTも使うとして、アプリ専用のモナドを構築します。

type App e s a = ExceptT e (StateT s IO) a

runApp :: App e s a -> s -> IO (Either e a, s)
runApp app s = runStateT (runExceptT app) s

evalApp :: App e s a -> s -> IO (Either e a)
evalApp app s = evalStateT (runExceptT app) s

ついでに結果だけを取り出すevalAppも作っておきました。

いろいろ

さて、今回はただの例なので扱う状態は簡単にIntということにして、このアプリで使うであろう色々な関数を書いてみます。

import Data.Char

-- 状態をインクリメントする関数
incState :: Monad m => StateT Int m ()
incState = modify succ

-- 今の状態を出力する関数
printState :: StateT Int IO ()
printState = get >>= lift . print

-- 失敗するかもしれない状態を扱う関数
doubleNonZero :: Monad m => ExceptT String (StateT Int m) ()
doubleNonZero = do
  n <- lift get
  case n of
    0 -> throwE "zero!"
    n -> lift . put $ n * 2

-- ユーザから入力を受け付ける失敗するかもしれない関数
echo :: (MonadTrans t, Monad (t IO)) => ExceptT String (t IO) ()
echo = do
  lift . lift $ putStr "type 'haskell': "
  cs <- lift . lift $ getLine
  if cs == "haskell"
    then lift . lift $ putStrLn "yes, haskell!" 
    else throwE "boo, not haskell"

-- ユーザに指示を仰ぐ失敗するかもしれない状態を扱う関数
tellNumber :: Monad m => ExceptT String (StateT Int IO) ()
tellNumber = do
    lift . lift $ putStr "tell a number: "
    s <- lift . lift $ getLine
    if isNumbers s
      then lift . put $ read s
      else throwE "not number"
  where isNumbers = all isNumber

-- ユーザをバカにする関数
fool :: IO ()
fool = putStrLn "Hey! You fool!!"

一体何に使うのか分からない関数もありますが、とりあえずこんなところでしょうか。

これらの関数は適切に持ち上げることでAppにすることができます

lift incState :: App e Int ()
lift printState :: App e Int ()
doubleNonZero :: App String Int ()
echo :: App String Int ()
tellNumber :: App Int ()
lift . lift $ fool :: App e s ()

感想

聡明な皆さんならば既にお気付きのことでしょう。これらの関数は型を見るだけでその関数が状態を扱えるかどうかが分かります。実際に型注釈にStateTが入っているincStateprintStatedoubleNonZerotellNumberは状態を扱っています。逆に型注釈にStateTが入っていないechofoolは状態を扱っていません。IOが入っているかどうかも同様です。

StateTが入っていれば必ず状態を扱うわけではないですが、確実に言えることは「StateTが入っていない関数は絶対に状態を扱えない」ということです。こうすれば実質グローバルな状態でもわざわざ使わないStateTを書かない限りはどこで状態を使っているかが一目瞭然です。変な場所で状態が変わっているかもしれない心配とはサヨナラ!

最後に

注意するべきことはこのグローバル変数を本当にグローバル変数として良いか考えることです。モナドトランスフォーマーを積み重ねすぎるとパフォーマンスに影響が出ますし、高く積もったモナドトランスフォーマーは見通しが悪く、バグの元になります。状態を扱う部分をモジュールとして切り出せないか考えるべきです。

TAPL4章の型無し算術式の実装

型無し算術式の実装 - プログラミング勉強日記に影響されて実装。

data Term = TmTrue
          | TmFalse
          | TmIf Term Term Term
          | TmZero             
          | TmSucc Term
          | TmPred Term        
          | TmIsZero Term
  deriving(Eq, Show)
  
isnumericval :: Term -> Bool
isnumericval TmZero = True
isnumericval (TmSucc t) = isnumericval t
isnumericval _ = False

isval :: Term -> Bool
isval TmTrue = True
isval TmFalse = True
isval t | isnumericval t = True
isval _ = False

eval1 :: Term ->  Maybe Term
eval1 (TmIf TmTrue x _) = pure x
eval1 (TmIf TmFalse _ y) = pure y
eval1 (TmIf b x y) = TmIf <$> eval1 b <*> pure x <*> pure y
eval1 (TmSucc x) = TmSucc <$> eval1 x
eval1 (TmPred TmZero) = pure TmZero
eval1 (TmPred (TmSucc x)) | isnumericval x = pure x
eval1 (TmPred x) = TmPred <$> eval1 x
eval1 (TmIsZero TmZero) = pure TmTrue
eval1 (TmIsZero (TmSucc x)) | isnumericval x = pure TmFalse
eval1 (TmIsZero x) = TmIsZero <$> eval1 x
eval1 x = Nothing

eval :: Term -> Term
eval t = maybe t eval $ eval1 t

アプリカティブファンクターとmaybeが便利だと思いました(小並感)

Eitherを使うことでどこでeval1が失敗したか分かるようにしてる。本当は失敗したところの1つ前の段階を出力して欲しかったけど眠いのでこれでいいや。

2016/04/13 色々と勘違いをしていたのでソースを修正しました。

ghci > eval $ TmIf (TmIsZero TmZero) TmFalse TmZero
TmFalse
ghci > eval $ TmIf (TmIsZero (TmSucc TmZero)) (TmSucc $ TmSucc $ TmSucc TmZero) TmZero
TmZero
ghci > eval $ TmIf (TmIf TmZero TmFalse TmTrue) TmZero TmZero
TmIf (TmIf TmZero TmFalse TmTrue) TmZero TmZero

eval1の最後の節で評価が終わったことを表すNothingを返すことで、evalで結果(=これ以上評価ができないもの)を返しています。これはOCamlの元ネタにおいて例外を投げたステップで結果を返すことに相当します。

ghciでの最後の例は1ステップ目で、内側のTmIfの第一引数であるTmZeroを評価しようとしてNothingを返すので、このステップの値である全体がそのまま返ってきています。OCamlのコードを読んだした感じでもそういう実装っぽい(OCamlの環境がないので予想)。

TmIfTmSuccTmPredTmIsZeroは1ステップの中で引数(TmIfは第一引数)をもう一度評価するので、そこが評価ができないと全体を返すっぽいぽいぽい!!

f:id:techno_tanoC:20160413231508j:plain

今回はここまで。 最後にみなさんご一緒に。

Haskellと夕立はいいぞ。

elixir1.3で入りそうな機能

こんにちは。最近haskellの書き方を忘れてきたtechno-tanoCです。

「elixirのwhenってEitherっぽいよなーでも失敗した瞬間失敗したものが返ってくるから使いづらいなーもう自分でマクロ作ろうかなー」と思いながらネットの海を漁っていたら(海だけに)、whenelse節が入りそうなコミットを見つけました。

そのコミットのリンクはこちら

issueを見た感じ、「マッチに成功した時は良い感じに平坦化してくれるんだけど、エラーケースではイケてない」ってことらしいです。禿同。

具体的には

with {:ok, res} <- 41,
     do: res,
     else: ({:error, error} -> error; res -> res + 1)
#=> 42

みたいになるようです。

加えてこのコミットwithでガードが使えるようになるようです。やったね。

こっちは

with x when x < 2 <- 4,
     do: :ok
#=> 4

のようになるみたいです。