Hatena::Grouphaskell

knenetのhaskell手記

2007-10-01

getArgs 06:49 はてなブックマーク - getArgs - knenetのhaskell手記

今回はコマンドライン入力を試してみよう。

import System

main = do x <- getArgs
          print x

getArgsはPreludeに含まれていない。特殊なIOだからね。

そこで、Systemをimportする。

GHCiで、:set argsを使うとコマンドライン入力をテストできる。

最近のバージョンだと:mainで出来るみたいだけど……アップデートした方がいいかな。

*Main> :t main
main :: IO ()
*Main> :t getArgs
getArgs :: IO [String]
*Main> :set args hatena
*Main> main
["hatena"]
*Main> :set args hige hoge
*Main> main
["hige","hoge"]

mainは相変わらずIO ()で、getArgsはIO [String]。Stringは[Char]だから、リストのリストだ。

スペースで分けられたコマンドラインのリストを返してくれる。便利……なのか?

*Main> :set args "C:\nanika arg"
*Main> main
["\"C:\\nanika","arg\""]

""で括られていても平気で分けてしまうぞ? 抑制できたりするのかな?

まあ、getArgsの挙動はさておいて、main関数の方が問題だ。


mainは引数を持たないのに、副作用を持っている。正確には、持っているかのように見える。

main自体はIO ()で固定なので、getArgsが何をしたかに頓着しない。

xの中身が変わることは看過する。型が変わらなければ問題ない。

printが画面に表示することも気にしない。表示はプログラムの外で行われていることだ。


main関数の中で、<-というのが出てくるが、これが怪しい。

*Main> :t do x <- getArgs
<interactive>:1:3: The last statement in a 'do' construct must be an expression

調べようとしても、式ではないと言われてしまう。

動作としては、IO [String]から[String]を取り出してxに格納しているのだが。

これは関数ではない? よく見ると関数の形ではないな。

そもそも、関数はそれ自体が値になるのだから、剥き出しで値が出ているのはおかしい。

letのようなものだと考えればいいのだろうか?

MaybeとIO

そういえば、do記法モナドの書き方なのである。

昨日の自作Maybeとのアナロジーで理解できないだろうか?


print xは、x -> IO ()だ。引数はIO xではないので、これを中継する関数がある。

apl (IO x) f = f x

これだ。これを使えば、doを使わないでmainを書ける。

import System

apl (IO a) f = f a

main = apl getArgs print

試してみる。

*Main> :r
Compiling Main             ( ./getArgs.hs, interpreted )

./getArgs.hs:3:5: Not in scope: data constructor `IO'
Failed, modules loaded: none.

あ、IOはデータ構築子ではないってエラーが出た。そういえばそうだ。

Prelude> :i IO
newtype IO a
  = IO (GHC.Prim.State# GHC.Prim.RealWorld
	-> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))
  	-- Imported from GHC.IOBase
instance Monad IO 	-- Imported from GHC.IOBase
instance Functor IO 	-- Imported from GHC.IOBase

dataではなくて、newtypeで宣言されている。データ構築子はないんだろうか?

さて、次はnewtypeMonad、Functorをキーワードに調べてみようかな。


newtype 07:50 はてなブックマーク - newtype - knenetのhaskell手記

これはHaskellWikiの説明で分かりそうだ。

newtypeの項目を見てみよう。

dataとの違いは、値の評価されるタイミングらしい。

dataだと、データ構築子は型が合っているかチェックされるけど、newtypeだと、評価されるタイミングまでチェックされない。

それで……どうなんだろう。とりあえず、dataで宣言するより中身を無視できるってことかねえ。

IO

まあ、IOのnewtype宣言に戻ってみると、データコンストラクタはやっぱりIOっぽいなあ。

でも、中身がかなり怪しい。関数の形をしてるようだけど、RealWorldとか、(# #)というのがわからない。

もう少し別の例を当たってみることにしよう。

2007-09-30

knenetです。ニーニと読みます。よろしくお願いします。

これまで、haskellを調べて、どうやって使うんだとぼんやり考えているだけでしたが、

実際に触って、動作を確認して学習することにしました。

そういう学習をしばらく行い、やっていけそうな感触を得たので、ここに住むことにしました。

以前の記録は微妙にhttp://www41.atwiki.jp/knenethaskell/pages/1.html:titie=wikiとして残っています。残っているかな?

抱負 13:12 はてなブックマーク - 抱負 - knenetのhaskell手記

今のところ考えている目標はクリアライトという名前のお絵描きツールを作ることです。

パーサとGLUTが使えることを当てにしてhaskellを選択しました。

現在web上で情報を集めて、ゆるゆる学習しているところです。

堅苦しいのは苦手なので、適当にやります。


型コンテナ 14:33 はてなブックマーク - 型コンテナ - knenetのhaskell手記

モナドという言い方にはまだ慣れないけど、その概念に近づきつつある気がする。

data MyType = Fail | Val Int deriving (Show,Eq)

と書くと、データ構築子のみのデータと、Intを入れたデータの2種類のデータを扱える型が作れる。

これを使って、Intの計算をするが、計算できない形の場合、Failを返す関数を作る。


まあ、二値を加算して、制限を越えた数値になったら失敗という関数にしよう。Int型だし。

add a b | (a + b < 0)||(a + b > 256) = Fail
        | True = Val (a + b)

これだけだと入力と出力の型が異なるので、1 + 10 + 100みたいに結合させることが出来ない。

Failになったら計算できないし。そこで、次のような関数を用意する。

apl Fail _ = Fail
apl (Val a) f = f a

Failになったら計算を止めて、そうでない場合は値を取り出して計算を続ける。

これで、MyTypeからaddへ値を送れる。あと、MyTypeから値を取り出してIntにする関数も必要だ。

ej Fail    = 0
ej (Val a) = a

こいつの挙動はちょっと怪しいけど、今は気にしないでおこう。

まあ、定義はこのくらいにして、とりあえず使ってみよう。

*MyData> add 100 100
Val 200
*MyData> add 200 200
Fail

定義通り。完璧なチェックが必要な訳じゃないので次。

*MyData> apl Fail (add 1)
Fail
*MyData> apl (Val 10) (add 10)
Val 20
*MyData> apl (apl (Val 10) (add 10)) (add 10)
Val 30
*MyData> apl (apl (Val 100) (add 100)) (add 100)
Fail
*MyData> apl (apl (apl (Val 100) (add 100)) (add 100)) (add 100)
Fail

ちゃんと動いてるみたいだ。しかし見づらいなあ。中置演算子として書いてみよう。

*MyData> (Val 10) `apl` (add 10) `apl` (add 10)
Val 30
*MyData> (Val 100) `apl` (add 100) `apl` (add 100)
Fail

見通しが良くなった。Val Intを取り込んで、一つずつ関数適用されているのが見て取れる。

ejの使い方はこう。

*MyData> let vadd x y = add (ej x) (ej y)
*MyData> vadd (Val 100) (Val 100)
Val 200
*MyData> vadd (Val 200) (Val 200)
Fail

*MyData> let vaddInt x y = ej (vadd x y)
*MyData> vaddInt (Val 100) (Val 100)
200
*MyData> vaddInt (Val 200) (Val 200)
0

これでMyTypeとIntの関係を尽くせたかな?

add :: Int -> Int -> MyType
vadd :: MyType -> MyType -> MyType
vaddInt :: MyType -> MyType -> Int

ej :: MyType -> Int
add 0 :: Int -> MyType
apl :: MyType -> (Int -> MyType) -> MyType
*MyData> let iapl x = apl (add 0 x) 
iapl :: Int -> (Int -> MyType) -> MyType

最後、一つ追加したけど、これだけあれば、MyTypeとIntを相互変換しつつ計算できると思う。

ただ、FailからIntに変換するときに情報が失われるので、適当に使うとエラーの元になるよね。

実際に使うときは、値を返すだけでなくて、エラーを伝える必要があるかな。

Maybe

型コンテナに型を入れて、データの種類で状態(ことなる型)を表すことをした。

これで、元の数値型より表せる状態が増えて便利になった。


こんな便利な表し方が、標準で付いていないわけがない。これに丁度当てはまるのがMaybeだ。

*MyData> :i Maybe
data Maybe a = Nothing | Just a 	-- Imported from Data.Maybe
instance Eq a => Eq (Maybe a) 	-- Imported from Data.Maybe
instance Monad Maybe 	-- Imported from Data.Maybe
instance Functor Maybe 	-- Imported from Data.Maybe
instance Ord a => Ord (Maybe a) 	-- Imported from Data.Maybe
instance Show a => Show (Maybe a) 	-- Imported from GHC.Show

というか、Maybeの説明を読んで、どう使うか試しただけだけど。

Maybeは一般的なので、機能を制限したものを作った方が理解しやすかったからね。

これを見ると、aに入る型は何でも良いらしい。Nothingの使い方はMyTypeのFailと同じだ。

で、Eq,Showはもちろん、Ordサブクラスでもある。

そして、Monadインスタンスだ。……という読みで良いのかな?


Monadは一般的すぎるんだよなあ。一般的なものほど難しい。まあ、一つの面は見えたと思う。

そしてArrowはもっと一般的だというのが……。使いこなせたら表現力が高くなるよねえ。