LoginSignup
4
3

More than 5 years have passed since last update.

Haskell入門ハンズオン! #3: 当日用資料 1/5

Last updated at Posted at 2018-03-09

Haskell入門ハンズオン! #3: 当日用資料 1/5

はじめに

この記事は2018年3月11日に開催のHaskell入門ハンズオン #3の当日用の資料だ。

サンプルコード

サンプルコードは以下の短縮URLから入手できる。

内容

Haskellでパーサコンビネータを自作し、四則演算の式を解析して(しつつ)、評価するプログラムを作っていく。コードを書きながら、Haskellの構文や技法を紹介していく。例題のコードは拙書「Haskell - 教養としての関数型プログラミング」の例を使った。

「第20章 演習: 構文解析」の1章ぶんをまるまる解説する。第20章はこの書籍の298から310ページまでの内容だ。書籍では200ページ以上かけて、この例題で必要な要素を説明している。その部分をまるまるとばす。コードを書くなかで、適宜、解説をいれるが、完全に理解できなくていい。雰囲気を感じとってもらいたい。

環境構築

環境の構築はHaskell入門ハンズオン! 事前資料(2/3)を参考にしてもらいたい。

環境の確認

つぎのようにして、環境構築ができていることを確認する。

% stack ghc -- --version
The Glorious Glasgow Haskell Compilation System, version 8.2.2

対話環境

対話環境を試してみよう。ターミナルにstack ghciと打ち込む。

% stack ghci

とりあえず、数値や文字を打ち込む。

Prelude> 123
123
Prelude> 'c'
'c'

足し算や引き算もできる。

Prelude> 3 + 5
8
Prelude> 15 - 9
6

関数を使う

関数を使ってみよう。

Prelude> negate 8
-8

関数を使うには、つぎのようにする。

[関数名] [引数1] [引数2]

+や-などの演算子も、実は関数だ。

Prelude> (+) 3 5
8
Prelude> (-) 15 9
6

関数(+)に引数3と5とを、関数(-)に引数15と9とを、それぞれ、あたえている。

関数を作る

まずは、変数を定義してみる。

Prelude> n = 1234
Prelude> n
1234
Prelude> n + 4321
5555

関数を定義する。半径から円の面積をもとめる関数。

Prelude> area r = r ^ 2 * pi
Prelude> area 3
28.274333882308138

半径3cmの円の面積は、だいたい28cm^2。変数の定義と関数の定義の構文は、よく似ている。Haskellでは変数piは円周率として定義ずみだ。

定義ファイル

対話環境を終了する。

Prelude> :quit

対話環境で定義した関数は、対話環境の終了とともに消えてしまう。定義した関数を、もっと長く使いたいときには、関数を定義したファイルを対話環境に読み込んで使う。好きなエディタでファイルcircle.hsを作る。僕の場合はVimを使う。

circle.hs
area r = r ^ 2 * pi

これを対話環境に読み込む。

% stack ghci
Prelude> :load circle.hs

試してみよう。

*Main> area 3
28.274333882308138

読み込めたようだ。ここで、対話環境は立ち上げたままにして、ファイルcircle.hsに円周をもとめる関数を追加する。

circle.hs
circumf r = r * 2 * pi

対話環境に、コマンド:reloadを打ち込む。

*Main> :reload
*Main> circumf 3
18.84955592153876

たいていにおいて、僕の開発スタイルはターミナルを、ふたつに分割して、Vimと対話環境を開きっぱなしにして、ファイルを変更するたびに:reloadしながら。というもの。

値には型がある。対話環境で型をみてみよう。

*Main> :type 'c'
'c' :: Char

文字'c'はChar型の値だ。

*Main> :type True
True :: Bool

真(本当)であることをあらわす値TrueはBool型の値だ。

関数の型

関数にも型がある。関数の型は、引数の型と返り値の型とで決まる。例として、文字を大文字にする関数と、文字が大文字であることを確認する関数をみる。まずは、それぞれを使ってみる。

*Main> :module Data.Char
Prelude Data.Char> toUpper 'c'
'C'
Prelude Data.Char> isUpper 'c'
False

関数toUpper, isUpperはモジュールData.Charから公開されている。対話環境では、コマンド:moduleでモジュールを導入する。

関数toUpperは引数も返り値もChar型の値であり、関数isUpperは引数がChar型で、返り値はBool型の値だ。それぞれの型をみてみよう。

Prelude Data.Char> :type toUpper
toUpper :: Char -> Char
Prelude Data.Char> :type isUpper
isUpper :: Char -> Bool

関数の型は、引数の型と返り値の型とを'->'でつなぐ。

型宣言

Haskellでは(たいてい)型は自動的に決まる。しかし、型を明示するのは良い習慣だ。ファイルCircle.hsを編集する。

circle.hs
area :: Double -> Double
area r = r ^ 2 * pi

circumf :: Double -> Double
circumf r = r * 2 * pi

対話環境に読み込んで試す。

Prelude Data.Char> :load circle.hs
*Main> area 3
28.274333882308138

入出力について

Haskellでは「関数は関数」だ。「足し算を評価するついでに、設定ファイルを書きかえておきましたよ」といった(気の利いた|余計な)ことはしてくれない。関数は入出力をしない。

...

「入出力なしで、どうやって生きていけと?!」
Haskellでは関数の評価と入出力とは分離されている。
入出力は、むしろ、ただの値だ。

リストという構造がある。たとえば数のリストの総和をもとめられる。

*Main> sum [3, 4, 5]
12

入出力も値なのでリストの要素にすることができる。

*Main> hello = [putStrLn "hello", putStrLn "world"]

リストに含まれる入出力を組み立てることもできる。

*Main> sequence_ hello
hello
world

逆順にして組み立てることもできる。

*Main> sequence_ (reverse hello)
world
hello

対話環境は評価した結果の値が入出力だったとき、その入出力を実行してくれる。ちなみに、結果の値が入出力でないとき、もしそれが表示可能なら、表示する。

Haskellでは入出力(および状態変化)は、IOモナドという、わくぐみであつかわれる。今回はIOモナドについては、くわしく説明しない。とりあえず覚えておくことは、Haskellでは「入出力は評価のついでに、おこなうものではない」「入出力も、また値である」ということだ。
詳細は拙書を参照のこと。

当日用資料 2/5へ

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3