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 では空文字列が得られる。

Haskell でネットワーク - zyxwvの日記

で紹介されているように、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 をあわせた感じ (実装を見てみたらもっと複雑なことしてた)。

一般的な例外処理

なんか書こうと思った (というか一回書いたんだけど) けど、よくまとまってる記事があった。

2006-06-13 - syd_sydの日記 - haskell

紹介されてるのは今でいう古い例外モジュールだけど、例外の型を明らかにしないといけないこと以外はあんまり変わってないんじゃないかな。たぶん。

あと IO エラーの記事では ErrorT がどうのと書いたけど、ErrorT はあくまでエラーモナドモナド変換子なだけで、例外処理は関係ないものだった。エラーモナドと例外は別物。