Haskellの勉強 1日目

公開日:2015-01-03 更新日:2019-05-13

はじめる

関数型プログラミング言語は全くやったことがないので、
Haskell(ハスケル)を少しだけ勉強してみることにしました。

Haskell のインストール

いま使っている OS が Windows7 64bit なので、
https://www.haskell.org/platform/windows.html から、64bit版の方をダウンロードしました。
ファイルをダウンロードしたら、ファイルを実行してインストールする。
適当に次へ次へでインストールしたら、
C:/Program Files/Haskell Platform にインストールされました。

コンパイルと実行

Haskell で書かれたソースファイルを実行するには、コンパイルしてEXEファイルを作成する必要があります。
Haskellのインストールフォルダの bin 配下にある ghc.exe がコンパイラです。
具体的には、
C:/Program Files/Haskell Platform/2014.2.0.0/bin/ghc.exe
のような場所にあります。
これに引数としてソースファイルを渡すと、コンパイルしてくれます。

例えば、以下のファイルを作成して、
//test.hs
main = putStrLn "Hello"
コマンドプロンプトで、
"C:/Program Files/Haskell Platform/2014.2.0.0/bin/ghc.exe" test.hs
のように実行すると、test.exe が作成されます。
test.exe をコマンドプロンプト上で実行すると、
以下のような結果になります。
Hello

試行錯誤

以下はいろいろ試行錯誤した結果です。
まだはじめたばかりなので、誤解している点が結構あるかもしれませんのであしからず。
ソース、実行結果、説明の順で書いて行きます。

main関数

main = do
  putStrLn "Hello"
  putStrLn "Hello"
Hello
Hello
C言語で言うところの、
void main() {
  printf("Hello\n");
  printf("Hello\n");
}
と同じです。
関数を定義する時は、代入するようです。
また、関数の処理が複数行になる場合は、do を付けます。

数値を文字列に変換する

main = putStrLn(show(123) ++ "4")
1234
数値を文字列を変換するには、show を使います。
また、文字列結合には ++ を使います。

文字列を数値に変換する

main = putStrLn(show(read("123") + 1))
124
文字列を数値に変換するには、read を使います。
上記のソースでは、putStrLn が文字列を受け取るため、show で数値から文字列に戻しています。

計算1

main = do
  print (1 + 2)
  --print 1 + 2  -- エラー
3
3行目は、print 1 の結果と 2 を加算することを意味するためエラーとなる。

計算2

test a = a * 10

main = do
  print (test 1 + 2)
  print (test (1 + 2))
12
30
4行目は、test 1 の結果である 10 と 2 を足しているので 12 となる。
5行目は、test 3 が実行されるため、30 となる。

計算3

main = do
  print (1 + 2)
  print ((1::Int)       + (2::Int))
  print ((1::Integer)   + (2::Integer))
  print ((1.23::Float)  + (9.87::Float)) 
  print ((1.23::Double) + (9.87::Double))
  
  print (fromIntegral(2::Int) + (7::Int))
  print (fromIntegral(2::Int) + (7::Integer))
  print (fromIntegral(2::Integer) + (7::Int))
  print (fromIntegral(2::Integer) + (7::Integer))
  print (realToFrac(1.23::Float)  + (9.87::Double))
  
  --print ((1::Int)      + (2::Integer))            -- エラー
  --print ((1::Num)      + (2::Num))                -- エラー
  --print ((1::integral) + (9::integral))           -- エラー
  --print ((1.23::Fractional) + (9.87::Fractional)) -- エラー
3
3
3
11.1
11.1
9
9
9
9
11.100000019073486
リテラル値(数値)に型を明示的に付けることができる。
その場合、型が一致しないと計算できない。
型が一致しない場合は、fromIntegral や realToFrac を使用して、
Num や Fractional に型を変換すると計算できる。

関数合成

main = (putStrLn . show) 123
123
上記のソースは、putStrLn(show(123)) と同じ意味になります。

関数1

test = (putStrLn . show)

main = test 123
123
関数合成した関数を test として定義しています。

関数2

increment a = a + 1

main = (putStrLn . show . increment . increment . increment) 1
4
1を加算する関数 increment を定義しました。
最初の increment a は、関数名が increment、引数が1つあることを意味します。
= の右が処理内容になります。今回は1つ加算なので、a + 1 としています。

関数3

add a b = a + b

main = (putStrLn . show)(add 1 2)
3
渡された2つの引数を加算するだけの関数です。
最初の add a b は、関数名が add、引数が2つあることを意味します。
= の右が処理内容になります。今回は加算なので、a + b としています。

関数を使用する時は、add 1 2 のようにします。
add(1, 2) のようにして、括弧やカンマは付けない。

以下の書き方は全てエラーになります。
main = (putStrLn . show . add)(1 2) -- エラー
main = (putStrLn . show)add 1 2     -- エラー
main = (putStrLn . show)add(1, 2)   -- エラー
main = (putStrLn . show)add(1 2)    -- エラー
main = (putStrLn . show)(add(1 2))  -- エラー
main = (putStrLn . show)(add(1, 2)) -- エラー
1行目の関数合成を使った書き方は、問題なさそうなのですが、
引数が一致していないせいかエラーになる。

関数4

inc :: String -> Integer
inc a = (read a) + 1

add :: Integer -> String -> Integer
add a b = a + (read b)

main = do
  (putStrLn . show . inc) "1"
  (putStrLn . show)(add 1 "2")
2
3
型を明確に定義することもできる。
1行目は、Stringの引数を受け取り、Integerを返すことを意味します。
4行目は、1番目の引数に Integer、2番目の引数に Stringを受け取り、Integerを返すことを意味します。
この書き方、すごくわかりづらい。。。

関数5

:type fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b
Haskell の bin 配下の ghci.exe を実行するか、
Haskell のソースファイル(例:test.hs) をダブルクリックすると、
コマンドが入力できる画面が出ます。
ここで、:type fromIntegral と入力すると、定義がわかります。:t でも可。
また、:info、:i でも、定義が表示されます。

fromIntegral の定義は、Integral の引数を受け取り、Num を返すことがわかります。
=> の左側に、引数の型を定義して、
=> の右側に、引数と戻り値を定義する。

ちなみに、引数の型は、引数の数だけ定義する必要はない。

関数6

test a b c = show (read (show (a + b + c)) :: Int)
main = print (test 1 2 3)
"6"
関数の型を定義せずにコンパイルして、
ソースファイルをダブルクリックして ghci.exe を起動し、
:t type とすると、
test :: (Show a, Num a) => a -> a -> a -> String
と、自動的に定義された内容がわかる。

test と言う関数名で、
Num の引数を3つ取り、String を返す関数であることがわかる。

問題は、Show a。
Num と同じく a になっているので、必要なのかどうか。

関数の定義をソースファイルに追加して試行錯誤してみる。
test :: (Show a, Num a) => a -> a -> a -> String
test a b c = show (read (show (a + b + c)) :: Int)
main = print (test 1 2 3)
そのまま実行すると動くので、
試しに、Show a や Num a を削除してみる。
削除すると、エラーになるので、Show a は必要であることがわかる。
ただ、read も使っているけど、read は定義していない。

試しに、Read a を追加してみる。
test :: (Show a, Num a, Read a) => a -> a -> a -> String
test a b c = show (read (show (a + b + c)) :: Int)
main = print (test 1 2 3)
実行すると動く。
read は引数が文字列なので省略可能、
show は show の引数の型指定で必要、
Num は a + b + c の演算で必要、なのかな。

試しに + の演算を削除して、:type で定義を確認してみる。
test a = show (read (show (a)) :: Int)
main = print (test 1)
test :: Show a => a -> String
予想通り、Num a が消えた。

関数7

test 0 = 100
test 1 = 200
test x = x + 1

main = do
  print (test 0)
  print (test 1)
  print (test 2)
  print (test 3)
100
200
3
4
関数は条件毎に式を定義できる。
0 の場合は 100 を返し、
1 の場合は 200 を返し、
それ以外の場合は、引数の値に 1 を加算して返す。
x と言う変数名を使っているが、何を使っても問題ない。
また、上から評価しているので、x = x + 1 を先に定義すると、動作が変わる。
test xxx = xxx + 1
test 0 = 100
test 1 = 200

main = do
  print (test 0)
  print (test 1)
  print (test 2)
  print (test 3)
1
2
3
上記の場合、先に xxx で全てのケースの値を受け取ってしまうので、
その後の 0 の場合、1 の場合は、処理されない。

関数8

test a
  | a == 0    = 123
  | a  > 0    = a + 1
  | otherwise = -999

main = do
  print (test 0)
  print (test 1)
  print (test(-1))
##src|123 2 -999 ##/e## このように書くこともできる。

ちなみに、-1 を引数で渡す時には、括弧で囲わないとエラーになる。
test - 1 と分解されて、- を引数として認識するため?

ここまでの感想

コンパイルエラー出まくりで、頭が爆発しそう。。。。。。
特に Haskell の構文が難しい。
Wikipedia に初歩的なことが書いてあるので、
まずはそれを見た方が良さそうかな。

とりあえず現時点では、
Haskell でプログラムを書けるようになる自信が全くない。。。