My Favorite Things - Coding or die.

とある技術者の経験記録、的な。

Vagrant + CentOS7 で、RustのWebフレームワークRocketを発射する

Linuxとか環境面詳しくないのでメモ。

Rocketは最近出たRustのWebフレームワーク。
https://rocket.rs

環境

VirtualBox: 5.0.30
Vagrant: 1.9.1
CentOS: 7.1
Rust: 1.16.0-nightly
Rocket: 0.1.2

ちなみにこれを書いている時点で最新のCentOSは7.3だったけれど、vagrant upで立ち上げたら途中で止まってしまったので断念。

VagrantでCentOS7を立ち上げる

まずは発射台を準備。

$ mkdir launch-pad
$ cd launch-pad

Boxイメージは、Chef社がOSSとして公開してる「Bento」を使う。
(弁当箱とは誰がうまいことを)
https://atlas.hashicorp.com/bento

$ vagrant init bento/centos-7.1
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Vagrantfileは以下のようにしてプライベートネットワークとして接続できるようにしておく。

Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.1"
  config.vm.network "private_network", ip: "192.168.33.11", auto_config: false
end

原因はよく分かっていないのだけれど、auto_config: falseを設定しておかないと、立ち上げたVM上で正しくIPアドレスが振り当てられなかった。

VMイメージを起動。(Boxの取得から始まるので時間かかるよ!)

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
...

SSHで接続。

$ vagrant ssh
-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
[vagrant@localhost ~]$

基本設定

ファイアウォールを切ってしまう。(仮想環境なので)

$ sudo systemctl stop firewalld
$ sudo systemctl disable firewalld

SELinuxが無効か確認。今回は無効になっているのでそのまま。

$ getenforce
Permissive

Rustのインストール

Rocketでは構文拡張を利用するため、NigthlyなRustが必須。(2016/12/30時点)

Rust1.14.0で公式ツールとなったrustupをインストールする。 https://rustup.rs

$ curl https://sh.rustup.rs -sSf | sh
info: downloading installer
...

選択肢が表示されるが、とりあえずデフォルトの1)を選択。

Current installation options:

   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation

Rust is installed now. Great!

To get started you need Cargo's bin directory in your PATH environment variable. Next time you log in this will be done automatically.

To configure your current shell run source $HOME/.cargo/env

パスを設定してくださいと言われるので、.bash_profile に記述しておく。

# Rust
source $HOME/.cargo/env

再読込して現在のシェルに反映させておく。これでRustが使えるようになった。

$ source ~/.bash_profile
$ rustc --version
rustc 1.14.0 (e8a012324 2016-12-16)

そしてRustを最新版(Nightly)に変更。

$ rustup default nightly
$ rustc --version
rustc 1.16.0-nightly (4ecc85beb 2016-12-28)

Hello, world

Rocket用のプロジェクトを作成して、そこに移動。

$ cargo new hello-rocket --bin
     Created binary (application) `hello-rocket` project
$ cd hello-rocket

Cargo.toml の依存ライブラリに以下を追加。

[dependencies]
rocket = "0.1.2"
rocket_codegen = "0.1.2"

src/main.rs を以下のように編集。このあたりは公式ドキュメントどおり。

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}

そしてビルド・実行・・・するがエラーになる。

$ cargo run
...

error: aborting due to previous error

error: Could not compile `unicase`.

To learn more, run the command again with --verbose.

どうやらgccが入っていないのが原因らしいのでインストール。

$ sudo yum -y install gcc
...

今度こそロケットを打ち上げられた。

$ cargo run

Finished debug [unoptimized + debuginfo] target(s) in 79.19 secs
 Running `target/debug/hello-rocket`
🔧  Configured for development.
=> listening: localhost:8000
=> logging: Normal
🛰  Mounting '/':
=> GET /
🚀  Rocket has launched from http://localhost:8000...

VM内でcurlを叩くとちゃんと動いていることが分かる。

$ curl localhost:8000
Hello, world!

80ポートで起動する

ここからはあんまり正攻法じゃない。 ので、とりあえずこの方法でうまくいった程度の話。

Rocketのドキュメントを見ると、Productionで実行するには以下のようにすればよいと書いてある。

$ sudo ROCKET_ENV=staging cargo run
sudo: cargo: command not found

が、エラーとなる。

これは環境変数PATHがsudo時に引き継がれないのが問題らしい。

$ env | grep PATH
PATH=/home/vagrant/.cargo/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin
$ sudo env | grep PATH
PATH=/sbin:/bin:/usr/sbin:/usr/bin

で、調べるといろいろと情報が出てくる。

CentOS で sudo 時に実行ユーザーのPATHを引き継ぐ http://qiita.com/ogwmtnr/items/3ec2fab50d069a3cf335

sudoで環境変数を引き継ぎたい http://mikio.github.io/article/2012/03/10_sudo.html

が、何を試しても一向にcargoのパスが引き継がれない。

仕方ないのでルートユーザーでもrustupを入れてしまう。(同様なので方法は割愛)

$ su -
# rustupのインストールと設定

ちなみにパスワードは「vagrant」。

そしてそのままルートユーザーで以下を実行すると、80ポートで起動する。

# ROCKET_ENV=staging cargo run

🔧  Configured for staging.
    => listening: 0.0.0.0:80
    => logging: Normal
🛰  Mounting '/':
    => GET /
🚀  Rocket has launched from http://0.0.0.0:80...

ホストマシンからブラウザでアクセスしてみるとちゃんと動いている。 http://192.168.33.13

GET /:
    => Matched: GET /
    => Outcome: Succcess
    => Response succeeded.
GET /favicon.ico:
    => Error: No matching routes for GET /favicon.ico.
    => Warning: Responding with 404 Not Found catcher.
    => Response succeeded.

ちなみにデフォルトの8000ポートだと以下のように指定してもアクセスできなかった。
http://192.168.33.13:8000/

ファイアウォールを切ったので問題ないと思ったのだが、何か設定がマズイのかもしれない。

終わり

Linuxとか環境構築とかあんまり経験ないので思ったよりもずっとしんどかった。 まぁしかしVagrantとか楽でいいなと思った。

とりあえず以下は解決したい課題。

  • ルートユーザで実行しちゃってる。(本番環境なら大問題)
  • 8000ポートでホストマシンから接続できるように。
  • Linuxとか詳しくなる。

Swift3: バージョン番号を楽に比較する

なんか面倒だったので書いた。

これがSwift使いの戦い方。たぶん。

Swiftコンパイラに詳しくないけど、こういった構造体でラップするだけのコードって最適化後はゼロコストになるのかしら?
Haskellnewtypeはゼロコストだったはずだけど)

SwiftでApplicative Style

書いたことなかったので書いてみた。

我流なのでいい感じに出来てるかは微妙だけどApplicative Styleがパーサ周りで有効活用できるという確かな実感は得られた。 Swift 4で(?)ジェネリクスが強化されれば、Haskellの型クラスのようにProtocolでもジェネリクスが使えるようになるのかしら?

iOSオールスターズ2に参加してきた感想

帰ってブログ書くまでがイベントって言われたので書くよ! eventdots.jp

ブログなので当然ですけれど、私が思ったことをそのまま書いてます。 (とりあえずファーストインプレッションをダンプしてきます)

あ、登壇者の皆様、本当に貴重な情報をありがとうございました!

RxSwift in Practice

  • 状態をストリームとして表現するのは良いと思った。
  • しかし、やっぱ殆どのケースでは大げさ=オーバースペック感は否めない。
  • インジケータ非表示にするの忘れる、くらいであればテストで十分感あり(発表でもあったけど)。
  • やはり状態がたくさんある状態で、最新のViewに更新される必要がある、みたいなケースが活きるのではないか?
  • コードレビューをランダムに回すのは良いアイディアだと思った。
  • 学習コストが高い、っていうのは素直に認めなきゃと思う。

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法

  • ギャグ満載。
  • Fat VC=「肥満体質な」VCって表現は分かりやすかった。(コードの臭い匂い的な意味とも通じる)
  • クリーンアーキテクチャは大げさ過ぎる気がしたけど、ViewController/Presenterまわりの実装の仕方はありだと思った。
  • 「君の名は」を見ておくべきだった?

Type-safe URL Routing in Swift

  • サーバサイドの話かと思ったけれど、URLスキーム起動とかでiOSも該当する。なるほど。
  • ApplicativeFunctorでパース処理を書けるのは良いと思った。(JSONパーサとかでも活用されてるけど)
  • 途中から全く分からん状態!
  • 関数型もっと勉強しよう・・・

Using PDF in iOS

  • 濃い・・・。
  • でも実際にPDFを扱う必要が出た場合に、間違いなく参考になる貴重な情報。

Xcode8で開発はどうかわったのか

  • 変わってない。
  • Visual DebuggingでAutoLayoutの制約が確認できるようになったのは良いと思った。
  • FPS Performance Gauge、いずれ使ってみたい。
  • プロファイラも進化したなぁ、と思う。

これから始めるProtocol Buffers導入

  • Googleが考えた、シリアライズフォーマット。
  • バイナリで高速(サイズがJSONの半分以下に)
  • .protoファイルからSwiftコードを生成できる
  • ありだとは思うけれど、本当にパフォーマンスが重要な場面以外では導入は控えて、JSONにすべきだと思った

DIを前提にしたiOSアプリの設計

  • DIコンテナを使うべきか、というとそこまでは判断できないけど、DI設計はありだと思った。
  • 依存性注入を意識したコードスタイルになっていれば、DIコンテナを導入しなくても、テストなどは相当楽になるはず。
  • Storyboardからインスタンス生成されるとプロパティインジェクションになっちゃうのは確かに。(個人的にもコンストラクタインジェクション派)

Swiftらしい表現を目指そう

  • すごく分かりやすかった。
  • イニシャライザは文章っぽく書かなくてもいいってのは知らなかった。
  • プロトコルHaskellで言えば型クラス)を意識したコードを書くのは、もうちょっと日頃意識したいと思った。

おわり

  • 楽しかった!
  • 途中でピザの匂いで食欲そそられた。
  • イベント会場キレイだった。
  • 関数型はもっと勉強していくよ!

Qiita記事「Haskell チュートリアル (Haskell Day 2016)」を読んでメモ

以下を読んでメモ。

Haskell チュートリアル (Haskell Day 2016) から学んだこと http://qiita.com/hiratara/items/169b5cb83b0adbfda764

Shell以降は新しいことばっかりだったのと、元記事の完成度が高すぎて途中からは殆ど写経みたいになってしまった。

基本

暗黙的に副作用を起こす式がなく、明示的に副作用を起こす式(IO型)がある。
言い換えると、IO型が登場しない関数は副作用がないことを保証できる。

Hindley/Milner型推論アルゴリズムによる推論で、型は1つも書かなくても大丈夫。(readなどは例外)

Shellプログラミング

副作用の塊なので相性が悪いと思われるが(私はそう思ってた)、そうでもない。

1行目にシェバング、2行目にstack runghcコマンド、を書いておくことで直接実行できる。

#!/usr/bin/env stack
-- stack --resolver lts-6.15 --install-ghc runghc --package turtle
...

REPLでは:lでソースを読み込んで、:mainで実行できる。
その後、:r:main とすると効率的に開発できる。

turtle

Haskellでshell相当の関数が実装されたもの。

以下の2行のおまじないで使える。

#!/usr/bin/env stack
-- stack --resolver lts-7.0 --install-ghc runghc --package turtle

{-# LANGUAGE OverloadedStrings #-}
import Turtle

REPLで試す場合、Stringのオーバーロード設定を忘れずに。

Prelude> :set -XOverloadedStrings

MonadoIO

MonadoIOは型クラスで、IOはそのインスタンス

MonadoIO io => Text -> io ()Text -> IO () と読みかえて問題ない。

<-=の違い

=Haskellの言語仕様で、let式、where句で使える。

<-バインドの糖衣構文で、do専用。

main = do
    let title = "now: "
    now <- date
    putStrLn (title <> show now)

<>は文字列(StringまたはText)の連結。(Monoid)

main関数はmain :: IO ()という型を持つ。

printfとformat

Turtle.Formatモジュールに定義されている。

*Main> printf ("My name is "%s%". "%d%" years old.\n") "shu1" 0
My name is shu1. 0 years old.

Turtleの関数

-- 引数取得
arguments :: MonadIO io => io [Text]

-- ファイルパスに変換
fromText :: Text -> Turtle.FilePath

-- 最終更新日付を取得
datefile :: MonadIO io => Turtle.FilePath -> io UTCTime

-- Textへ変換
repr :: Show a => a -> Text

mapM IOアクション リストという形式で繰り返し処理が出来る。 (「通常の引数をとってモナドに包まれた値を返す」関数をリストにmapするときに使うものっぽい)

mapM print [1,2,3]
mapM echoModified args

IOアクションはモナドから値を取り出した時に、はじめて実行される。

nestedIO = do
    putStr "Hello, "
    return (putStrLn "I/O!")

main = do
    r1 <- nestedIO
    r2 <- r1 -- I/O! はここでの評価によって出力される

ストリーム処理

UNIXのパイプの実現に、IO ...では役者不足。
Shell ...というTurtleが提供する型を利用するのが良い。

IO ...:すべての結果を一度に返す `Shell ...``:複数行の結果を1行ずつ返す

入力の関数

入力はShell ...という型で表現される。

empty :: Shell a
stdin :: Shell Text
input :: FilePath -> Shell Text
select :: [a] -> Shell a
"INPUT" :: Shell Text

出力の関数

出力はShell ... -> IO ...という型で表現される。
最終的にIO ...になるので、doブロックで書ける。

sh :: Shell a -> IO ()   -- 出力を捨てる
view :: Shell a -> IO () -- 出力を表示
stdout, stderr :: Shell Text -> IO
output :: FilePath -> Shell Text -> IO ()  -- ファイルに出力
shell :: Text -> Shell Text -> IO ExitCode -- 外部コマンドに流し込む

パイプの関数

パイプ(||の間)はShell ... -> Shell ...という型で表現される。

id :: Shell a -> Shell a
limit :: Int -> Shell a -> Shell a
inshell :: Text -> Shell Text -> Shell Text -- 外部コマンドを通す

組み合わせ

-- 通常の関数適用
stdout (limit 10 (input "sample.txt"))

-- $によるカッコの省略
stdout $ limit 10 $ input "sample.txt"

-- 関数合成
(stdout . limit 10 . input) "sample.txt"

-- `&`による関数適用(UNIXライク)
input "sample.txt" & limit 10 & stdout

fold

Control.Foldlに定義されたFoldを使って、Stream ...を回収できる。

import qualified Control.Foldl as Fold

fold :: Shell a -> Fold a r -> IO r
foldIO :: Shell a -> FoldM IO a r -> IO r

foldlfoldrと違って、初期値は不要。(foldl1foldr1と同じ)

fmap

すべての行を関数によって変換する。

fmap :: (a -> b) -> Shell a -> Shell b

ls "." & fmap (format fp) & stdout

grep

PatternShell ...をとり、grepされたShell ...を作る。

grep :: Pattern a -> Shell Text -> Shell Text
select ["Haskell", "Turtle", "Shell"] & grep (plus dot <> "ll") & stdout

hasprefixsuffixが便利。

select ["Haskell", "Turtle", "Shell"] & grep (suffix "ll") & stdout

パターンについてはドキュメントを参照。

do記法

IO ...と同様に、Shell ...do記法が使える。(モナドだから)

doブロックの戻り値はShell ...となる。

戻り値はループ処理となる。

lsPrintf = do -- Shell ... の do ブロック
    file <- ls "."
    -- 全ファイル分、ループ処理される
    printf (fp%"\n") file

main = do     -- IO    ... の do ブロック
    lsPrintf & sh

Applicative

[...]Maybe ...IO ...Shell ...のようなコンテナ型を指す。

通常の関数を使った演算が可能なコンテナのこと。

(<*>) :: f (a -> b) -> f a -> f b
(<$>) :: (a -> b) -> f a -> f b

(+) <$> [1, 2] <*> [3, 4] -- => [4,5,5,6]

fがn引数の関数のとき、f <$> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn

(,)(タプル)やコンストラクタに適用すると、一度に複数の方法で畳み込める。

Main Fold Turtle> select [1..6] & (`fold` ((,) <$> Fold.minimum <*> Fold.maximum)) & view
(Just 1,Just 6)

パーサ

Applicativeはパーサで使われることが多い。

パーサの本質は「文字列の消費」と「結果の生成」からなる。

「結果の生成」をApplicativeで演算。

match :: Pattern a -> Text -> [a]

*Main Fold Turtle> match ((,) <$> "a" <> star dot <*> "d" <> star dot) "abcdefg"
[("abc","defg")]

戻り値の差し替え

(*>) :: f a -> f b -> f b -- 右のパーサの結果のみを使う
(<*) :: f a -> f b -> f a -- 左のパーサの結果のみを使う
sed :: Pattern Text -> Shell Text -> Shell Text
pure -- パースしない(結果のみを返す)

-- 文字列が消費されていって、左か右のどちらの結果を扱うか決めている
*Main Fold Turtle> "abcdefg" & sed ("abc" *> pure "xyz") & stdout
xyzdefg
*Main Fold Turtle> "abcdefg" & sed ("abc" <* "def") & stdout
abcg

コマンドライン引数のパーサ

Turtle.Optionsコマンドライン引数をパースできる。

parser :: Parser (Maybe Text, Bool)
parser = (,) <$> optional (optText "dir" 'd' "Target directory")
             <*> switch  "show" 's' "Show module names."

(mDir, isShow) <- options "Count import." parser

演習問題の答え合わせ

letは一つの宣言で複数書ける。

let fileTxt = head args
    file = fromText fileTxt

Haskellでは短い変数名をつけることが多い?(Maybe型の頭にmをつけるのは分かりやすいと思った)

dt <- datefile file

最後がIOアクションで終わる場合はreturnは不要。(でも書いておけば、とりあえずコンパイラは黙らせられる)

<-バインドしなくても、最終行に記述すればIOアクションは処理される。

nestedIO = do
    putStr "Hello, "
    return (putStrLn "I/O!")

-- 解答
main = do
    printIO <- nestedIO
    printIO

-- 私の答え
main = do
    r1 <- nestedIO
    r2 <- r1
    return ()

Turtleの(疑似)パイプ処理は、Shellコマンド実行にしても、関数にしても、&で繋げられる。

find (suffix ".hs") path
    & grepImport
    & (`fold` Fold.length)
    & view

もうちょっと関数の分離を意識すると良いコードが書けるかも。

AtCoderに挑戦 - ACR001、ACR002 :: Haskell

関数プログラミング実践入門の巻末に載っていたAtCoderHaskellで挑戦してみた。

001は、sortとgroupを活用すればすぐ出来ることが分かった。

002は、すごいH本に載っていたチェス盤のナイトのコードを参考にして、リストモナドを使って解いてみた。

実際にコードを書いてみると、以外とスラスラ書けないという思う反面、Haskellは強力だとあらためて感じた。