《プログラミングの基礎》をReScriptで読む 第18章

《プログラミングの基礎》](https://www.amazon.co.jp/dp/4781911609/ "Amazonへのリンク")はとても良い本です。
サンプルプログラムをReScriptで書いて、プログラミングの基礎とReScriptを学びます。
# 環境
* macOS Sonoma(バージョン 14.4.1)
* ReScript 11.1.0-rc.8
* @rescript/core 1.2.0
# 第18章
```ReScript:Ex18_1.res
open Ex08_3 // person_t の定義
open Ex08_4 // person_t 型のデータの例
 
// person_t list 型のデータの例
let lst1 = list{person1, person2, person3}
let lst2 = list{person3, person2, person1}
 
// 目的：lst に含まれる最初の A 型の人を返す
// first_A : person_t list -> person_t option
let rec first_A = (lst) => switch lst {
| list{} => None
| list{first, ...rest} => switch first {
  | {name: _, shincho: _, taiju: _, tsuki: _, hi: _, ketsueki: k} => 
      if k == "A" {Some(first)} else {first_A(rest)}
  }
}

// テスト
Console.log(first_A(list{}) == None)
Console.log(first_A(lst1) == Some(person1))
Console.log(first_A(lst2) == Some(person1))
```
```ReScript:Ex18_2.res
// 目的：item の値段を調べる
// price : string -> (string * int) list -> int option
let rec price = (item, yaoya_list) => switch yaoya_list {
| list{} => None
| list{(yasai, nedan), ...rest} =>
    if item == yasai {Some(nedan)}
    else {price(item, rest)}
}

// 八百屋においてある野菜と値段のリストの例
let yaoya_list = list{("トマト", 300), ("たまねぎ", 200), 
  ("にんじん", 150), ("ほうれん草", 200)}

// 目的：八百屋リストには入っていない野菜の数を返す
// count_urikire_yasai : string list -> (string * int) list -> int
let rec count_urikire_yasai = (yasai_list, yaoya_list) => switch yasai_list {
| list{} => 0
| list{first, ...rest} => switch price(first, yaoya_list) {
	| None => 1 + count_urikire_yasai(rest, yaoya_list)
	| Some(_) => count_urikire_yasai(rest, yaoya_list)
  }
}

// テスト
Console.log(count_urikire_yasai(list{"たまねぎ", "にんじん"}, yaoya_list) == 0)
Console.log(count_urikire_yasai(list{"たまねぎ", "じゃがいも", "にんじん"}, yaoya_list) == 1)
Console.log(count_urikire_yasai(list{"しいたけ", "なす", "にんじん"}, yaoya_list) == 2)
```
```ReScript:Ex18_3.res
// 目的：受け取った ekimei0 までの距離を lst から探して返す
// 見つからなかったら例外 Not_found を起こす
// assoc : 'a -> ('a * 'b) list -> 'b
let rec assoc = (ekimei0, lst) => switch lst {
| list{} => raise(Not_found)
| list{(ekimei, kyori), ...rest} => 
    if ekimei == ekimei0 {kyori}
    else {assoc(ekimei0, rest)}
}

// テスト
//Console.log(assoc("後楽園", list{})) // Not_found を起こす
Console.log(assoc("後楽園", list{("新大塚", 1.2), ("後楽園", 1.8)}) == 1.8)
// Console.log(assoc("池袋", list{("新大塚", 1.2), ("後楽園", 1.8)} // Not_found を起こす *) 
```
```ReScript:Ex18_5.res
open Ex09_10  // ekikan_t, global_ekikan_list の定義
open Ex12_1   //eki_t の定義
open Ex17_10  // ekikan_tree_t の定義
open Ex17_13  //inserts_ekikan の定義
open Ex18_3   //assoc の定義

// 目的：ふたつの駅の間の距離を求める
// 見つからなかったら例外 Not_found を起こす
// get_ekikan_kyori : string -> string -> ekikan_tree_t -> float
let rec get_ekikan_kyori = (eki1, eki2, tree) => switch tree {
| Empty => raise(Not_found)
| Node(left, k, lst, right) =>
    if eki1 < k {get_ekikan_kyori(eki1, eki2, left)}
    else if k < eki1 {get_ekikan_kyori(eki1, eki2, right)}
    else {assoc(eki2, lst)}
}

// テスト
let global_ekikan_tree = inserts_ekikan(Empty, global_ekikan_list)
Console.log(get_ekikan_kyori("茗荷谷", "新大塚", global_ekikan_tree) == 1.2)
//Console.log(get_ekikan_kyori("茗荷谷", "池袋", global_ekikan_tree)
  // Not_found を起こす
Console.log(get_ekikan_kyori("東京", "大手町", global_ekikan_tree) == 0.6)
 
// 目的：未確定の駅のリスト v を必要に応じて更新したリストを返す
// koushin : eki_t -> eki_t list -> ekikan_tree_t -> eki_t list
let koushin = (p, v, ekikan_tree) => switch p {
| {namae: pn, saitan_kyori: ps, temae_list: pt} =>
    List.map(
      v,
      (q) => switch q {
	    | {namae: qn, saitan_kyori: qs, temae_list: _} => 
		    try {
  		    let kyori = get_ekikan_kyori(pn, qn, ekikan_tree)
		      if ps +. kyori < qs {{namae: qn, saitan_kyori: ps +. kyori, temae_list: list{qn, ...pt}}}
		      else {q} 
        } catch {
        | Not_found => q
        }
      }
    )
}

// 駅の例
let eki1 = {namae: "池袋", saitan_kyori: Float.Constants.positiveInfinity, temae_list: list{}} 
let eki2 = {namae:"新大塚", saitan_kyori: 1.2, temae_list: list{"新大塚", "茗荷谷"}}
let eki3 = {namae:"茗荷谷", saitan_kyori: 0., temae_list: list{"茗荷谷"}}
let eki4 = {namae:"後楽園", saitan_kyori: Float.Constants.positiveInfinity, temae_list: list{}}
 
// 駅リストの例
let lst = list{eki1, eki2, eki3, eki4}
 
// テスト
Console.log(koushin(eki2, list{}, global_ekikan_tree) == list{})
Console.log(koushin(eki2, lst, global_ekikan_tree) == 
 list{{namae: "池袋", saitan_kyori: 3.0, temae_list: list{"池袋", "新大塚", "茗荷谷"}},
  eki2, eki3, eki4})
```
```ReScript:Ex18_6.res
// 駅名が見つからなかったことを示す例外
exception No_such_station(string)
```
```ReScript:Ex18_7.res
open Ex08_5 //ekikan_t の定義
open Ex09_9 //ekimei_t, global_ekimei_list の定義
open Ex18_6 //No_such_station の定義

// 目的：ローマ字の駅名を漢字に直す
// 見つからなかったら例外 No_such_station を起こす
// romaji_to_kanji : string -> ekimei_t list -> string
let rec romaji_to_kanji = (r0, ekimei_list) => switch ekimei_list {
| list{} => raise(No_such_station(r0))
| list{{kanji: k, kana: _, romaji: r, shozoku: _}, ...rest} =>
    if r0 == r {k} else {romaji_to_kanji(r0, rest)}
}

// テスト
Console.log(romaji_to_kanji("myogadani", global_ekimei_list) == "茗荷谷")
Console.log(romaji_to_kanji("shibuya", global_ekimei_list) == "渋谷")
Console.log(romaji_to_kanji("otemachi", global_ekimei_list) == "大手町")
// Console.log(romaji_to_kanji("osaka", global_ekimei_list))
  // No_such_station "osaka" を起こす
```