お布団宇宙ねこ

にゃーん

『Effective Ruby』を読んだ

Effective Ruby

Effective Ruby

なぜ読んだか

Ruby は一応書けるけれど、実践的なコードを書くことになったときに発生する問題を解決するためのベストプラクティスや、より深いイディオムについてまだまだ足りない部分があると感じていて、Ruby の中級者向け以上の書籍を読みたかったので本書を読みました。

感想

各章の項目にその項目で言いたい結論が書かれているので、読み進める前の頭の整理にもなるし、何よりすべて読み終えた後で参照したくなったときに目次を見ればどの項目を読めばいいのか分かるのが良いと思いました。

例えば

第3章 コレクション

項目17 nilスカラーオブジェクトを配列に変換するには、 Array メソッドを使おう

のような。

普段 Ruby を書く上で意識していなかった継承階層の組み立て方や super の括弧有り無しでの挙動の違いから、構造化データの表現には Struct を使うのがよいとか reduce でコレクションの畳み込みを使おうなど、なるほどと思えるテクニックもあり面白く読み進めることができました。

ただ、メタプログラミングの章については、この本の性質上メタプログラミング全体について書いてあるわけではなかったので、理解が追いつかない部分がありいまいち自分の中で消化できていない感じがしました。 『メタプログラミング Ruby』 を読んでから出直したいと思います。

あと、この書籍とはあまり関係がないのですが、本書の「第1章 Ruby に身体を慣らす」で言及しているように、 Ruby では定数がミュータブルであることだったりオブジェクトが nil になる可能性を常に考慮しないといけないことは、 Haskell を学習した後では煩わしさをよく感じるようになりました(自分の中に Static おじさんの存在を感じる...)。そんなこともありつつ、特異な Ruby の思想を取り上げている第1章やテストの章はわかるわかる〜と思いながら読んでました。

まとめ

本書を読んだことで Ruby の様々なテクニックを知ることができて Ruby と少し仲良くなれた気がしました。 何か問題にぶち当たったときや、別の書籍を読み終わった後に改めて読み返したいと思います。

HaskellでAOJの問題を解いた(ITP1_6)

AOJ の Introduction to Programming を解いたときのメモです。

前回: HaskellでAOJの問題を解いた(ITP1_5)

今回解いた問題

6_A Reversing Numbers

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_A

import Control.Applicative

main = do
    getLine
    xs <- map (read :: String -> Int) . words <$> getLine
    putStrLn $ unwords $ show <$> reverse xs

入力として受け取った数列を逆順に出力するだけだったので、 reverse 関数を使って簡単に解けました。 [5,4,3,2,1] のようなリストを "5 4 3 2 1" のように出力するのに各要素に show を適用してから unwords で連結すると上手くいきます。

6_B Finding Missing Cards

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_B

import Control.Applicative
import qualified Control.Monad as Monad

type Card = (String, Int)

main = do
    n <- read <$> getLine
    hc <- map (toCard . words) <$> Monad.replicateM n getLine
    mapM_ (putStrLn . fromCard) $ missingCards hc

toCard :: [String] -> Card
toCard [p,r] = (p, read r)

fromCard :: Card -> String
fromCard (p,r) = p ++ " " ++ (show r)

missingCards :: [Card] -> [Card]
missingCards hc = [ (p,r) | p <- ["S","H","C","D"], r <- [1..13], not $ (p,r) `elem` hc ]

すべての要素から入力として与えられた要素を除いた要素のみを出力すればよい、と考えると、リスト内包表記を使うことでスマートに書けました。カードを表現する型がタプルのままだと分かりづらいと思い type を使って別の名前を付けています。

6_C Official House

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_C

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (forM_)
import qualified Control.Monad.Trans as Trans (lift)
import qualified Control.Monad.State as State (execStateT, modify)
import qualified Data.List.Split as Split (chunksOf)

type Room = (Int, Int, Int, Int)

main = do
    n <- getLine
    rs <- putRooms (read n)
    let rs' = Split.chunksOf limitRoom $ zipWith (++) (repeat " ") (show . getValue <$> rs)
    Monad.forM_ [1..(limitBuilding*limitFloor)] $ \bf -> do
        putStrLn $ concat $ rs' !! (bf-1)
        if ((bf `mod` limitFloor == 0) && bf /= (limitBuilding*limitFloor))
            then putStrLn "####################"
            else return ()

putRooms :: Int -> IO [Room]
putRooms n = (`State.execStateT` officialHouse) $ do
    Monad.forM_ [1..n] $ \_ -> do
        room <- Trans.lift $ toRoom . map (read :: String -> Int) . words <$> getLine
        State.modify (`updateRoom` room)

limitBuilding = 4
limitFloor    = 3
limitRoom     = 10

officialHouse :: [Room]
officialHouse = [ (b,f,r,0) | b <- [1..limitBuilding],
                              f <- [1..limitFloor],
                              r <- [1..limitRoom] ]

toRoom :: [Int] -> Room
toRoom [b,f,r,v] = (b,f,r,v)

getValue :: Room -> Int
getValue (_, _, _, v) = v

updateRoom :: [Room] -> Room -> [Room]
updateRoom [] _ = []
updateRoom (room1@(b1,f1,r1,v1):rs) room2@(b2,f2,r2,v2)
    | (b1,f1,r1) == (b2,f2,r2) = (b1,f1,r1,(v1 + v2)) : rs
    | otherwise                = room1 : updateRoom rs room2

officialHouse ですべての部屋の情報を作成し、入力として入居・退去の情報を受け取る度に State モナドを使って部屋情報を更新するようにしています。 putRooms 関数では State モナドと IO モナドを同時に扱う必要があったのでモナド変換子を使っています。 State モナドを使わなくても解けた気がしますが、勉強のために今回は使った感じです。

それとこの問題から、モジュールは使う関数のみをインポートするようにしました。

6_D Matrix Vector Multiplication

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_6_D

import Control.Applicative ((<$>))
import qualified Control.Monad as Monad (forM, replicateM)

type Vector = [Int]
type Row = [Int]
type Matrix = [Row]

main = do
    [n,m] <- getLine'
    a <- Monad.replicateM n getLine'
    b <- toVector <$> Monad.replicateM m getLine'
    mapM_ print $ vectorAndMatrixProduct a b

getLine' :: IO [Int]
getLine' = map (read :: String -> Int) . words <$> getLine

toVector :: [[Int]] -> Vector
toVector = concat

vectorAndMatrixProduct :: Matrix -> Vector -> Vector
vectorAndMatrixProduct xs ys = toVector $ do
    Monad.forM [0..(length xs - 1)] $ \i -> dotProduct (xs !! i) ys

dotProduct :: Row -> Vector -> Row
dotProduct xs ys = return $ sum $ zipWith (*) xs ys

行列( Matrix )は二重のリストとして、ベクトル( Vector )はリストとして表現しました。ベクトルと行列の積は、 forM を使って行列の二重リストから行番号ごとにリストを取得し、ベクトルのリストと sum $ zipWith (*) で計算するようにしています。

この問題の応用である 7_D Matrix Multiplication では、行列の転置を使うことでもう少しスマートに書くことができました。

参考文献

github.com

HaskellでAOJの問題を解いた(ITP1_5)

AOJ の Introduction to Programming を解いたときのメモです。

前回: HaskellでAOJの問題を解いた(ITP1_3~4) - お布団宇宙ねこ

今回解いた問題

5_A: Print a Rectangle

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_A

import Control.Applicative
import qualified Control.Monad as Monad

main = loop

loop :: IO ()
loop = do
    [h,w] <- map (read :: String -> Int) . words <$> getLine
    Monad.when (h /= 0 || w /= 0) $ do
        printRectangle h w
        loop

printRectangle :: Int -> Int -> IO ()
printRectangle h w = do
    Monad.forM_ [1..h] $ \_ -> do
        putStrLn $ concat . take w $ repeat "#"
    putStrLn ""

ある文字列を決められた長さだけ出力するには、 take と repeat を使って無限長のリストから任意の数の要素を取得する方法で上手くできました。と書いている途中で replicate でも同じことができたことに気がつきました...。replicate を使って書くなら replicate w "#" ですね。

5_B: Print a Frame

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_B

import Control.Applicative
import qualified Control.Monad as Monad

main = loop

loop :: IO ()
loop = do
    [h,w] <- map (read :: String -> Int) . words <$> getLine
    Monad.when (h /= 0 || w /= 0) $ do
        printFrame h w
        loop

printFrame :: Int -> Int -> IO ()
printFrame h w = do
    Monad.forM_ [1..h] $ \i -> do
        putStrLn $ frameOrDot h w i
    putStrLn ""

frameOrDot :: Int -> Int -> Int -> String
frameOrDot h w i = if (i == 1 || i == h)
                     then concat . take w $ repeat "#"
                     else "#" ++ (concat . take (w-2) $ repeat ".") ++ "#"

A の問題ではただ # を出力するだけでよかったのですが、こちらは長方形の中身を . にする必要があります。 frameOrDot という関数で、受け取った引数の行番号 i が最初か最後なら # のみを連結した文字列を返し、そうでないなら . の両端に # を連結した文字列を返すようにしました。こちらも replicate を使えばすっきりしそうです。

5_C: Print a Chessboard

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_C

import Control.Applicative
import qualified Control.Monad as Monad

main = loop

loop :: IO ()
loop = do
    [h,w] <- map (read :: String -> Int) . words <$> getLine
    Monad.when (h /= 0 || w /= 0) $ do
        printChessboard h w
        loop

printChessboard :: Int -> Int -> IO ()
printChessboard h w = do
    Monad.forM_ [1..h] $ \i -> do
        putStrLn $ squareLine w i
    putStrLn ""

squareLine :: Int -> Int -> String
squareLine w i = if (odd i) then
                   takeSquare w "#" "."
                 else
                   takeSquare w "." "#"

takeSquare :: Int -> String -> String -> String
takeSquare w s1 s2 = take w $ concat $ zipWith (++) (repeat s1) (repeat s2)

A, B をさらに発展させた問題で、今度は #..# が交互に組み合わせた長方形を出力する問題です。 takeSquare で文字列を生成する部分は replicate を使っても書けそうですが、 take 3 $ concat $ replicate 3 "#." のようなコードだと replicate で余分な文字列を生成してしまうので厳しそうです。 take で余分な文字列は捨ててしまうので問題はないのですが、無駄がある感じがします。

5_D: Structured Programming

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_5_D

import Control.Applicative

main = do
    n <- read <$> getLine
    putStrLn $
        concat $ zipWith (++) (repeat " ") (show <$> filterCheckNumber n)

filterCheckNumber :: Int -> [Int]
filterCheckNumber n = [ x | x <- [1..n], (x `mod` 3 == 0) || includeThree x ]

includeThree :: Int -> Bool
includeThree x
    | x == 0          = False
    | x `mod` 10 == 3 = True
    | otherwise       = includeThree $ x `div` 10

C++ で書かれたサンプルプログラムと同じプログラムを実装するという問題でした。プログラムの処理内容は、整数 1 から与えられた整数 n までの間で、ある条件にマッチする数だけを出力するというものです。ある条件というのは3の倍数か includeThree に書かれているガードの条件です。サンプルプログラムには goto 文が多用されており大変読みにくかったですね...。

github.com

HaskellでAOJの問題を解いた(ITP1_3~4)

AOJ の Introduction to Programming を解いたときのメモです。

github.com

今回解いた問題

3_A: Print Many Hello World

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_A

こちら に書きました。

3_B: Print Test Cases

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_B

import qualified Control.Monad as Monad

main = loopPutCase 1

loopPutCase :: Int -> IO ()
loopPutCase i = do
    x <- getLine
    Monad.when (read x /= 0) $ do
        putStrLn $ "Case " ++ show i ++ ": " ++ x
        loopPutCase $ i + 1

複数の入力データが与えられる問題が初登場しました。

はじめは Writer や State モナドを使ってカウントの状態を管理しないといけないのでは?と考えて色々試行錯誤をしてみましたが、最終的に引数にカウントを取る関数を再帰させる形に落ち着きました。

3_C: Swapping Two Numbers

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_C

import Control.Applicative
import qualified Control.Monad as Monad

main = loopPutNumbers

loopPutNumbers :: IO ()
loopPutNumbers = do
    [x,y] <- map (read :: String -> Int) . words <$> getLine
    Monad.when (x /= 0 || y /= 0) $ do
        putStrLn $ if (x < y)
                    then unwords $ show <$> [x,y]
                    else unwords $ show <$> [y,x]
        loopPutNumbers

リストもモナドなので <$> が適用できます。とは言っても getLine で受け取っている IO () も中身は String で同じリストモナドなので今更なのですが。
今回は show <$> [x,y] と書きましたが、IO () に普通の関数を適用したい場合は <$> を使えばよさそうですが、リストの場合は map show とも置き換えられるので好みの問題かもしれません。

3_D: How Many Divisors?

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_D

import Control.Applicative

main = do
    [a,b,c] <- map (read :: String -> Int) . words <$> getLine
    print $ length $ findDivisors a b c

findDivisors :: Int -> Int -> Int -> [Int]
findDivisors a b c = [n | n <- [a..b], c `mod` n == 0]

問題文から即座に集合の問題だと判断し、リスト内包表記で解いてみました。このような数学らしい問題を解くのに便利な構文があるのも Haskell の強みですね。

4_A: A / B Problem

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_A

import Control.Applicative
import Data.Fixed

main = do
    [a,b] <- map (read :: String -> Int) . words <$> getLine
    putStrLn $
        show (a `div` b) ++ " " ++
        show (a `mod` b) ++ " " ++
        show ((realToFrac a) / (realToFrac b) :: Fixed E9)

簡単な除算と剰余演算かと思っていましたが、浮動小数点数の扱いだけは少し悩みました。

/ 演算子を使えば除算はできますが、それだけでは期待する結果になりません。例えば、 3 / 2 には 1.50000 という結果が期待されるのですが、実際にはこうならず 1.5 が返ってきます。

この問題を解決するために Data.Fixed というモジュールを使います。このモジュールが提供している型コンストラクタ Fixed を使うことで任意の精度の小数を扱えるようになります。今回は問題文より0.00001以下の誤差が認められているので桁数としては E9 で十分そうです( 10^-9 = .000000001 )。 Fixed 型コンストラクタを使うためには Fractional 型クラスのインスタンスである必要があるので Int を realToFrac で変換しています。

https://hackage.haskell.org/package/base-4.9.0.0/docs/src/Data.Fixed.html#line-199

4_B: Circle

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_B

import Control.Applicative
import Data.Fixed

main = do
    r <- (read :: String -> Double) <$> getLine
    putStrLn $ unwords $ show . toFixedE9 <$> [(pi*r^2), (2*pi*r)]

toFixedE9 :: Double -> Fixed E9
toFixedE9 r = realToFrac r :: Fixed E9

Aと同じく浮動小数点数を扱う必要があるので Data.Fixed を使っています。変換処理の記述が長くなって読みづらくなりそうだったので関数に切り出しました。

4_C: Simple Calculator

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_C

import Control.Applicative
import qualified Control.Monad as Monad

main = calculator

calculator :: IO ()
calculator = do
    [a, op, b] <- words <$> getLine
    Monad.when (op /= "?") $ do
        print $ calculate (read a) op (read b)
        calculator

calculate :: Int -> String -> Int -> Int
calculate a op b = interpret op
    where interpret "+" = a + b
          interpret "-" = a - b
          interpret "*" = a * b
          interpret "/" = a `div` b

引数で与えられた演算子の文字列をパターンマッチで評価している部分は、

calculate a "+" b = a + b
calculate a "-" b = a - b
...  

と書いてもよかったのですが、 where キーワードで一時関数を作ってそこでパターンマッチさせるようにしました。個人的にパターンマッチさせたい値が1つだけの場合や、パターンマッチによる分岐が多い場合は、 where キーワードを使った方が見やすい気がします。

4_D: Min, Max and Sum

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_4_D

import Control.Applicative
import qualified Control.Monad as Monad

main = do
    getLine
    xs <- map (read :: String -> Int) . words <$> getLine
    putStrLn $ unwords $ show <$> Monad.sequence [minimum, maximum, sum] xs

同じ値に対して複数の関数を適用したいときは sequence が便利です。似たような関数に sequenceA というのがありますが、モナドを取るかアプリカティブファンクターを取るかの違いしかなく、今回はリストを取るのでどちらでもよさそうです。

参考文献

Haskellで空白区切りの入力を受け取る

ku00.hatenablog.com

前回、AOJ の問題をいくつか解いたときに空白区切りの入力の扱いに悩んでいましたが、前回より良い方法が見つかったのでメモしておきます。

<$> を使おう

入力として2つの整数が空白区切りで渡されるとき、それぞれの整数を a, b という変数で取りたいとします。すると、次のようなコードになります。

(例: "123 456")

Before

l <- getLine
let (a:b:[]) = map read $ words l :: [Int]

After

import Control.Applicative

[a, b] <- map read . words <$> getLine

まず <- の右側から見ていきましょう。

Before では、 getLine したものを一度 l に束縛し、それから words , map read の順で関数を適用することで [Int] 型の値を取ることができました。

それを After では、 <$> を使うことで、一時的な変数に束縛することなく words , map read を適用できます。

一方 <- の左側ですが、これは事前に与えられる数が分かっているので素直に2つの要素を持つリストに束縛しました。 (x:xs) のような文法はパターンマッチで使う方が一般的でしょう(むしろ変数に束縛するのに有用なケースってあるのだろうか)。

<$> とは

<$>Control.ApplicativeData.Functor にある関数で、アプリカティブ値やファンクター値に対して普通の関数を適用する関数です。

*Main Lib> show <$> Just 1
Just "1"
*Main Lib> (+10) <$> [1..5]
[11,12,13,14,15]

では、 IO () に対して適用できるのはなぜかというと、 IO ()モナドであり、またアプリカティブ・ファンクターでもあり、ファンクターでもあるからです。これは GHCi の info オプションでも確認できます。アプリカティブファンクターがモナドスーパークラスになっているので当たり前なのですが...すっかり抜けていました。

*Main Lib> :i IO ()
instance Monad IO -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Monoid () -- Defined in ‘GHC.Base’

そして、今回のコード map read . words <$> getLine では何が起きているのかというと、

  1. getLine が入力を受け取り IO String を返す
  2. <$> によって IO String から String を取り出し words に適用する( ["123", "456"] )
  3. その後 map read を適用する( [123, 456] )

という流れになっています。

型推論ができない場合

<$> を使うと IO () の中身に対して普通の関数を適用できるようになるのですが、その後の map read で型推論ができない場合があります。その場合は、以下のように型注釈を使って明示的に型を教えてあげる必要があります。

[a, b, c] <- map (read :: String -> Int) . words <$> getLine

まとめ

モナドに埋もれがちですが、アプリカティブファンクターやファンクターも重要であることを再認識したのでしっかり復習しようと思いました。また、 read に型注釈を付けるときにはいつも read x :: Int みたいな使い方しかしてなかったので、関数全体の型注釈ができるのは知りませんでした。

参考文献

Haskellに慣れるためにAOJの問題を解いてみた

すごいHaskellたのしく学ぼう!関数プログラミング実践入門 を読んだだけでは経験値が全く足りないので、AOJ (Aizu Online Judge) を使って Haskell を書く練習をすることにしました。

問題セットは色々ありますが、とりあえず Introduction to Programming から始めていくことにしました。解いていく中で思ったことなどを残しておきます。

解いた問題は GitHub にもあげてあります。

github.com

今回解いた問題

  • ITP1_1_A~D
  • ITP1_2_A~D
  • ITP1_3_A

X Cubic

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_B

main = do
    x <- getLine
    print $ powerThree $ read x

powerThree :: Int -> Int
powerThree = (^3)

getLine を束縛すると文字列が取れるので、 read で変換してから3乗する関数に渡しました。 それと Int 型の結果は putStrLn ではなく print で出力できるということも忘れていました...(エラーになって気がついた)。 GHCi も出力には print を使っていましたね。

Rectangle

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_C

main = do
    l <- getLine
    let (a:b:[]) = map read $ words l :: [Int]
        x = area a b
        y = circumference a b
    putStrLn $ show x ++ " " ++ show y

area :: Int -> Int -> Int
area a b = a * b

circumference :: Int -> Int -> Int
circumference a b = (a + b) * 2

入力で受け取る空白区切りの文字列をどうやって分割しようかとても悩みました。文字列を分割する関数が Data.Text v:splitOn にありましたが、なるべく他のパッケージを使わないやり方を探してみたかったのです。それと (a:_:b) <- getLine みたいな束縛のやり方もありますが、 Char を Int に変換するのが面倒だったので止めました。

Watch

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_1_D

main = do
    s <- getLine
    putStrLn $ toTime (read s :: Int) 2

toTime :: Int -> Int -> String
toTime s 0 = show s
toTime s n = show divSecond ++ ":" ++ toTime modSecond (n-1)
    where divSecond  = s `div` (60 ^ n)
          modSecond  = s `mod` (60 ^ n)

問題をみたときにこれは再帰関数で実装できそうと思ったので頑張りました。変換処理のための関数を書いていると Int や String を行ったり来たりするのつらいなあと感じました。

Small, Large, or Equal

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_2_A

main = do
    l <- getLine
    let (a:b:[]) = map read $ words l :: [Int]
    putStrLn $ a `compare'` b

compare' :: Int -> Int -> String
compare' a b
    | ordering == LT = "a < b"
    | ordering == EQ = "a == b"
    | ordering == GT = "a > b"
    where ordering = a `compare` b

Ordering の LT, EQ, GT の意味をすぐに忘れてしまうのでメモ。

  • A is Less Than B
  • A is EQual to B
  • A is Greter Than B

文字列でも比較できるからといって文字列比較にしてみたら、 -20 -10 のような入力が来たときに期待する結果にならないことが分かり、入力として整数が与えられるという問題の主旨は履き違えてはいけないなと思いました(当たり前ですが)。

Sorting Three Numbers

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_2_C

import qualified Data.List as List

main = do
    l <- getLine
    let xs = map read $ words l :: [Int]
        sxs = map show $ List.sort xs
    putStrLn $ unwords sxs

ソート問題ということでしたが速度は気にしてなかったので無難に Data.List v:sort を使いました。

パッケージをインポートする際に関数名の衝突を防ぐために qualified というものがあるのですが、個人的には関数名の衝突に限らず付けておいたほうが分かりやすいように思いました。付けないとどのパッケージの関数か分からないので。

Print Many Hello World

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_3_A

import qualified Data.Function as Function
import qualified Control.Monad as Monad

main = do
    flip Function.fix 0 $ \loop i ->
        Monad.when (i < 1000) $ do
            putStrLn "Hello World"
            loop $ i + 1

すごいH本だとループに関する記述は when , forever 関数の説明くらいだった気がするので、他にどんなものがあるのか調べてみました。すると fix という関数を見つかり、この関数を使うと再帰関数を使うことなく再帰を実現することができます。これは fix 関数が、関数の不動点( f a == a を満たすような値 a のこと)を導くようになっているためです。この辺りの数学の話になってくると半分くらいしか理解できなくなってきます...。

まとめ

いざ問題を解いてみるとなると簡単な問題でも意外と手は動かないもので、まず入力を関数が処理できるような型に変換するにはどうすればいいんだっけといったところから始まり、問題の核となる処理は関数に切り出したほうがいいのか否か、関数の引数の型は入力の型に合わせて String の方がいいのかなど、色々悩みながら書いていました。それでも、解けてACをもらえたときは嬉しいもので、競技プログラミングの問題をやっていて久しぶりに楽しいなと思えました。

引き続き、AOJ の問題を解いていって少しでも Haskell に慣れていきたいです。

参考文献

『コンピューターで「脳」がつくれるか』を読んだ

gihyo.jp

発売前から大人気だった kazoo04 さんの『コンピューターで「脳」がつくれるか』を読みました。

元々人口知能には興味があったのですが、書店で目にする人口知能を扱った書籍は自分にとっては難しいものばかりで、 集合知プログラミング に手を出してみるもすぐに挫折してしまいました。読んでいてなるほど面白いなと思えたのは 『マッチ箱の脳(AI) - 使える人口知能のお話』 くらいでした。それが2013年の話。

それから時は経ち、マッチ箱の脳みたいな読みやすい書籍はないかなと思っていたところ、かずー氏さんが人口知能に関する本を出版するというツイートを見かけて、即買いしました。

どんな本?

人口知能の本です。ざっくり以下のような内容でした。

  • 人口知能と一口に言っても、実は特化型AIと汎用AIという分類ができ、それぞれどう違うのか
  • そもそも知能を支える脳って一体どうゆう仕組みなのか
  • 特化型AIはどのようにして実現しているのか、機械学習をもとにしたアルゴリズムの解説
  • 脳の仕組みや特化型AIのアルゴリズムを交えながら、汎用AIはどうすれば実現できそうなのか
  • 汎用AIが実現したときに何が起こるのかなど、未来の話について

著者のかずー氏さん自身も本書のFAQをまとめているので、興味を持った方はそちらを一読するのがよいかと思います。

kazoo04.hatenablog.com

感想

人口知能の本なので機械学習などのアルゴリズムが分かりやすく解説されているのかなと思っていたのですが、まさか人間の脳の働きについても解説してるとは思いませんでした。けれど、汎用AIを実現する上で人間の脳の仕組みを解き明かすことは重要であることが分かりましたし、未だ解明されていない部分の多い人間の脳は改めてハイテクなんだなあと感じられる内容でした。

さらに、以下の点が本書を読みやすくかつ面白くしているのだと思いました。

  • 数式が1つも出てこない
  • 未来の話をしてくれる

数式が1つも出てこない

人口知能を扱った書籍はだいたい大学数学レベル以上の数式が載っており(個人的所感です)、パラパラと読むだけでも「うっ頭が...」となってしまうのですが、本書は自分のような方でもすんなりと読むことができます。読む上で、数学と向き合う心構えが必要ないというのはとても安心させられました。

未来の話をしてくれる

実際に汎用AIが実現すると世界がどう変わるのかという疑問にも答えてくれています。SFでいうAIによる人類支配の可能性の話から、雇用問題のような人間との付き合い方の話など、汎用AIと人間の未来についての話で締めてくれるのは個人的にとてもワクワクさせられました。

まとめ

本書でもはじめに触れている通り、人口知能入門の最初の一冊に最適な書籍でした。かずー氏さん本当にすごい...。

自分が冒頭で触れた 『マッチ箱の脳 - 使える人口知能のお話』 は、特化型AIを実現しているアルゴリズム(遺伝的アルゴリズムニュートラルネットワークなど)についてイラストを入れながら丁寧に説明している書籍なので、本書を読んで特化型AIに興味が出てきた人はこちらもおすすめです。

けれど、汎用AIについてさらに詳しく知りたくなった方は次に何を読めばいいんだろう...。教えてかずー氏さん!

その後

かずー氏さんがリプライで『考える脳 考えるコンピューター』という書籍を教えてくださったので、次はこれを読もうと思います。ありがとうございました!

考える脳 考えるコンピューター

考える脳 考えるコンピューター