第1回 OCaml勉強会をしました (2020/3/19)
友達とOCamlの勉強会をした時のメモ。
読んだのは プログラミングの基礎。
メトロネットワーク最短経路問題
を解くプログラムを作るのがゴールになっている。
リンクメモ
「関数型言語」に関するFAQ形式の一般的説明・・・関数型言語とは、副作用とは
tryOCaml・・・対話環境を触ってみることができる。
著者の授業・・・情報学科2年生の動画授業、本の後ろの方が中心なので最後の方になってきたら参考にする。スライドと予習問題もついてる。
OCamlの特徴
- 型宣言が不要 (型推論してくれる)
- 型変換が自由自在
- コンパイル時に全ての x ( 例えば全てのint ) で成り立つかどうか証明をしてくれるので、コンパイルできればエラーが起きない。(Coqという証明プログラミング言語で証明しているそう)
型推論
変数も関数も、型を指定してあげなくても推論してくれる。
let a = 3
で変数定義ができる
[対話環境]
# let a = 3;;
- : int = 3 (*勝手にintだと推論してくれる*)
型チェック
let f x = x + 1
で、xに1をたす関数が定義でき、入力値と出力値を推論してくれる。
使うときは、関数に違う型が入っていないかチェックしてくれる。
強く型付けされた言語では、型チェックによってプログラム中の間違いが大幅に少なくなり、プログラムの信頼性が大きく向上する。
# let f x = 2 * x;;
val f : int -> int = <func>
# let g x y = (x +. y) /. 2. ;;
val g : float -> float -> float = <func>
勝手にint -> int の関数だと推論し、それ以外の型が入るとエラーになる(型チェック)。
型を推論できるように、float型の演算は +. -. /. *.のように.をつける決まりになっている。
任意の型が入る関数
恒等関数など ’a -> ‘a
イコール
代入の = がないので、数学的な = しかない
3 = 4って書いたら条件式になる
組とレコード
組は(“hori” , 3) と書いた場合、string * intの順番を覚えていないといけない
レコード = 名前のついたデータの集まり
を使うと、要素の順番を変えても同じデータとして認識することができる
レコードの定義はtypeを使う
type gakusei_t = { name : string ; ten : int ; seiseki : string};;
みたいな感じで定義する。
構造体のようなデータ型。
パターンマッチ
match 式 with
パターン -> 式
match (3, 5) with
(a, b) -> a + b;;
a, bのことをパターン変数
という。
これの最初の方に let add pair = と書いてあげると関数になる。
let add pair = match pair with
(a, b) -> a + b ;;
レコードで駅の情報を持ったレコード、接続情報を持ったレコードを定義することができた。
しかし、例えば茗荷谷駅に接続している駅全てを抽出したりすることはまだできない。
そのために必要なのが「リスト」
という概念。
リスト
何百、何千という大量のデータを扱う時に、威力を発揮する。
組やレコードは決まった個数のデータを取り扱うものだけど
リストは 要素がいくつ並んでいても構わない点で大きく異なる。
データを好きな個数並べることができるので、柔軟性が高く色々な場面で使うことができる!!!
[] : 空リスト
:: : cons (コンストラクト)
リストは再帰的なデータ型 / 自己参照をするデータ型
<<定義>>
空リスト [] はリストである
x が要素、yがリストの時、x :: y はリストである
このようなデータ型を再帰的なデータ型
という。任意の長さのリストを作ることができる。
x :: [] // 型リストに x を追加
1 :: 2 :: 3 :: [] (*[1; 2; 3]・・・要素数3のリスト*)
1 :: [] (*[1]・・・要素数1のリスト*)
"a" :: "b" :: "c" :: [] (*["a"; "b"; "c"]・・・string型 要素数3のリスト*)
リストとパターンマッチ
リストの場合は形が一つに決まってはいないので、リストは空リストかもしれないし長いリストかもしれない。
match 式 with
パターン1 -> 式1
| パターン2 -> 式2
| パターン3 -> 式3
以下の構文は入力データの型が、リストだった場合のテンプレート
match 式 with
[] -> 空リストだった時の場合
| x :: y -> そうでなかった時の場合
リストと再帰関数
リストは新しいし、重要な概念なので練習問題をしっかり解きました。
再帰関数を定義するときは let rec
で定義します。
ex1 ) リストに0が含まれるかどうか判定する関数
リストの先頭から順番に0かどうか判定していく
let rec contain_zero list = match list with
[] -> false
| x :: rest -> if x = 0 then true else contain_zero rest
let a = 1 :: 0 :: 2 :: []
let test = (contain_zero a = true) (*test = aを入れた結果がtrueかどうか*)
val test : bool = true
ex2 ) リストの要素を全部足し合わせる関数
リストの先頭から一つずつ足していく
let rec sum list = match list with
[] -> 0
| x :: rest -> x + sum rest
let a = 1 :: 1 :: 1 :: 1 :: 1 ::[] (*1が5つ入ってるリスト*)
let test = (sum a = 5) (*test = aを入れた結果がtrueかどうかの結果*)
val test : bool = true
ex3) リストの長さを返す関数
let rec len list = match list with
[] -> 0
| x :: rest -> 1 + len rest
let a = 1 :: 1 :: 1 :: [] (*1が3つ入ってるリスト*)
let test = (len a = 3)
val test : bool = true
ex4) リストの偶数だけ取り出したリストを返す関数
let rec even list = match list with
[] -> []
| x :: rest -> if x mod 2 = 0 then x :: even rest else even rest
let a = 1 :: 2 :: 3 :: 4 :: 5 :: 6 :: 7 :: 8 :: []
let test = (even a = [2; 4; 6; 8])
val test : bool = true
ex5) 文字列のリストを受け取って全て繋げた文字列を返す関数
let rec concat list = match list with
[] -> ""
| x :: rest -> x ^ concat rest
let a = "春" :: "夏" :: "秋" :: "冬" :: []
let test = (concat a = "春夏秋冬")
val test : bool = true
複雑なリスト
intやstring型のリストではなく、レコード型のリストのときは少し複雑になる。
例えば、(名前、点数、成績)を表すレコードのリストを受け取った時に、成績Aの学生の人数を数えたりしたい。
成績Aの学生の人数を数える
リストの最初の要素をパターン変数を使って書いてあげてあとは普通に再帰。
(*学生一人分のデータ*)
let student_t = {
name : string;
ten : int;
seiseki : string;
}
let rec count_A list = match list with
[] -> 0
| {name = n; ten = t; seiseki = s} :: rest
-> is s = "A" then 1 + count_A rest else count_A rest