Skip to main content

tk3++

Tag: Fsharp

F# Workbook

Google Gemini を使って、F# の基本的な学習をやってました。その記録を簡単にまとめておきます。

プロンプトは特に考えずに、F# を基本が学習したい と入力し、次に 1 日ひとつ F# に関する何かを学習したい と入力するだけ。 たった 2 つのプロンプトで短期のカリキュラムを考えてくれました。

それで実際生成されたものです。1 週間分あります。1 日 10 分くらいでできるものでした。内容が足りないと感じたら、もっと深く理解したい と入力すると、さらに課題を出してくれます。便利な世界になりましたね。

  • Day 1: 不変性(Immutable)の安全さと、let の基本
  • Day 2: 関数をパーツのように扱う「カリー化と部分適用」
  • Day 3: 括弧の地獄から解放される「パイプライン演算子(|>)」
  • Day 4: データの形を綺麗に仕分ける「パターンマッチ」
  • Day 5: Nullの悪夢を世界から消し去る「Option 型」
  • Day 6: 不正な状態を型レベルで防ぐ「判別共用体(DU)」
  • Day 7: ループを使わずに宣言的にデータを加工する「コレクション操作」

# 学習した内容

## 「最後の引数をあえて省略して関数を定義する」スタイル(ポイントフリースタイルとも呼ぶ)

let multiply x y = x * y

let triple = multiply 3

let result = triple 4
printfn "結果は %d です" result  // 12

## パイプライン演算子(|>)

let square x = x * x
let increment x = x + 1

// パイプラインを使った書き方
let result = 4 |> square |> increment

printfn "結果は %d です" result // 17

実は (|>) という関数だという。

MyList実装 Head/Tail/Remove F#

dojo/fsharp/MyList/Program.fs at main · tk3/dojo

今日はHead/Tail/Removeを実装した。これまでの経験があればこそ比較的楽に書けた。 ただ、書けはしたものの、tryItemがちょっと怪しくて、removeAtは怪しい。体感では理解していても、きちんとした理解になっていない気がする。慣れるしかないのか。分かるようで分かっていない感じ。あーもどかしい。

let tryHead list =
    match list with
    | Empty -> None
    | Cons(x, _) -> Some x

let tryTail list =
    match list with
    | Empty -> None
    | Cons(_, tail) -> Some tail

let rec tryItem idx list =
    match list with
    | Empty -> None
    | Cons(x, tail) ->
        if idx = 0 then
            Some(x)
        else
            tryItem (idx - 1) tail

let rec removeAt idx list =
    match list with
    | Empty -> Empty
    | Cons(x, tail) ->
        if idx = 0 then
            tail
        else if idx < 0 then
            Cons(x, tail)
        else
            Cons(x, removeAt (idx - 1) tail)

MyList実装 mapとfilterそしてfold F#

dojo/fsharp/MyList/Program.fs at main · tk3/dojo

今日はmapとfilterそしてfoldを実装した。 foldができたということで、これまで実装したlength、sum、reverseをfoldを使い実装し直した。foldを使うとこれまでのコードが短く書けて、汎用性が高い関数であることが分かった。

例えば、foldを使うことで、次のコードが

let sum list =
    let rec loop acc currentList =
        match currentList with
        | Empty -> acc
        | Cons(x, tail) -> loop (acc + x) tail
    loop 0 list

次のように書ける。

fold (fun acc x -> acc + x) 0 list5

ただ、実装していて腑に落ちなかったことが一つ。F#の文法についてだ。

コンパイルNGのコード

110 let foldFilter f list =
111     let folded = fold (fun acc x ->
112         if f x then
113             Cons(x, acc)
114         else acc
115     ) Empty list
116
117     reverse folded

エラーメッセージはこちら

Program.fs(115,7): error FS0010: Unexpected identifier in binding. Expected incomplete structured construct at or before this point or other token.
Program.fs(111,5): error FS3118: Incomplete value or function definition. If this is in an expression, the body of the expression must be indented to the same column as the 'let' keyword.

コンパイルOKのコード

リストの実装(MyList) F#

dojo/fsharp/MyList/Program.fs at main · tk3/dojo

F#でデータ構造を理解するためにリストを実装した。面白かった。

おおもとのデータ定義。 ConsはConstructの略語。お決まりの宣言みたい。Emptyはその名の通り、空を表すもの。‘Tは値。今回のコードではintを使っている。

type MyList<'T> =
    | Empty
    | Cons of 'T * MyList<'T>

実際にリストを表現すると、以下のようになる。マトリョーシカのような感じで、前の値を包み込むようになっている。

Cons (4, Cons (3, Cons (2, Cons (1, Empty))))

今回は、length・sum・Some・contains・reverseを書いてみた。リスト実装はもう少し深掘りする予定。

ちなみにlengthはこんな感じ。forループではなく再帰で書いている。 accとは「Accumulator(アキュムレータ)」の略で、累積した値を保持している。この場合は長さを保持している。 matchで条件分岐し、Emptyになったら終了。再帰は何となくこんな感じで書くのかという理解。まだ慣れていないけれど。

let length list =
    let rec loop acc currentList =
        match currentList with
        | Empty -> acc
        | Cons(_, tail) -> loop (acc + 1) tail
    loop 0 list

選択ソート F#

dojo/fsharp/MyList/Program.fs at main · tk3/dojo

今日は選択ソート。昨日よりはすんなり書けて満足。もっと短く書けそうだが、今の自分には無理そう。

open System

[<EntryPoint>]
let main arg =
    let rnd = Random()
    let randomArray = [| for i in 1 .. 10 -> rnd.Next(1, 101) |]

    for i = 0 to randomArray.Length - 1 do
        let mutable target = i 
        let mutable minVal = randomArray[i]
        for j = i to randomArray.Length - 1 do
            if minVal > randomArray[j] then
                minVal <- randomArray[j]
                target <- j

        let tmp = randomArray[i]
        randomArray[i] <- randomArray[target]
        randomArray[target] <- tmp

    printfn "%A" randomArray

    0