Haskellで世界のナベアツ問題
※以前C#で書いたネタはこちら→FizzBuzz問題と世界のナベアツ
※後にPowerShellで書いたネタはこちら→PowerShellで世界のナベアツ問題
コンバット越前よろしく、
せっかくだから俺はHaskellで世界のナベアツ問題を書くゼ(ネタがないという意味で)
{- Haskellで世界のナベアツ -} main : IO () main = return (nabeatsuOfWorld [1..40]) >>= print {- 3の倍数と3の付く数字だけアホになり、8の倍数だけ気持ち良くなります -} nabeatsuOfWorld : [Int] -> [String] nabeatsuOfWorld [] = [] nabeatsuOfWorld (x:s) | x `mod` 3 == 0 = (show x ++ "Aho") :nabeatsuOfWorld(xs) -- 3の倍数でアホになる | find (show x) '3' = (show x ++ "Aho") :nabeatsuOfWorld(xs) -- 3の付く数字でアホになる | x `mod` 8 == 0 = (show x ++ "Ahan") :nabeatsuOfWorld(xs) -- 8の倍数で気持ち良くなる | otherwise = show x :nabeatsuOfWorld(xs) find : String -> Char -> Bool find [] _ = False find (x:s) y = if (x == y) then True else find(xs) y
本物のHaskellerであれば、もっと簡潔明快で美しく書けるんだろうけど、
今の自分の力量ではこんなところで落ち着く。まぁそこそこ明快かと。
(追記)
アホ且つ気持ちいいが表現できていませんでしたね(^-^;)
修正版をあげておきます
{- Haskellで世界のナベアツ -} main = return (nabeatsuOfWorld [1..40]) >>= print {- 3の倍数と3の付く数字だけアホになり、8の倍数だけ気持ち良くなります -} nabeatsuOfWorld :: [Int] -> [String] nabeatsuOfWorld [] = [] nabeatsuOfWorld (x:xs) | aho x && ahan x = (show x ++ "AhoAhan") : nabeatsuOfWorld(xs) -- 3の倍数または3の付く数字または8の倍数で、アホ気持ち良くなる | aho x = (show x ++ "Aho") : nabeatsuOfWorld(xs) -- 3の倍数または3の付く数字でアホになる | ahan x = (show x ++ "Ahan") : nabeatsuOfWorld(xs) -- 8の倍数で気持ち良くなる | otherwise = show x : nabeatsuOfWorld(xs) aho :: Int -> Bool aho x = x `mod` 3 == 0 || find (show x) '3' ahan :: Int -> Bool ahan x = x `mod` 8 == 0 find :: String -> Char -> Bool find [] _ = False find (x:xs) y = if (x == y) then True else find(xs) y
Haskellのお勉強 その21
モジュールとは
Haskellで言うモジュールとは、いわゆる関数や型の定義をまとめであり、Javaで言うところのパッケージであり、.NET(C#,VB)で言うところの
名前空間(namespace)のことと考えて差し支えない。
■Haskellのモジュールに属するもの
・変数
・型コンストラクタ
・データコンストラクタ
・フィールドラベル
・型クラス
・クラスメソッド
上記6つをまとめて「エンティティ(entity)」と呼ぶ。
また、Haskellでは.NETと同じようにエンティティに対する名前空間はモジュールごとに分かれており、
別モジュールで同じ名前の関数を定義しても問題ないように設計されている。
基本的なモジュール
■Haskellの標準のモジュールモジュール名 | 内容 | |
Prelude | 言語の基本的な関数、型、型クラスなど | |
Ratio | 有理数 | |
Complex | 複素数 | |
Numeric | 数値 | |
Ix | 値と整数とのマッピング | |
Array | 配列 | |
List | リストに関連したユーティリティ | |
Maybe | Maybeモナド | |
Char | 文字に関連したユーティリティ | |
Monad | モナドに関連したユーティリティ | |
IO | 入出力 | |
Directory | ディレクトリ(フォルダ)の操作 | |
System | コマンドライン引数や環境変数など | |
Time | 日付と時刻 | |
Locale | ロケール | |
CPUTime | CPU時間の取得 | |
Random | ランダムな数値の取得 |
なお、すべてのモジュールは、暗黙のうちにPreludeモジュールをインポートされている。
なのでimport宣言を書かなくても、どこでもPreludeモジュールで定義された関数を使うことができる。
階層化ライブラリ
Haskell Hierarchical Librarieshttp://www.haskell.org/ghc/docs/latest/html/libraries/index.html
module宣言
新しいモジュールを定義するには、module宣言を使う。
たとえばFileUtilsというモジュールを定義するには、
module FileUtils where
と宣言してモジュールの定義を書くことができる。
モジュールの名前は、アルファベットの大文字で始まる識別子を、「.(ドット)」でつないだ名前となる。
たとえば「Char」「Data.List」「Text.Regex.Lazy」などという具合。
また、上記のようにmodule宣言を書くと、FileUtilsモジュールで定義したエンティティは全てエクスポートされる。
エクスポートされるとはつまり、他のモジュールにインポートして使うことができるということ。
エクスポートするエンティティの限定
特定のエンティティだけをエクスポートするには、次のように書く。
module FileUtils (makePath, forceRemove) where
このように書くと、makePathとforceRemoveだけをエクスポートすることができる。
このエクスポートするエンティティのリストのことを「エクスポートリスト」と言う。
ちなみに、二項演算子をエクスポートする場合は、カッコでくくる必要がある。
こんな具合
module PathUtils (joinPath, (+), concatPath) where
データコンストラクタをエクスポートリストに書きたいときは、
型コンストラクタと一緒に書く必要がある。
たとえばSomeTypeという型のデータコンストラクタConsAをエクスポートするには、
module AnyModule (SomeType(ConsA), a, b, c) where
また、複数のデータコンストラクタを指定するときは、
SomeType(ConsA, ConsB)のように書くこともできる。
またある型のすべてのデータコンストラクタとフィールドラベルを一括してエクスポートしたいときは、
SomeType(..)という省略表現で表すことができる。
モジュールのエクスポート
インポートしたモジュールのエンティティを、すべてまとめて再エクスポートするには、エクスポートリストに「module モジュール名」と書く。
mainモジュール
module宣言を省略すると、ファイルの先頭に次のmodule宣言が暗黙的に設定される。module Main (main) where
つまり、特に何もmodule宣言をしない場合、
「Mainモジュールの定義を書き、エクスポートリストにmainだけを記述した状態」ということになる。
mainの型は常に、アクション(IO a)でなければならない。
Haskellのお勉強 その20
class宣言
Haskellで新しい型クラスを宣言するには、class宣言を用いる。例えばEqクラスの宣言であれば、
class Eq a where (==) , (/==) :: a -> a -> Bool {- クラスメソッドの型宣言 -} x == y = not (x /= y) {- (==)クラスメソッドのデフォルト実装 -} x /= y = not (x == y) {- (/=)クラスメソッドのデフォルト実装-}
という感じ。
最初の「Eq a」は、宣言する型クラスの名前(Eq)と、型クラスの宣言に使う型変数(a)を宣言している。
このように書くことで、このclass宣言の中に現れる方変数 a は
すべてEqクラスのインスタンスという制約を課すことができる。
また、class宣言をする際、型クラスの名称は、1文字目がアルファベット大文字である必要があり、
型クラス名は型コンストラクタと同じ名前は使えないという制限がある。
2行目はクラスメソッド(==)と(/=)に型を宣言している。class宣言では型宣言を省略することはできない。
3行目以降は、それぞれのクラスメソッドのデフォルト実装。
インスタンス独自に実装しなかった場合、class宣言で指定したデフォルト実装が使われる。
この「Eq」の例では、クラスメソッド(==)の定義の中で(/=)が使われていたり、
反対にクラスメソッド(/=)の定義の中で(==)が使われているので、インスタンスでは必ず
(==)または(/=)のいずれかのクラスメソッドを定義しておかないと無限ループとなってしまうという点に着目。
継承を含むclass宣言
型クラスを継承する例としてOrdクラスの宣言を見てみる。class (Eq a) => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (>=) :: a -> a -> Bool (>) :: a -> a -> Bool (<=) :: a -> a -> Bool max :: a -> a -> a min :: a -> a -> a instance (Ord a) => Ord (Maybe a) instance (Ord a, Ord b) => (Either a b) instance Ord Integer instance Ord Float instance Ord Double instance (Ord a) => Ord [a] instance Ord () instance Ord Char instance Ord Bool instance Ord Ordering instance Ord Int {-ちなみに、この定義はGHCiインタプリタで「:info Ord」を実行すると参照できます-}
最初の「(Eq a) =>」で継承元のクラスを指定している。
このクラス宣言の中では、型変数aは「Ordクラスのインスタンス」であると同時に
「Eqクラスのインスタンス」でもある、という制約が課される。
Eqクラスの他にBoundedクラスもスーパークラスに指定したい様な場合は、
「(Eq a, Bounded a) =>」のように書けばよい。
compareからminまでをスーパークラスからそのまま使い、
残りをinstance宣言を使って、再実装しているってことですね。
instance宣言
ある型がある型クラスのインスタンスであることを宣言するには、instance宣言を使う。data Person = P String Int instance Eq Person where {- 「nameとageが一緒であれば等しい。」という実装。実際の人間の場合、あきらかに条件不足である -} (P name age) == (P name' age') = (name == name') && (age == age')
のように書くと、Person型がEqクラスのインスタンスであると宣言したことになる。
また、where節でPerson型のための(==)クラスメソッドを実装している。
deriving宣言
クラスメソッドの実装が単純に済むような場合、deriving宣言を使うとinstance宣言を自動生成してくれるらしい。data Person = P String Int deriving Eq
と宣言することで、「Person型はEqクラスのインスタンス」という宣言であると同時に、
「Eqクラスのクラスメソッドを自動生成せよ」という指示を同時に行ったこととなる。
deriving宣言は結構便利そうだが、いつでも使えるわけじゃないみたい。
Haskellのお勉強 その19
クラスメソッド
Eqクラスの制約を満たす型は、「(==)関数で同値検査ができる」あるいは、「(/=)関数で同値ではないという検査ができる」という制約を満たす。
また、Ordクラスは「(>)関数や(<)関数などの関数で比較できる」という制約を満たす。
これらの関数は、「そのクラスを特徴付ける関数」と考えることができる。
こうした「Eqクラスに対する(==)関数や(/=)関数」「Ordクラスに対する(>)関数や(<)関数」のような
「あるクラスを特徴付ける関数」のことを、「クラスメソッド」と言う。
多重定義
「ある型t」を「ある型クラスc」のインスタンスとして実装したい場合、「型t」は「型クラスc」のすべてのクラスメソッドが実装されている必要がある。
しかし、クラスメソッドは、型によって実装のしかたが異なりそうだ。
たとえば同じ「等しい」でも、「単なる値だけの型」と「構造を持った型」では
「等しい」を判断するための関数の実装方法が異なるだろう。
Haskellでは、型ごとにクラスメソッドの実装方法を変えることができる。
これを、「クラスメソッドの多重定義」と呼ぶ。
型クラスの継承
Ordクラスのクラスメソッドには、「(<=)関数」や「(>=)関数」のように、「等しさ」の判断も必要とされる関数も含まれている。
この2つのクラスメソッドを実装するにはEqクラスのクラスメソッド「(==)関数」もあったほうが良いだろう。
つまり「Ordクラスのインスタンスは、常にEqクラスのインスタンスでもある」という条件があると便利っぽい。
Haskellでは、このような条件を「型クラスの継承」として表現することができる(class宣言)。
型クラスを継承すると、継承元の型クラスのクラスメソッドがあることを前提にして
クラスメソッドを実装することができる。例えば、Ordクラスを定義する場合、
Eqクラスを継承していれば、Ordクラスのクラスメソッド(>=)関数や(<=)関数を実装するときに、
(==)関数を利用することができるようになる。
また、「OrdクラスがEqクラスを継承している」という関係のとき、
「EqクラスはOrdクラスのスーパークラスである」と表現し、
またその逆に、「OrdクラスはEqクラスのサブクラス」という表現をする。
これは普通のプログラマにとっては、お馴染みの表現ですな。
Haskellのお勉強 その17
type宣言
type宣言を使うと型に別名をつけることが出来る。例えば、type FilePath = String
と書くことで、String型の別名としてFilePath型が使えるようになる。
type宣言は型に別名をつけるだけで、型を作る方法が増えるわけではない。
つまり、新しいデータコンストラクタは導入されない。
また、type宣言でも型変数を使った宣言ができる。例えば、
type MyList a = [a]
と書くことで、[a]型の別名としてMyList型を宣言することができる。
newtype宣言
某機動戦士のアレを想起するが、アレのことではない。newtype宣言を使うと、既存の型をもとにして新しい型を宣言することができる。例えば、
newtype StackNT a = MkStackNT [a]
このように書くと、[a]型を元にStackNT a型を宣言し、
そのデータコンストラクタはMkStackNTである、と宣言していることになる。
newtype宣言がtype宣言と違うのは、型を作る方法が増えるという点。
つまり、新しいデータコンストラクタが導入されることである。
実は、newtype宣言は、同じことをdata宣言でも表現することができる。
data StackNT a = MkStackNT [a]
と表現することができる。ただし、newtype宣言はあくまで既存の型をもとに
新しい型を宣言するので、データコンストラクタのフィールドは1つに制限される。
そのため、複数フィールドがあるようなデータコンストラクタを持つdata宣言を
newtype宣言で書くことはできない。