Skip to content

Latest commit

 

History

History
424 lines (328 loc) · 9.83 KB

_ocaml.md

File metadata and controls

424 lines (328 loc) · 9.83 KB

インストール

Install OCaml

  • OCamlとOPAMをインストール
    • OPAM -> OCamlのパッケージマネージャ
# macOS
$ brew install ocaml
$ brew install opam

トップレベル対話環境

  • ocamlコマンドで実行
    • Ctrl + cで停止

演算子など

  • ;; -> 入力の区切り
  • 整数同士の四則演算は +, -, *, /, mod
  • 実数同士の四則演算は +., -. *., /.
  • 実数のべき乗は ** (べき乗は実数のみに用意されている)
    • 整数と実数の計算を行うと型エラー
  • 文字列の連結は ^
  • 論理演算は not, &&, ||
    • 優先順位は not > && > ||
  • 比較演算は >, >=, ==, <, <=
    • 整数・実数の区別はない
  • -. -> 正負を入れ替える単項演算子

変数

  • 変数定義 -> let 変数名 = 式 ;;
  • 変数の先頭はアルファベット小文字

関数

  • 関数定義 -> let 関数名 引数 = 式 ;;
    • 引数が複数の場合 -> let 関数 x y ;;
  • val x : A -> B = <fun> -> 「float型の値を受け取り、float型の値を返す」
    • 引数が複数の場合 -> val x : A -> B -> C = <fun>
      • 右に結合する(一つ目の引数にAを取り、二つ目の引数にBを取り、結果としてCを返す)
    • xはfloat型の関数のため、引数にintを渡すことはできない
    • 型に対するOCamlインタプリタの役割は
      • 型推論
      • 型チェック
  • 引数に負の数を渡す場合は()で囲む

実行順序

  • OCamlでは式を右から実行する
f 0 + f 1 ;;
  • 先にf 1が実行された後f 0が実行される

関数定義のデザインレシピ

目的

  • 何をする関数なのか
    • 何を受け取り、何を返すのか -> 型の決定
  • ヘッダ・見出しを作成

  • 関数に望まれる入力と出力の例を作成
  • 実行可能なテストプログラムを作成

本体

  • どうやって実現するか

テスト

  • 望む動作をしているか

テスト

# function.ml
let function x = x

let test1 = function x = y
  • インタプリタにテストファイルを読み込むだけでテストが自動的に実行される
# #use "function.ml" ;;

条件分岐

if 条件 then 式 else 式
  • 式同士の型を揃える
  • 式中にif文を埋め込むことができる
let 関数 x = x * (if true then 1 else 100)

条件分岐のデザインレシピ

  • 全ての場合についてテストを用意する
  • 閾値をテストする

本体

  • 先に条件式を書き、条件が特定できたらthen節、else節を書く

  • いくつかのデータをまとめて一つのデータにしたもの -> 組
(3.14, 2.71 ) ;;
- : float * float = (3.14, 2.71)
  • 要素の型の組み合わせは異なっていても可、要素数はいくつでも可
    • 組の組も可
((3.14, "a"), 3.14) ;;
- : (float * string) * float = ((3.14, "a"), 3.14)

パターンマッチ

  • 複数のデータから成るデータの中身を取り出す -> パターンマッチ
match 式 with
  パターン -> 式
match (3, 5) with
  (a, b) -> a + b ;;
- : int = 8
  • a, b -> パターン変数
let add pair = match (a, b) with
  (a, b) -> a + b ;;

# 上記と同じ
let add (a, b) = a + b ;;
  • パターン変数はお互いに異なっている必要がある
  • 複数のデータが一つのデータになる場合に組を使用する

構造データのデザインレシピ

テンプレート

  • 構造データが入力された場合、中身を取り出すmatch文を書く
  • テストでmatchの構文があっていることを確認

レコード

  • 名前のついたデータの集まり -> レコード
{ フィールド1 = パターン変数1 ; フィールド2 = パターン変数2 ; フィールド3 = パターン変数3 ; ...}
  • type定義をしてから使う
type student_t = {
  name : string; (* 名前 *)
  score : int; (* 点数 *)
  grade : string; (* 成績 *)
}

let notice student = match student with
  { name = n; score = s; grade = g; } ->
    n ^ "さんは" ^ string_of_int s ^ "点で、成績は" ^ g ^ "です"
  • n, s, gがパターン変数
  • typeのフィールド名は一意であること

テータ定義のデザインレシピ

データ定義

  • 入力データの型・出力データの型を考える

再帰関数に対するデザインレシピ

  • 再帰するケース
  • 再帰しないケース(基本ケース...リストが空の場合)

本体

  • 再帰関数の場合はletの後にrecを付加
  • first :: restの場合、再帰できるのは自己参照しているrestのみ

局所変数定義

let 変数名 = 式1 in 式2
  • let 変数名 = 式1 までを一区切りとして考える

局所関数定義

let 関数名 引数 = 式 in 式
let rec 関数名 引数 = 式 in 式

無名関数

fun 引数 -> 式
(fun x -> x + 1) 5 ;;
- : int = 6
  • 再帰関数は定義できない

無名関数に名前を与える

let add_one = (fun x -> x + 1) 5 ;;
val add_one : int = 6

infix関数とprefix関数

  • 関数の後に引数を置く関数(普通の関数)
  • infix関数 -> +のように関数の前後に引数を置く関数
    • ()で囲むことによってprefix関数に変換することができる

一般の再帰に対するデザインレシピ

テンプレート

  • 自明に答えが出るケースの条件
  • 自明に答えが出るケースの式
  • それ以外のケースの式

本体

  • 自明に答えが出るケースはどのような場合か
  • その場合の答えは何か
  • 部分問題はどのように作れるか
  • 部分問題の結果から全体の結果を得るにはどうすれば良いか

アキュムレータ

  • 欠落している情報を補うために導入される引数(情報を貯めておくために使う)

アキュムレータに対するデザインレシピ

本体

  • アキュムレータを使った関数は局所的に補助関数として使用され、特定の初期値を要とする

バリアント型

  • どれか一つを表す型
type team_t = Red | White

# RedやWhiteは構成子
# 構成子は必ず大文字から始める

let team_string team = match team with
    Red -> "赤"
  | White -> "白"
  • 構成子には引数を渡すことができる
type year_t = Meiji of int
              | Taisho of int
              | Showa of int
              | Heisei of int
              | Reiwa of int
# 引数は「構成子 of 型」という形で表現する

Reiwa (1)

Empty ;;
# -> 空の木

Leaf (3);;
# -> int 3を格納した葉

Node (Empty, 7, Leaf(3)) ;;
# -> int 7を格納し、左右にそれぞれ空の木とint 3を格納した葉を持つ節
# -> 左右の葉が自己参照になっている

再帰的なデータ構造に対するデザインレシピ

データ定義

  • 自己参照しないケースがあることを明確にしておく

テンプレート

  • 型に対応するmatch文を書く
    • 自己参照するケースが再帰呼び出しのケースに対応する
    • 自己参照する数だけ再帰呼び出しされる

オプション型

type 'a option = None
               | Some of 'a
  • 値が無いか'a型の値があるかのどちらか

例外処理

  • raise XXXError -> 例外を発生させる
  • exception XXXError -> 例外を定義する
    • exception XXXError of 引数の型
  • try 式 with -> 例外処理(例外が発生しなければ式の結果が返る)

モジュール

module モジュール名 = struct
  # 本体
end
  • モジュール名は大文字から始める

シグネチャの宣言

  • シグネチャ = モジュールのインタフェース定義(抽象データ型)
module type シグネチャ名 = sig
  # 本体
end

モジュールとシグネチャを同時に定義する

module モジュール名 : シグネチャ名 = struct
  # 本体
end
module モジュール名 : sig
  # シグネチャ本体
end = struct
  # モジュール本体
end

unit型

  • 何も引数に取らず、何も意味のある値を返さない型
  • 返される値は()(ユニット: 意味のない値)

逐次実行

(式1; 式2; ..; 式n)
  • 左から順に実行しては返り値を捨て、最終的に右端の式を実行してその結果を返す
  • 式nよりも前に実行する式の返り値がunitでない場合、警告が表示される

参照型

  • 参照型の定義
let x = ref 初期値
  • 参照型の呼び出し
!x
  • 参照型の再代入
x := 値
  • 一度定義された参照型は同じメモリ番地を参照し続ける(参照型に対して行われる操作は常に破壊的)

ミュータブル

  • 基本的に全てのレコードの全てのフィールドがイミュータブル
  • 型を定義する際、フィールド名の手前にmutableをつけることでミュータブルなフィールドになる
type = {
  mutable hoge : string; # ミュータブル
  fuga : string; # イミュータブル
}
  • ミュータブルなフィールドに対しては<-で値を再代入できる
hoge <- "moge"
  • ミュータブルなレコードに参照透過性はない

配列

[|要素1; 要素2; ...; 要素n|]
  • 配列の要素の型は全て同じであること
  • 配列の要素を取り出す
let arr = [|1; 2;|]
Array.get arr 0
# => - : int = 1

arr.(0)
# => - : int = 1
  • 配列の要素を書き換える
let arr = [|1; 2;|]
Array.set arr 0 0
# => - : unit = ()

a
# => - : int array = [|0; 1; 2;|]

arr.(3) <- 3
# => - : unit = ()

a
# => - : int array = [|0; 1; 2; 3;|]
  • 配列に参照透過性はない