Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

F#で遊んでみる(8)

7章を読んでいこう。コレクションの話。

まずはリスト。関数型でリストといえば、(car, cdr)のリストだ。

  • 'a list
    • ::がcons。[]が空リスト
    • carはList.head。cdrはList.tail
    • パターンマッチ
      • 空リストパターン
      • コンスパターン(_::_みたいな感じ)
      • リストパターン([ _; x;]みたいな感じ)

リストは、記号をケース識別子にできているという点でコンパイラに特別扱いをされているけど、そこを普通のケース識別子を使っていいのであれば、DUとして定義出来るのが面白い。

> type MyList<'T> = 
-     |Nil
-     |Cons of 'T * MyList<'T>
- ;;
type MyList<'T> =
  | Nil
  | Cons of 'T * MyList<'T>

> let list = Cons (3, Cons(2, Cons(1, Nil)));;
val list: MyList<int> = Cons (3, Cons (2, Cons (1, Nil)))

再帰定義。一気にLISPっぽい。

さて、リストを扱うにはF#のListモジュールを使う。 たとえば、ソートはList.sortを使う。

この関数の型は、こうなってる。

> List.sort;;
val it: ('a list -> 'a list) when 'a: comparison

「comparison制約が付いている」と、読む。どういうことかは下を読む。

List (FSharp.Core)

Sorts the given list using Operators.compare.

というわけで、Operators.compare(Ruby出言うところのUFO演算子<=>にあたるもの)が定義されてないとダメだよってことですな。

で、sortはcompareを使って比較するわけだけども、比較する方法を与えてやりたいことはもちろんある。2つあって、

> List.sortBy;;
val it: (('a -> 'b) -> 'a list -> 'a list) when 'b: comparison

> List.sortWith;;
val it: (('a -> 'a -> int) -> 'a list -> 'a list)

この2つ。まあ、なんとなく想像は付きますな。sortByは'aから'bに変換する関数を渡して、'bを評価して並び替える。だから'bにcomparison制約が付く。sortWithの方は、今回限りのcompare関数を渡してソートする。

> let gengo = [("Meiji", 1868); ("Taisho", 1912); ("Showa", 1926); ("Heisei", 1989); ("Reiwa", 2019)];;
val gengo: (string * int) list =
  [("Meiji", 1868); ("Taisho", 1912); ("Showa", 1926); ("Heisei", 1989);
   ("Reiwa", 2019)]

> gengo |> List.sort;;     // タプルの比較は先頭の要素の比較
val it: (string * int) list =
  [("Heisei", 1989); ("Meiji", 1868); ("Reiwa", 2019); ("Showa", 1926);
   ("Taisho", 1912)]

> gengo |> List.sortBy (fun x -> match x with _, year -> - year);;  // タプルの2つ目の要素の逆順で比較
val it: (string * int) list =
  [("Reiwa", 2019); ("Heisei", 1989); ("Showa", 1926); ("Taisho", 1912);
   ("Meiji", 1868)]

> gengo |> List.sortWith (fun x y ->    // タプルの比較の逆順で比較
-     let i = compare x y
-     -i);;
val it: (string * int) list =
  [("Taisho", 1912); ("Showa", 1926); ("Reiwa", 2019); ("Meiji", 1868);
   ("Heisei", 1989)]

他にいろんな関数が紹介される

  • List.permute indexMap list
    • indexMap : int -> int // n番目の要素をm番目に移すっていうマップを示す関数
  • List.append

リストは内包表記もある。シーケンス式って言うらしい。見た目は、Pythonのアレとほぼ同じだね

> [1..3];;
val it: int list = [1; 2; 3]

> [0..2..10];;
val it: int list = [0; 2; 4; 6; 8; 10]

> [ for n in 0..9 do
-     let m = n * n
-     if m > 10 then m ]  
- ;;
val it: int list = [16; 25; 36; 49; 64; 81]

さて、ここで本では

> [ for n in 0..9 do
-     let m = n * n
-     if m > 10 then yield m ]  
- ;;

と書いてある。yieldを使うことによって、どれをリストの要素として返すのか明確にできる・・・のかな。ま、この例では省略可能です。

yieldには後ろに!が付いたバージョンもあって、yield!はflattenしてくれます。

> [ yield [1..3]
-   yield [4..6]
- ];;
val it: int list list = [[1; 2; 3]; [4; 5; 6]]

> [ yield! [1..3]
-   yield! [4..6]
- ];;
val it: int list = [1; 2; 3; 4; 5; 6]

他のListの関数がざっと紹介されている。

  • length
  • isEmpty
  • rev
    • reverseの略。何故略した・・・
  • nth

まあ、わかる。下は演算系

  • max
  • min
  • sum
    • sumBy
  • average
    • averageBy

これが実行出来るためにはsortの時と同じようにいろいろ条件が付く。比較できないとダメだったり、足し算が定義されてないとだめだったり。例えば、averageは割り算をした結果が同じ型にならないので、整数では使えない。小うるさいね。そのために、要素をfloatに変換する関数を引数に取れるaverageByが用意されてる。それはやり過ぎじゃない?(笑)

  • map
    • mapi(Rubyでいうところのmap.with_index)
    • map2(Rubyでいうところのzipしてmap)
  • reduce
  • fold
    • 初期値を指定するバージョンのreduce
  • foldBack, reduceBack
    • 後ろから適用する
  • zip
  • unzip
  • concat
  • collect(Rubyでいうところのflat_map/collect_concat)
  • filter
  • partition(渡した関数の結果がtrueとfalseの両方のバージョンを返すfilter)
  • choose(mapしてfilterする。mapするときはoptionを作るようにすると、filterがSomeだけにしてくれる。便利そう)
  • find(見つからなかったら例外)
  • tryFind(optionを返す)
  • findIndex/tryFindIndex
  • pick/tryPick(findしてmapする)
  • exists/forall(Rubyのany?とall?)
  • scan(foldの途中の計算結果をリストにして返す)
  • iter
  • toArray/ofArray, toSeq/ofSeq
    • 両方あるのが面白い

本でListモジュールで紹介されている関数は以上。

で、公式ドキュメントも見ておこう。10年の間に増えてるかもしれないし。

fsharp.github.io

  • allPairs(RubyのArray.product)
  • chunkBySize
  • compareWith(2つのリストを与えたcompare関数(List<'T> -> List<'T> -> int)で比較する)
  • contains
  • countBy(関数を渡して変換して、変換結果をtallyする)
  • distinct/distinctBy
  • exactlyOne(要素が一個だけならそれを取り出して返す。要素が空、もしくは複数なら例外)
  • expect(引数に渡したシーケンスに含まれているモノを対象から除外したリストを返す)
  • groupBy
  • indexed
  • insertAt
  • insertManyAt
  • item
  • pairwise(リストの先頭から2要素ずつ取ってペアを作る [1; 2; 3; 4] -> [(1, 2); (2, 3); (3,4)])
  • removeAt
  • removeManyAt
  • replicate(List.repl.icate 3 "a" -> ["a"; "a"; "a"])
  • singleton(引数をそれが1つだけ入ったリストにする)
  • skip(先頭のいくつかを飛ばす)
  • skipWhile
  • splitAt
  • splitInto(リストを指定した数に分割する)
  • transpose(転置行列を作る)
  • where(filterと同じ)
  • windowed

たっぷりあるね