Skip to content

Instantly share code, notes, and snippets.

@matsubara0507
Last active December 8, 2015 02:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matsubara0507/e21b08e2fe94038c9364 to your computer and use it in GitHub Desktop.
Save matsubara0507/e21b08e2fe94038c9364 to your computer and use it in GitHub Desktop.
Land of Haskell #2

Land of Haskell #2 - Build Haskell -

What is This ?

IGGG Advent Calender 2015のために書いた記事です。
プログラミング言語Haskellを複数に分けて簡単にまとめてみました。
第2回はHaskellの基本構文についてです。
基本構文なんて、今更自分が話す必要はないのですが、以下が解ればコードを読めるので(出来るかわからないけど)今後Haskellの細かい話をするうえで便利なので、まとめておきます。

開発環境

Haskellの開発環境にはいくつか種類があります。
恐らく一番簡単なのは公式サイトにいってHaskell Platform をインストールする事でしょう。
Haskell Platform はGHCを利用しています。
他にもHugsやJHCと言うのもあるようです。

また、最近はGHCよりもsltackというのが流行っているようです。
stackはHaskell Platformに比べ、バージョンの競合とかが起きにくい(と言うか起きないようにしてる)感じです。
検索するとやり方が出てくるので、今回は割愛します。

実行の仕方

ターミナル上で、ghc <ファイル名>と打つとコンパイルされて実行ファイルが生成されます。
また、ghciと打てばHaskellのインタプリタが起動します。
関数を定義して利用する、と言う話であればGHCiの方が楽なので、今回はGHCi主体で説明します。

GHCi

GHCiにはいくつかの専用コマンドがあります。
例えば

  • :q,:quit: GHCi終了
  • :l <ファイル名>,:load <ファイル名>:ソースコードを読み込む
  • :r,:reload: 読み込んでいるソースコードの再読み込み
  • :t <式>, :type <式>: 式(関数や値)の型を返してくれます(便利)

他にもいろいろあります。
:help:hとやれば確認できます。

基本演算と関数の利用

GHCiで試してみましょう。

Prelude> 1 + 1 - 2 * 3 / 2
-1.0
Prelude> 4 `div` 2 `mod` 3
2
Prelude> 2 ^ 4
16
Prelude> True && False
False
Prelude> div 4 2
2
Prelude> (+) 1 2
3

Haskellでは+^のような記号で書かれた演算(関数)は中置記法になります。
それ以外の関数は前置記法で、空白区切りで引数を与えます。
また、前置記法の関数もバッククォート(`)で囲ってやると、中置記法になり、中置記法の関数は括弧で囲むと前置記法になります。

コメントアウト

--でそれ以降コメントアウトで、{- -}で囲むとその範囲をコメントアウトします。

関数の定義

例として、引数の値に10を加える関数を定義します。

add10 :: Int -> Int
add10 n = n + 10

まず、最初の行に関数名 :: 型を書きます。 ただし、Haskellは強力な型推論を持っているので、関数の定義から型が推論できる場合、書かなくてもコンパイルは通ります(コンパイラが型を推論するので)。
今回はわかりやすく全部書いていきます。

Int -> Intは"Int型の引数を1つ貰いInt型の値を返す"型と言う意味です。
Haskell(というか全ての関数型)は関数を第一級オブジェクトとして扱う(このような関数を高階関数と言う)ので、Int -> Intとうのも、IntDouble等と変わらない1つの型です。

2行目から関数の定義を書きます。
add10 nnが引数になり、=の右辺が返す値(と言うかは式)になります。
ちなみに、引数の部分を書かずに以下のように定義することも可能です。

add10' = (+ 10)

実行してみると

*Main> add10 1
11
*Main> add10 0
10

となります。

条件分岐と繰り返し

Haskellの条件分岐はいくつか書き方があります。

if-then-else

だいたいどの言語にでもあるif-then-else構文が利用出来ます。
ただし、elseを省略できません。

sign :: Int -> Int
sign n = if n == 0 then 0 else if n > 0 then 1 else -1

case

case 式 of で与えた式の場合分けで記述する。

fizz :: Int -> String
fizz n = case n `mod` 3 of
           0 -> "Fizz"
           _ -> show n

(_はワイルドーカード)

ガード

ガード(|)に条件を優先度の高い順に記述して分岐する。

fizzBuzz :: Int -> String
fizzBuzz n
    | 0 <- n `mod` 15 = "FizzBuzz"
    | 0 <- n `mod` 5  = "Buzz"
    | 0 <- n `mod` 3  = "Fizz"
    | otherwise       = show n

ちなみに、ガードもif-then-elsecaseの構文糖衣でしかないです。

パターンマッチ

パターン町井と言う機能を利用すると、そもそも構文がいらない。
(というか、ガードとかもパターンマッチは利用してたけど...)

sign :: Int -> Int
sign 0 = 0
sign n = if n > 0 then 1 else -1

ちなみに、分岐を書くなら、このパターンマッチの書き方を極力お勧めします。
読みやすいので。
次に話す、whereをうまく利用すれば、全てパターンマッチで書けます。
余り冗長になるようでしたら他でもいいですが。

繰り返し

Haskellには、forwhileのような繰り返し文はありません。
なので、関数の再帰呼び出しで表現します。

例えば、最大公約数を求める関数gcdを考えます。
C言語なら(本質的でないのでa > bを仮定します)

int gcd(int a, int b) {
	int c;
	do {
		q = a % b;
		a = b;
		b = q;
	} while (q > 0)
	return b;	
}

Haskellなら

gcd :: Int -> Int -> Int
gcd a b
    | b > 0 = gcd b (a `mod` b)
    | otherwise = a

関数が自分自身を呼び出すことを再帰呼び出しと言います。

値の束縛

値(と言うか式)の局所的な束縛の仕方には2種類あります。

  • let
  • where節: 正確には束縛ではなく定義

それぞれ以下のように書きます。

hoge :: Int -> Int
hoge a = let b = 100 in
         a + b

fuga :: Int -> Int
fuga a = a + b
    where
      b = 100

もちろん、関数なんかも束縛できます。
大きな違いは、let式は(その名の通り)式ですが、where節は(その名の通り)式ではありません。
letは式なのでどこでも書くことが出来ます。
例えば(let a = 100 in a) + 100と書くのもありです。
対して、whereは関数定義(とcase式)の構文の一部です。

let式の方が便利ですが、インデントの関係で見にくいので、whereを極力使うことをお勧めします。

組込みの型は以下のようなのがあります

  • Int : 整数型
  • Double : 実数型
  • Integer : 長整数(何十桁数が使える)
  • Char : 文字
  • String : 文字列
  • Bool : 真偽値

さらに、特殊な型として

  • リスト : [Int] みたいに書く、線形リスト型
  • タプル : (Int,Char) みたいに書く、いわゆる直積型
  • Maybe : Maybe Int みたいに書く、Just aNothingを返す

リストやタプルは便利なデータ構造です。
特に、リストはHaskellにとって最も基本的な構造なので、リスト操作関数が豊富です。
このページがかなり便利で、Haskellを書くときは常に開いてる気がします(笑)

Maybe型と言うのは、不定値を含む場合に利用する型です。
他の言語では、不定値としてnull何かを利用しますが、Nothingを利用すれば型として保証でき、より安全になります。
(Either型と言うのを利用すれば、不定値ではなく、別の型の値を返すことが出来ます。)

型の定義

Haskellにおいて型はひじょーーーーに奥深いので今回で書きるはずはないのですが、簡単な型の定義方法だけ説明しておきます。

定義の仕方は3つあります(型名の先頭は大文字)。

  • type宣言
  • data宣言: type Tree = Leaf Tree Tree |
  • newtype宣言

type宣言はただのエイリアス(別名のコト)で type Hoge = [Int]のように使います(右辺は既存の型)。
例えば、StringはCharのリストでできているので、type String = [Char]と書きます。

対して、全く新しい型を作る場合はdata宣言を利用します。
例えば、Booldata Bool = True | Falseと等価です。 少し詳しく説明するために二分木の型を考えます。

data Tree a = Node a Tree Tree
            | Leaf a

(複数の値を定義する場合あh|で区切っていきます。)
これは、Tree Intのように利用します(Maybe型と同じです)。
このa型変数と言います。
そして、TreeIntをもらってTree Intを返す初期化関数と考えられるので、型コンストラクタと言います。
型の持ちうる値を定義するためにはNodeLeafのような識別子が必要です。
そして、LeafIntをもらって、(Tree Intの値である)Leaf Intを返す初期化関数と考えられるので、値コンストラクタと言います(紛らわしい)。
ちなみに、値コンストラクタと型コンストラクタは同じでもよく、True,Falseのように値コンストラクタは引数を撮らなくてもいいです(型コンストラクタは引数を撮らない場合、たぶんコンストラクタと呼びません)。
(また、コンストラクタといったら、値の方を指す気がします)。

newtype宣言は値コンストラクタを一つしか持たないdata宣言です。
なんでわざわざ分けているのかと言うと、(たしか)型推論周りを効率化するためらしいです。

おわりに

以上のことがわかれば、とりあえずはコードを読めると思います。
他の関数はHoogleHayooを利用すると簡単に調べられるんじゃないですかね(別に怪しいサイトじゃないですよ)。

おしまい

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment