開発環境の整備とプロジェクトディレクトリ
ちゃんとしたプロダクトを作るにあたり、まずは開発環境を整えないといけない。 ソース管理は当然なので、今ならgithubが妥当だろう。早速ユーザ登録して、 新規リポジトリを開始する。この辺の詳しいところは、以下の書籍を参考にした。
GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)
- 作者: 大塚弘記
- 出版社/メーカー: 技術評論社
- 発売日: 2014/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (13件) を見る
コラボレーションするようなモノではないので少々オーバースペックな感じはするが、 自宅でバージョン管理システムを運用するのは大変面倒くさいのでやはりクラウド サービスが楽だ。
新しく、"picfinder"というリポジトリを作ったので、これをローカルにcloneしてくる。
$ git clone https://github.com/eijian/picfinder.git
簡単そうなところで、指定したディレクトリ内に存在するJPEGファイルの中で、同じ 画像であるものを探し出してレポートしてくれるプログラムを作ってみようと思う。 さて、Haskellでビルドやテストを繰り返し行うには、Cabalというのを使うと良さそう。 この辺は以下の書籍を参考にした。
関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)
- 作者: 大川徳之
- 出版社/メーカー: 技術評論社
- 発売日: 2014/11/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
まずは初期化をする必要があるらしい。
$ cabal init Package name? [default: picfinder] picfinder Package version? [default: 0.1.0.0] 0.1.0.0 Please choose a license: : 8) BSD3 : Your choice? [default: (none)] 8
とりあえずプロダクト名、初期バージョン、そしてライセンスを選ぶ。 Cabal(かHaskell界)のしきたりなのか、バージョンは4つの数字で表す ようにする、と先の[関数プログラミング…]に書いてある。まずは、 0.1.0.0で始めておく。
名前とかを入力した後、今回のプログラムは画像関係なのでカテゴリは Graphicsを、また実行プログラムとしておく。
Project category: : 10) Graphics : Your choice? [default: (none)] 10 What does the package build: 1) Library 2) Executable Your choice? 2
あとはmainが入るソースファイル名やHaskellのバージョンなどを聞かれたが よくわからないのでデフォルトのままで。とりあえず最後まで進んだので良しとする。
Generating LICENSE... Warning: LICENSE already exists, backing up old version in LICENSE.save0 Generating Setup.hs... Generating picfinder.cabal... You may want to edit the .cabal file and add a Description field.
あと、ソースツリーをどうするか考えないといけないがルートにバラバラいろんな ファイルやディレクトリが散らかっているのは嫌いなので、ソースとテストと文書用に 3つだけ作った。トップディレクトリは次のようになった。
.git <= gitが勝手に作った? .gitignore <= github登録時に指定 LICENSE <= githubで登録時に選択すると作られるがCabalが作った方を採用 README.md <= githubで登録した時に作られた Setup.hs <= Cabalが生成 doc/ picfinder.cabal <= Cabalが生成 src/ test/
次回からは少しずつプログラムを作ってみる。
簡易cat (2)
ファイル内容を表示するだけでは寂しいので、コマンドラインに オプションを追加してみる。ややこしいのは大変なので、先頭に 行番号を表示する -n モドキだけ。
-n オプションがある場合は各行の表示をする時に番号を付加する。 行番号はいわゆる「無限リスト」を各行とzipで組にしてみた。
Cat4.hs
-- -- Cat4 -- module Main ( main ) where import System.Environment main :: IO () main = do xs <- getArgs putFiles $ checkArgs xs checkArgs :: [String] -> (Bool, [String]) checkArgs [] = (False, []) checkArgs (x:xs) | x == "-n" = (True, xs) | otherwise = (False, (x:xs)) putFiles :: (Bool, [String]) -> IO () putFiles (_, []) = putStr "" putFiles (b, (x:xs)) = do cs <- readFile x putStr (decorate b cs) putFiles (b, xs) decorate :: Bool -> String -> String decorate False cs = cs decorate True cs = unlines $ map tr (zip [1..] $ lines cs) tr :: (Int, String) -> String tr (n, l) = (show n) ++ "\t" ++ l
単に最初の引数が"-n"かどうかをチェックしているだけなのに 結構邪魔くさいことになっている。 doブロック内でできることできないことについて、下記ではまった。
putFiles (b, (x:xs)) = do cs <- readFile x putStr (decorate b cs)
putStrのところ、今回は短いのでいいが、一旦変数にバインドしようとして 単に代入文を書いたらコンパイルエラーになった。
cs' = decorate b cs
putStr cs'
しょうがないのでputStr一行にまとめてしまったが、あとで試したらletを 使えば大丈夫そう。
let cs' = decorate b cs putStr cs'
一方で where句ではエラーになった。csがスコープにないと怒られたので where句の中に"cs <- readFile x"を書いてもエラーになってしまう。
putStr cs' where cs' = decorate b cs
letとwhereの違いを確認しておこう。
次回は使えそうなプログラムを作ってみる。
簡易cat - コマンドライン引数
はじめに、このブログはHaskellの入門記事でも何でもないので、その辺りはご期待には添えないのであらかじめお断りしておくことにする。単に、私がHaskellで何かしらソフトウェアを作るということのモチベーションを維持するためであり、またその過程を記録するものである。記述が大雑把なのもご容赦願いたい。ただせっかく稚拙で恥ずかしいソースを晒すので、願わくば「高位のHaskell使い」の方からHaskellらしからぬところ、より的確な関数/ライブラリなど、改善につながるご助言をいただければ幸いである。
さて、実用的なアプリケーションを作るにはコマンドライン引数を扱えないと話にならない(と考えた)ので、その辺を確認しておく。サンプルとして超簡易的なcatコマンドを作成する。
STEP 1
引数に指定したファイルを単に表示するだけのcatを作ってみる。まずは、最初の引数のファイルだけを表示する仕様とする。二番目以降の引数は無視。ちなみに、これではcatとは名ばかりである…。
Cat1.hs
module Main ( main ) where import System.Environment main :: IO () main = do (x:xs) <- getArgs putFile x putFile :: String -> IO () putFile f = do cs <- readFile f putStr cs
実行してみる。
$ ghc -o cat1 Cat1.hs $ ./cat1 Cat1.hs module Main ( : putStr cs
一応動いた。getArgsはリストを返すので最初の要素だけを取り出してみた。ファイルの読み出しと標準出力への書き出しはとても簡単。readFileで読んでputStrするだけ。ちなみにこの辺のテキスト処理的な簡易コマンドの書き方については、この本を参考にした。関数プログラミングの解説書として云々という評価はあるが、私的にはHaskellで実用アプリを作るという事ではなかなかいいアプローチで説明もわかりやすいと思う。ただしAmazonではもう新品は売ってないのかな?
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/06/01
- メディア: 単行本
- 購入: 25人 クリック: 314回
- この商品を含むブログ (328件) を見る
STEP 2
次に複数のファイル名を与えてそれらを順に表示させる事にする。これで一応catを名乗ることができる? 複数のファイル名はリストで渡されるので、リストの各要素に対し同じ処理を実行するには map というのがいいらしい。なので先ほどのプログラムにちょっとmapを付けてみる。
Cat2.hs
module Main ( main ) where import System.Environment main :: IO () main = do xs <- getArgs map putFile xs putFile :: String -> IO () putFile f = do cs <- readFile f putStr cs
コンパイルすると…
$ ghc -o cat2 Cat2a : Cat2a.hs:14:3: Couldn't match type ‘[]’ with ‘IO’ Expected type: IO () Actual type: [()] :
怒られた。確かに map の型は「(a -> b) -> [a] -> [b]」なのでmainの型と合わない。さすがに"純粋な" map をここで使う事に無理があるのか?あきらめて再帰関数に変えてみた。
Cat3.hs
module Main ( main ) where import System.Environment main :: IO () main = do xs <- getArgs putFiles xs putFiles :: [String] -> IO () putFiles [] = do putStr "" putFiles (x:xs) = do putFile x putFiles xs putFile :: String -> IO () putFile f = do cs <- readFile f putStr cs
うまく行った!(実行結果は割愛)
ちなみに最初は再帰の底(putFiles [] = do のところ)を書いていなかったので実行時に怒られた。この辺、慣れが必要か。でも 'putStr ""' という処理が適切かどうかはわからない。
続きは次回。
まずは "Hello, World!" から
実用的なプログラムを作ることが目的なので、実行ファイルを
作成できなくては始まらない。Haskellの本だと対話環境を使った
リスト操作や再起などの説明が多いが、そうではなくてまずは
コンパイルしてみる。お決まりの"Hello, World!"でも書いてみる。
(Greeting.hs) main = putStrLn "Hello, World!"
Rubyほど短くないが、かなり簡潔に書ける方だと思う。Javaだと
いろいろ修飾が大変、というのもHaskellに流れてきた理由の一つ。
コンパイルする。
$ ghc Greeting
これで、"Greeting"という実行ファイルができる。簡単だ。
出力ファイルの名前指定(-o オプション)や最適化(-O, -O2)も
OK。(前は"--make"をつけていたが今は要らないらしい)
http://d.hatena.ne.jp/kazu-yamamoto/20140206/1391666962
$ ghc -o gre -O2 Greeting
大きなプログラムになるとモジュール分割とか出てくるので、
その辺も書いておく。mainの型も。
(Greeting2.hs) module Main ( main ) where main :: IO () main = putStrLn "Hello, World!"
最初、モジュール名は何でもいいと思ってJavaみたいにファイル名と
同じにしてたら失敗した。ghcは「Mainモジュールの"main"」がないと
実行ファイルを作ってくれないみたい。最初の例ではモジュール名を
書かなかったので勝手にMainとして処置してくれたらしい。
Mainの後ろの括弧内に外部へ公開したい名前を羅列するそうだ
(カプセル化みたいに?)。
最後に、画面から名前を入力させてあいさつさせてみる。
(Greeting3.hs) module Main ( main ) where main :: IO () main = do putStrLn "What's your name? " name <- getLine putStrLn ("Hello, " ++ name ++ ". Nice to meet you!")
実行するとこんな感じ。
$ ghc -o gre Greeting3 : $ ./gre What's your name? Taro <= 入力 Hello, Taro. Nice to meet you!
できた。
Haskellコンパイラを入れる
どうやらHaskellのコンパイラとしてはGHCがメジャーということらしい。もろもろライブラリも使いたいのでHaskell Platformを入れるのがよいそうで。自宅はMacで、パッケージ管理はHomebrewを使っている。
$ brew install haskell-platform Warning: haskell-platform-2013.2.0.0 already installed
ああ、前に分けもわからず入れたんだった。ということでHomebrewの更新もやってしまおう。
$ brew update Cloning into '/usr/local/Library/Taps/homebrew-dupes'... : ==> Deleted Formulae : catdoc haskell-platform mpio texmacs :
あれ?もう一度インストールを試すと
$ brew install haskell-platform Error: No available formula for haskell-platform We no longer package haskell-platform. Consider installing ghc and cabal-install instead: brew install ghc cabal-install :
どうも"haskell-platform"という名前ではパッケージをインストールできなくなってしまっている。。。
指示通り、ghcとcabal-installを入れてみる。
$ brew install ghc cabal-install Error: ghc-7.6.3 already installed To install this version, first `brew unlink ghc' ==> Installing dependencies for cabal-install: gmp, ghc ==> Installing cabal-install dependency: gmp ==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/gmp-6.0.0a. ######################################################################## 100.0% ==> Pouring gmp-6.0.0a.mavericks.bottle.tar.gz 🍺 /usr/local/Cellar/gmp/6.0.0a: 15 files, 3.2M ==> Installing cabal-install dependency: ghc ==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/ghc-7.8.3.m ######################################################################## 100.0% ==> Pouring ghc-7.8.3.mavericks.bottle.1.tar.gz 🍺 /usr/local/Cellar/ghc/7.8.3: 5742 files, 724M ==> Installing cabal-install ==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/cabal-insta ######################################################################## 100.0% ==> Pouring cabal-install-1.20.0.3_1.mavericks.bottle.1.tar.gz ==> Caveats Bash completion has been installed to: /usr/local/etc/bash_completion.d Error: The `brew link` step did not complete successfully The formula built, but is not symlinked into /usr/local Could not symlink bin/cabal Target /usr/local/bin/cabal is a symlink belonging to haskell-platform. You can unlink it: brew unlink haskell-platform To force the link and overwrite all conflicting files: brew link --overwrite cabal-install To list all files that would be deleted: brew link --overwrite --dry-run cabal-install Possible conflicting files are: /usr/local/bin/cabal -> /usr/local/Cellar/haskell-platform/2013.2.0.0/bin/cabal ==> Summary 🍺 /usr/local/Cellar/cabal-install/1.20.0.3_1: 5 files, 18M $ ghc -v Glasgow Haskell Compiler, Version 7.8.3, stage 2 booted by GHC version 7.8.3 :
何とか入ったらしい。でも、本当にこの手順で良かったのか?