むかーし週報に書いた内容を、同僚が覚えていてくれて、嬉しかったんだけど自分はほぼ忘れかけていた。
まあ適当にでも情報を発信していれば、それに反応してくれる人が出てきて楽しいし、自分の考えも整理できて良いことだなと思った。

朝香宮邸の亡霊

www.teien-art-museum.ne.jp

朝香宮邸。
f:id:kawauso7c:20161005204135j:plain
f:id:kawauso7c:20161005204204j:plain

作品が雰囲気ある邸宅内に展開されていて最高でした。
最近はインスタレーションアートに興味があります。自分の感覚をハックされている気分になります。

mainでputStrをmapする方法

前回分からなかった件、簡単に書くとこんな感じ。

main = mapM putStrLn ["hoge", "piyo", "fuga"]

mapでも良さそうな気がしてしまうが駄目。mapMでもmapM_でも問題なく動くようなのだが。理由を自分なりにまとめた。

putStrLn

Prelude> :t putStrLn
putStrLn :: String -> IO ()

putStrLnは「文字列を引数に取り、()(空のタプル)を結果とするI/Oアクションを返す」。

アクションとは

Haskellの関数は全て参照透過性(同じ引数に対して常に同じ値を返す)がある。アクションは参照透過性がないものを扱う。例えば乱数を返すアクション、コンソールからの入力を返すアクション等。

I/Oアクションとは

実行されると、入力を読んだり画面やファイルに何かを書き出したりする動作をして、結果を返すアクション。

putStrLnが「()を結果とするI/Oアクション」を返すのは、文字列を端末に表示するアクションには意味のある返り値がないので、ダミーの値として()を使うため。*1

main変数

Mainモジュールに関係がある。

モジュールとは

エンティティ(変数、型コンストラクタ、データコンストラクタ、フィールドラベル、型クラス、クラスメソッド)をパッケージ化したもので、モジュールごとに名前空間が分かれている。

importで別モジュールをインポートできる。

import System.Random

モジュールを定義するには以下のように書く。

module MyFileUtils (tmpPath) where

MyFileUtilsモジュールを定義し、tmpPathエンティティを外部にエクスポートする宣言となる。

ファイル内でmodule宣言を省略すると、以下のmodules宣言が勝手に追加される。

module Main (main) where

このmainがプログラムエントリーポイントとなり実行される。 mainはI/Oアクションが束縛された変数でなければならない。

map、mapM

ここまで、main :: IO aputStrLn :: String -> IO ()となることを説明した。つまり

main = putStrLn "hoge"

これは型が合致し問題なく動くということだ。では間にmapを挟むにはどうしたらいいか。
sequence関数というのがある。I/Oアクションのリストを引数にし、それらを順に実行するI/Oアクション(シーケンス)を返す。これをつかって以下のように書ける。

main = sequence $ map putStrLn ["hoge", "piyo", "fuga"]

mapが[putStrLn "hoge", putStrLn "piyo", putStrLn "fuga"]を返すのでsequnceがI/Oアクションにまとめる訳だ。
mapM関数はシーケンスにまとめる処理を内部で行ってくれる。

main = mapM putStrLn ["hoge", "piyo", "fuga"]

結論として、mapMは、リストに対してI/Oアクションを返す関数をマップし、単一のI/Oアクションにまとめてくれるから、mainに束縛できるということだ。
最後に残ったmapM_、動作はmapMとほぼ同じで、違いはmapM_はI/Oアクションの結果を捨てる。

ちなみにsequenceとmapMのシグネチャは相変わらず読めない。

Prelude> :t sequence
sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
Prelude> :t mapM
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

参考にした。
http://jutememo.blogspot.jp/2010/03/haskell-mapm-foldr.html
すごいHaskellたのしく学ぼう!

*1:voidなんて無いんだね、と思ったらある?

Haskellで作る奇妙なプログラミング言語 Haskell練習問題②

これの続き。

問3 数を1ずつカウントしていく何かを作ってください。

inc :: Int -> Int
inc c = c + 1

main = do
  print $ inc $ inc $ inc $ inc 0

もともとの問題は、incメソッドを呼び出されるたび数をインクリメントしていくクラスを作れ、なので、副作用を扱わないといけないが、分からんのでお茶を濁す。

問4 自分自身のソースコードを出力するHaskellプログラムを書いてください。

main = do
  str <- readFile "problem4.hs"
  putStr str

こちらは簡単。

$ cat problem4.hs
-- 自分自身のソースコードを出力するHaskellプログラムを書いてください。

main = do
  str <- readFile "problem4.hs"
  putStr str
$ runhaskell problem4.hs
-- 自分自身のソースコードを出力するHaskellプログラムを書いてください。

main = do
  str <- readFile "problem4.hs"
  putStr str

ソースコードは以下。
https://github.com/kawausokun/esoteric-language-in-haskell

Hakellの型シグネチャを読む練習

ひたすら読む。読めねばならぬ。ghciを起動し:tだ。

Prelude> :t 'h'
'h' :: Char

'h'は「Char型」。

Prelude> :t "hoge"
"hoge" :: [Char]

"hoge"は「Char型のリスト」である。

Prelude> :t (True, 'a')
(True, 'a') :: (Bool, Char)

(True, 'a')は「タプル(Bool, Char)型」である。

Prelude> :t 4 == 5
4 == 5 :: Bool

4 == 5は「Bool型」である。

Prelude> import Data.Char
Prelude Data.Char> :t toLower
toLower :: Char -> Char

toLowerは「Charを引数としCharを返す関数」である。

Prelude> :t map
map :: (a -> b) -> [a] -> [b]

a,bは型変数であり、どんな型も取り得る。シグネチャ内で一つの型変数は常に同じ型を取る。
mapは「aを受けbを返す関数と[a]型を引数とし、[b]型を返す関数」である。
(この()がタプルに見えるんだよな。)

Prelude> import Data.List
Prelude Data.List> :t sort
sort :: Ord a => [a] -> [a]

sortは「[a]型を引数とし[a]型を返す関数、ただしaは型クラスOrdのインスタンス」である。
ある型が型クラスの制約を満たすとき、その型は型クラスのインスタンスである、と言う。
Ordクラスの定義を覗くと

Prelude> :i 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

compare,(<),(<=)...関数を実装しろという制約。Eqクラスを継承しているのでそちらの制約もある。

mapMの型シグネチャが読めなかったので今はここまで。

Haskellで作る奇妙なプログラミング言語 Haskell練習問題編

Rubyで作る奇妙なプログラミング言語Haskellで追った。

最初の章は言語の練習。もう勉強したことを忘れてきているのでちょうどいい。

1-1 Haskell練習問題

問1 画面に「Hello, world!」という文字列を出力するHaskellプログラムを書いてください。

main = do putStrLn "Hello, world!"

問2 「99 bottles of beer」の全文を出力するHaskellプログラムを書いてください。

verse :: Integer -> String
verse 0 = "No More bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottoles of beer on the wall."
verse n = bottles n ++ " of beer on the wall, " ++ bottles n ++ " of beer.\nTake one down and pass it around, " ++ bottles (n-1) ++ " of beer on the wall."
  where bottles 0 = "no more bottles"
        bottles 1 = "1 bottle"
        bottles n = show n ++ " bottles"

main = mapM putStrLn $ map verse [99,98..0]

mapMのところが何故こう書くのか分からない。後でIOとモナドを調べなおす。
上級者の回答。
http://www.99-bottles-of-beer.net/language-haskell-1070.html
Alternative Versionsが全く読めない。

ソースコードは以下。
https://github.com/kawausokun/esoteric-language-in-haskell

Haskell 勉強記 (1週間分)

1週間の休みをとったので、Haskellを書いてみたくなった。

6/13(月) 初日

すごいHaskellたのしく学ぼう!」を読み始めた。
今日は全体を見通した。Kindle版で見る場合、全体の80%を読めばよいようだ。残りはほぼ索引。

  • モナドとモノイドの違い (というか名前が似ているだけ?)
  • 副作用を持つ関数を扱うために、純粋な部分と汚い仕事をする不純な部分をどう分離しているのか
  • 第10章:関数型問題解決法は具体的な問題を解くようなので面白そうだ
  • 噂に聞くモナドの章はほとんど最後だな...
  • 型クラスって、オブジェクト指向型言語のクラスとは関係あるのかな

1日13%の進捗が無いと終わらない計算
(残り6日) To be continued...

6/14(火)

パターンマッチ、as、ガード、where、let、in、case

第3章まで読んだ。12%。最後まで読み切るのではなく、何か動くものを作るを目標にしたほうがいいかな。
次は再帰から。
(残り5日) To be continued...

6/15(水)

ふつうのHaskellプログラミング」を読み始めた。
簡単なLinuxコマンドを実装していくので、動くものができて嬉しい。すごいHと並行して読むと楽になりそうだ。

4.4 練習問題の解答がコンパイルできなかった。

import List

main = do cs <- getContents
          putStr $ unlines $ map sort $ lines cs
% ghc sort.hs -o sort
[1 of 1] Compiling Main             ( sort.hs, sort.o )

sort.hs:1:1: error:
    Failed to load interface for ‘List’
    Use -v to see a list of the files searched for.

この本はHaskell 98というバージョンで書かれている。対してインストールしたのはHaskell 2010。モジュールの構成が違うようだ。

import Data.List

main = do cs <- getContents
          putStr $ unlines $ map sort $ lines cs

新しいバージョンに合わせて読み替えが必要な箇所がある。
ふつkell第4章まで読んだ。第11章までは読みたい。

(残り4日) To be continued...

6/16(木)

ふつkell第8章まで読んだ。
(残り3日) To be continued...

6/17(金)

一日中Hunter2 観ていた。

(残り2日) To be continued...

6/18(土)

一日中Hunter2 観ていた。

(残り1日) To be continued...

6/19(日) 最終日

ふつkell第11章まで読んだ。一応構文はさらった程度で、まだ実用的なプログラムを書けるとはとても言えない。
以降はすごいHを読みこなしつつ、まずはProject Eulerでもやってみる。解答もあるし