ホームに戻る
C言語がわかる人のためのHaskell講座
0、はじめに
C言語を書ける人にわかりやすいように、
Haskellの説明を書いてみました。
工夫として、
「改訂新C言語入門 シニア編 著:林晴比古」
の説明順序と同じにして説明を書きました。
よって、C言語をどうHaskellに置き換えるか?
という点に重きが置かれており、
Haskell独自の記法の面白さがわからない、
かもしれません。
よって、最後に「19、追記」で補足しました。
1、Hackellの基本的な知識
Haskell は純粋関数型言語です。
手続き型では無いのでfor文やif文のような流れがありません。
関数型なので変数は存在せずすべてが関数です。
コンパイルは以下のようにする。
ghc sample.hs
コメントは次のように書く。
{- コメント -}
Hello!World! は次のようにする。
main = putStrLn "Hello,World!"
コードは以下のように () を省略できる。
main = print (2 * (3 + 4))
main = print $ (*) 1 $ 2 + 3
2行に渡るときには以下のように書ける。
このときインデントの位置は重要。
main = do putStrLn "abc"
putStrLn "def"
2、定数
8進数は頭に 0O と書くが、
1文字目はゼロで2文字目は英字のオー。
真偽値:True, False
8進数:0o37, 0O37
10進数:-123, 123
16進数:0xaf, 0xAF, 0xAF, 0XAF
浮動小数:1.1::Float, 1.1e3::Float
倍浮動小数:1.1, 1.1e3, 1.2::Double
文字定数:'a', 'A'
文字列定数:"abc", "ABC"
Int:-2147483648〜2147483647
Integer:制限なし
エスケープシーケンス
\t, \n, \r, \v, \f, \a, \b, \', \", \\
3、データ型
Bool:真偽値
Int:32ビット整数
Integer:整数
Float:浮動小数
Double:倍浮動小数
Char:文字型
String:文字列型。[Char]と同じ
型名の変換
type Name = String
4、配列と文字列
配列は次のように書く。0インデックス。
[0, 1, 2, 3, 4]
1番目の要素
[0, 1, 2, 3, 4] !! 1
要素数
length [0, 1, 2, 3, 4]
文字列
"abc" は ['a', 'b', 'c'] と同じ
ヌル文字 '\0' は存在しますが、
文字列の終端文字 '\0' の概念はありません。
5、型変換
fromIntegral:IntegralからNumに変換
realToFrac:RealからFractionalに変換
Integral(Int, Integer)
Num(Integer、Int、Float、Double)
Real(Int、Integer、Float、Double)
Fractional(Float,Double)
例えば Float型の 1.2 を Double型に変換するには、
((realToFrac (1.2::Float))::Double)
のようにする。
ちなみに Integral などは型クラスであり型では無い。
6、記憶クラス
スコープは関数単位になる。
7、初期化
Haskell は再代入は認められないが初期化はできる。
次のようにする。
a = 5
これは変数 a に 5 を初期化して入れてるように見えるが、
Haskell では単に 5 という数字を返す関数 a である。
8、演算子
Cとは / の使い方が異なる。
Cでいう / と % は `div` と `mod` を使う。
!= は使えず /= を使う。
+, -, *, / :四則演算 ただし / は浮動小数の計算になる。
`div`, `mod` :整数値について商と余り
==, <, >, <=, >=, /= :比較演算子(/= は「等しくない」)
&&, || :論理積、論理和
^, ^^ :累乗(^ は整数、^^ は浮動小数)
.&., .|., `xor`, complement :ビット演算子
`shiftL`, `shiftR`, `rotateL`, `rotateR` :ビットシフト
9、制御文
if 文は if 式で書くことができる。
if (x == 1) then True else False
for 文は再帰で書く。
(x はリストの先頭、 xs はそれ以降)
factorial :: [Int] -> Int
factorial [] = 1
factorial (x:xs) = x * factorial xs
main = print $ factorial [1..10]
上の再帰はスタックをたくさん使用します。
よって、次のような末尾再帰にすると良い。
(where によって f は factorial 内からしか使えない。)
factorial :: [Int] -> Int
factorial n = f n 1
where
f [] s = s
f (x:xs) s = f xs (x * s)
main = print $ factorial [1..10]
break の実装
factorial :: [Int] -> Int
factorial n = f n 1
where
f [] s = s
f (x:xs) s
| x == 5 = s
| otherwise = f xs (x * s)
main = print $ factorial [1..10]
continue の実装
factorial :: [Int] -> Int
factorial n = f n 1
where
f [] s = s
f (x:xs) s
| x == 5 = f xs s
| otherwise = f xs (x * s)
main = print $ factorial [1..10]
switch の実装
isTwo :: Int -> Bool
isTwo x = case x of
2 -> True
_ -> False
10、ポインタ
Haskell にポインタはありません。
11、関数
Int型の a と Int型の b を掛け算してInt型で返す関数は次のように書ける。
f::Int->Int->Int
f a b = a * b
12、構造体
data Dat = D{s::String, i::Int}
dat1::Dat
dat1 = D "abc" 123
使用する場合は i dat1 や s dat1 と書く。
13、ビットフィールド
.&., .|., `xor`, complement :ビット演算子
`shiftL`, `shiftR`, `rotateL`, `rotateR` :ビットシフト
ビット演算には「import Data.Bits」が必要
import Data.Bits
m, n :: Int
m = 7
n = 2
main = print $ (m .&. (complement n))
14、共用体
Haskell には共用体的なものが存在しますが、
実際には C の共用体とは異なるものです。
15、プリプロセッサ
Haskell では C スタイルのマクロが使える。
{-# OPTIONS_GHC -cpp #-}
#define NUM 5
main = print $ NUM
16、コンソール入出力
getChar, getLine, getContents:入力
putChar, putStr, putStrLn:出力
main = do name <- getLine
putStrLn name
17、入出力以外の標準関数
abs:絶対値
max:大きいほうの値
min:小さいほうの値
sin, cos, tan:三角関数
乱数
import System.Random
rand :: Int -> Int -> IO Int
rand x y = getStdRandom (randomR (x, y))
main = do n <- rand 1 6
print n
http://www.haskell.org/ghc/docs/latest/html/libraries/
18、ファイル処理関数
writeFile, appendFile, readFile:ファイル入出力
main = do text <- readFile "a.txt"
writeFile "b.txt" text
19、追記
データ型を作成できる。
data Type = A | B
タプルと呼ばれるデータ型が存在する。
(0, "abc")
型クラスが存在する。
class ClassA a where
fa :: a -> String
fa _ = "Error"
data A = A String
instance ClassA A where
fa(A s) = "String:" ++ s
main = print $ fa(A "abc")
リストの操作関数が充実している。
null []:空リストであれば True を返す
[1..]:正の整数のリスト(無限リストが可能)
0 : [1..5]:0 から 5 のリスト
[1..2]++[3..10]:1 から 10 のリスト
[x | x <- [1.. 5], x < 3 && x >= 5]:1, 2, 5 のリスト
length ['a'..'d']:リストの要素数を返す
head [0..6]:先頭の 0 を返す
tail [0..6]:[1..5] を返す
take 3 [1..5]:最初の3要素をリストで得る
import Data.List
reverse [1, 2, 3]:逆転
sort [3, 2, 1]:昇順ソート
map f [1, 2, 3]:すべてに関数 f を適応
any f [0, 1]:関数 f でひとつでも True であれば True
all f [0, 1]:関数 f ですべて True であれば True
main = print $ map (\ x -> x + 1) [1, 2]
main = print $ any (\ x -> x == 1) [1, 2]
ローカル関数を定義できる
ローカル関数 n1, n2 を定義している。
f x = let
n1 = x + 1
n2 = x + 2
in
n1 * n2
関数は定義を省くことができる。
main = print $ (\a b -> a * b) 2 3
遅延評価関数
次のような自作の if が書けます。
n == 3 であれば 1 になり そうでなければ 0 になる。
myIf :: Bool -> a -> a -> a
myIf True t f = t
myIf False t f = f
n = 2
main = print $ myIf (n == 3) 1 0
for 文は必ず末尾再帰を用いるわけではない。
再帰を使わずとも foldl, filter, takeWhile などで実装可能な場合がある。
foldl:左から演算
filter:条件を満たすものに限り
takeWhile:条件を満たす間は。満たさなくなったら打ち切り。
main = print $ foldl (*) 1
$ filter (/= 3)
$ takeWhile (< 5) [1..10]
モジュールを作成し import で使用できる。
モナドが使用できる。