Apache Ivy を試す
使うたびに最初のコマンドを忘れる Maven2 にどうしてもなじめないので, Home | Apache Ivy を試す.
今回やろうとしたのは, 高齢者でも効果ある? への依存関係を Ivy で解決する (ひらたく言えば fest の jar を Ivy で落としてくる) ということ. 結局うまくいかなかった.
fest の jar は, http://mvnrepository.com/ や http://mirrors.ibiblio.org/pub/mirrors/maven2/ にはあがっていない. この辺に置いてあれば御茶の子さいさいなのだけど.
pom ファイルは http://svn.codehaus.org/fest/m2/repo/ に置いてあるものの,
pom だけでは Ivy は満足してくれないようだった.
そんなこんなで色々さがしまわった結果, jar 単体ではどこにも置いてなさそうな感じなのだった. Ivy は jar ありきっぽいので, これじゃ無理だよねという話.
参考:
再帰的にディレクトリをたどる - その2
遅延評価をどうこうしようと思ったけど挫折。どっかを seq すりゃいいと思うんだけどなー。FilePath のリストを返さず、find 内で出力までやっちゃうようにすればすぐできそうなんだけど、それじゃ関数的じゃないよいなぁということで。
普通の find と比較していたら微妙に違うところがあったので、それを適当に修正した。
import System.Directory (doesFileExist, doesDirectoryExist, getDirectoryContents) import System.FilePath ((</>)) import Control.Monad (forM) import Control.Exception (catch, evaluate, SomeException) import System.Environment (getArgs) import Prelude hiding (catch) find :: FilePath -> IO [FilePath] find p = do isDir <- doesDirectoryExist p if isDir then find' p else return [p] where find' dir = do names <- getDirectoryContents dir let names' = filter (`notElem` [".", ".."]) names paths <- forM names' (\name -> find (dir </> name)) return (dir : concat paths) main = mapM_ putStrLn =<< find' =<< valid =<< head' =<< getArgs where head' xs = evaluate (head xs) `catch` (\e -> return (e::SomeException) >> return ".") valid p = do dir <- doesDirectoryExist p file <- doesFileExist p return (p, or [dir, file]) find' (_, False) = return [] find' (p, _) = find p
アクションを途中で終了する関数ってないのかなぁ。Bool が False のときには終了、Maybe が Nothing がときには終了的なもの。汎用的な guard というかなんというか。
再帰的にディレクトリをたどる
いわゆる find。forM ははじめて使った。ただこの実装だと、遅延評価の影響でメモリを馬鹿食いする。次回はこれの改善かな。
しかし例外 API をはじめてまともに使ったけども、とても使いやすいとは言えないな。\e を \_ として評価しないようにしても、型があいまいだと GHC にゴネられる。まぁ型レベルの話は評価するしないには関係ないのだろうけどさ。
import System.Directory (doesDirectoryExist, getDirectoryContents) import System.FilePath ((</>)) import Control.Monad (forM) import Control.Exception (catch, evaluate, SomeException) import System.Environment (getArgs) import Prelude hiding (catch) find :: FilePath -> IO [FilePath] find p = do isDir <- doesDirectoryExist p if isDir then find' p else return [p] where find' dir = do names <- getDirectoryContents dir let names' = filter (`notElem` [".", ".."]) names paths <- forM names' (\name -> find (dir </> name)) return (concat paths) main = mapM_ putStrLn =<< find =<< head' =<< getArgs where head' xs = evaluate (head xs) `catch` (\e -> return (e::SomeException) >> return ".")
エラーモナドを使ったエラー処理
ほとんど http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html#1 を写経しただけ。Error クラスのインスタンスにするのにどうすりゃいいかという話さえ分かってしまえば、あとは予想可能な例外として扱えばいいだけ。どうインスタンス化すりゃいいかがいまいち分かっていないのだけど。
import Control.Monad.Error (Error, noMsg, strMsg, throwError, catchError) data LengthError = EmptyList -- ^ リストが空 | OtherError String -- ^ その他のエラー -- ListError を Error クラスのインスタンスにする. 例外として投げられるよ -- うにするため. instance Error LengthError where noMsg = OtherError "A List Error!" strMsg s = OtherError s instance Show LengthError where show EmptyList = "The list was empty!" show (OtherError msg) = msg -- | LengthError を伴う関数の結果につかう型. type LengthMonad = Either LengthError -- | 引数が空のリストだったらエラーを返す head safeHead :: [a] -> LengthMonad a safeHead [] = throwError EmptyList safeHead (x:_) = return x -- | 引数が空のリストだったらエラーを返す tail safeTail :: [a] -> LengthMonad [a] safeTail [] = throwError EmptyList safeTail (_:xs) = return xs main = do put $ safeHead [1] put $ safeHead empty put $ safeTail [1, 2] put $ (safeTail empty `catchError` (\_ -> return [])) where put (Left e) = print e put (Right x) = print x empty :: [Int] empty = []
条件つき alias
extags を ctags に alias するといった、元々存在するコマンドに alias するということは、頻繁とはいかなくともぼちぼちある。vim と vi とか。
こんなとき .bashrc をいくつかのマシンで共有していると、 "ctags がある状態で ctags を extags の alias にしているのだけど、マシン B だけは extags がない" みたいなことがある。マシン B の状態で ctags を実行しようとすると、存在しない extags を実行しようとして command not found となる。
こんなときは hostname で alias するかどうかを切りかえるか、extags が有効なコマンドの場合だけ alias するかの手段が手取り早い。
以下はその後者の例。bash でしか試してないので bash ってことで。
#!/usr/bin/env bash conditioned_alias() { # 引数チェック if [ -z "$1" -o -z "$2" ] then exit 1 fi # 第二引数が有効なコマンドかどうかチェック if [ -x "`which $2`" ] then if [ -n "$3" ] then alias "$1"="$3" else alias "$1"="$2" fi fi } conditioned_alias vi vim conditioned_alias ctags exctags conditioned_alias mount_foo mount_foo.sh 'sudo mount_foo.sh'
3 つ目の引数がある場合には、"第 2 引数が有効なコマンドの場合だけ、第 1 引数を第 3 引数の alias にする" という動作になる。
まぁ、extags とか vim って打てよって話なのだけど。
hGetContents と hClose
hGetContents に渡したハンドラを hClose すると、hGetContents の結果が空文字列になる。
import IO (openFile, IOMode(ReadMode), hGetContents, hClose, bracket) f1 = do h <- openFile "hoge.txt" ReadMode s <- hGetContents h return s f2 = bracket (openFile "hoge.txt" ReadMode) (hClose) (hGetContents) main = do run "f1" f1 run "f2" f2 where run name act = do putStrLn name putStrLn =<< act putStrLn ""
f1 では hoge.txt の内容が得られて、f2 では空文字列が得られる。
で紹介されているように、hGetContents の結果文字列が評価されるまで hGetContents の IO 処理は遅延されるわけだから、その前に hClose すると空になっちゃうよという話。
じゃあこれは?
import IO (openFile, IOMode(ReadMode), hGetContents, hClose, bracket) import Control.Exception (evaluate) f3 = bracket (openFile "hoge.txt" ReadMode) (hClose) (\h -> hGetContents h >>= evaluate) f4 = bracket (openFile "hoge.txt" ReadMode) (hClose) (\h -> hGetContents h >>= return . length) f5 = bracket (openFile "hoge.txt" ReadMode) (hClose) (\h -> hGetContents h >>= print . length)
f3 と f5 は期待した値が得られた。f4 は f2 と同じ話で、評価する前に閉じるからアウト。
evaluate は値を正格評価しつつ IO モナドでくるむ。seq と return をあわせた感じ (実装を見てみたらもっと複雑なことしてた)。