Upgrade to Pro — share decks privately, control downloads, hide ads and more …

F# Design and Trade-offs: Functional Paradigm o...

Avatar for INOMATA Kentaro INOMATA Kentaro
June 14, 2025
460

F# Design and Trade-offs: Functional Paradigm on .NET

『F#の設計と妥協点 - .NET上で実現する関数型パラダイム』
関数型まつり2025 2025/06/15 10:30〜 Track C
F#は.NETプラットフォーム上で実装された実用的な関数型言語です。本セッションでは、F#の言語設計における特徴的な選択と制約について解説します。

Avatar for INOMATA Kentaro

INOMATA Kentaro

June 14, 2025
Tweet

Transcript

  1. F#の生みの親 Don Syme Microsoft Research (1998-2022), GitHub Next (2022-) F#

    における「優しい終身の独裁者 (BDFL)」 "The Early History of F#" https://fsharp.org/history/ 拙訳 https://matarillo.com/hopl-fsharp/ 5
  2. 非純粋関数型 あえて命令型スタイルでループする例 // ミュータブルな値 let mutable count = 0 while

    count < 5 do printfn "現在のカウント: %d" count count <- count + 1 // 値を変更 printfn "ループ終了。最終カウント: %d" count while の中でbreakやcontinueはできない 再帰関数で書く方がよい 11
  3. 副作用が型シグネチャに現れない // 状態変更とコンソール出力を含む関数 let mutable globalCounter = 0 let incrementCounter

    x = globalCounter <- globalCounter + 1 printfn "Call count: %d" globalCounter x + globalCounter // 型: int -> int // ファイルIOを含む関数 let saveAndAdd filename x y = let result = x + y File.WriteAllText(filename, string result) result // 型: string -> int -> int -> int 13
  4. 参考:OOPに対する4種類の設計判断(F#の妥協点) 1. 取り入れたい機能 2. 必要なら仕方ない機能 ドット記法 ( x.Length ) 型に基づく名前解決

    インスタンスメンバー、静的メンバー プライマリーコンストラクター インデクサー記法 ( arr[x] ) 名前付き引数、オプション引数 インターフェース型とその実装 ミュータブルなデータ 型に対する演算子の定義 自動プロパティ IDisposable と IEnumerable の実装 型拡張 構造体、列挙体、イベント、デリゲー ト キャスト 21
  5. 参考:OOPに対する4種類の設計判断(F#の妥協点) 3. できれば避けたい機能 多段の型階層 実装の継承 null と デフォルト値 4. 意図的にサポートしない機能:

    protected メンバー カリー化されたメソッド 自己型(self types) ワイルドカード型 アスペクト指向プログラミング 22
  6. F#における型チェック(F#の妥協点) 関数型プログラミングの範囲では、 Hindley-Milner型推論が効く // a' list -> (a' * a')

    option let firstTwo list = match list with | x :: y :: _ -> Some (x, y) | _ -> None // 'a * 'b -> 'b * 'a let swap (x, y) = (y, x) オブジェクト指向の範囲では、型に基づく 名前解決になる // コンパイルエラー: レシーバーの型が不明 let getLength x = x.Length let lambda = fun x -> x.Length // 型注釈をつければOK let getLength (x: string) = x.Length let lambda = fun (x: string) -> x.Length // コンパイルエラー: 引数の型が不明 let parse x = System.Int32.Parse(x) 23
  7. パイプライン演算子 ( |> ) の活用 ラムダ式の引数に型注釈が不要になる(こともある) ["apple"; "banana"; "cherry"] |>

    List.filter (fun s -> s.Contains("a")) |> List.map _.ToCharArray() // 省略記法 |> List.concat someStringList |> List.distinctBy (fun s -> s.) // 文字列のメンバーが補完される 24
  8. いくつかの関数型言語における、モナド用の構文 Haskell ⇒ do 記法 do x <- m1 y

    <- m2 x return f x y Scala ⇒ for 式 for { x <- m1 y <- m2 x } yield (f x y) F# ⇒ コンピュテーション式 builder { let! x = m1 let! y = m2 x return (f x y) } 30
  9. モナド構文の裏にある仕組み Haskellは 型クラス ⇒後述 あるデータ型がモナドの一種だと明示的に宣言することで do 記法が使える Scalaは 構造的 あるデータ型が特定のシグネチャのメソッドを提供していれば

    for 式が使え る F#は ビルダー が仲介 どのビルダーを使うかはコードで明示的に指定する 指定したビルダーが、あるデータ型を対象にした構文変換規則を提供してい ればコンピュテーション式が使える 31
  10. F#のコンピュテーション式の ちょっと変わった特徴 モナド( let! - return ) または リスト内包( for

    - yield )の構文変換が基本 Scalaの場合、どちらも for 式で扱う ビルダーの実装次第で、 さらに多様な構文変換をサポートする 遅延計算, ジェネレーター, while, 例外ハンドラ, コードクォート, カスタム操 作 基礎となる考え方は「継続渡しスタイル(CPS) 」 複雑だが、DSLとして柔軟な仕組み 32
  11. 非同期処理のためのコンピュテーション式 let getWebContentSafelyAsync (uri: Uri) : Task<string> = task {

    use client = new HttpClient() try let! content = client.GetStringAsync(uri) return content with | :? HttpRequestException as ex -> return sprintf "HTTPリクエストエラー: %s" ex.Message | ex -> return sprintf "予期せぬエラー: %s" ex.Message } 33
  12. 静的に解決される型パラメータ(SRTP) let inline double<'a when 'a:(member Double: unit -> 'a)>

    (x: 'a) = x.Double() 型パラメーター 'a をとる インライン関数 double の型は 'a -> 'a インスタンスメソッド Double() を持っている型しか 'a に渡せない 43
  13. SRTPでジェネリックな算術演算 module Number = let inline add x y =

    x + y // ^a -> ^b -> 'c when (^a or ^b) : (static member (+) : ^a * ^b -> 'c) module MyList = let inline sum xs = List.fold (+) LanguagePrimitives.GenericZero<_> xs;; // ^a list -> ^b // when (^b or ^a) : (static member (+) : ^b * ^a -> ^b) // and ^b: (static member Zero: ^b) 44
  14. 参考:さらにSRTPを駆使すると…… module Monad = type CMonad() = static member inline

    bind(f: ^a -> ^b list, m: ^a list) = List.collect f m static member inline bind(f: ^a -> ^b option, m: ^a option) = Option.bind f m // CMonadかカスタム型自身にbind静的メソッドがあれば、それを呼び出す static member inline bind_resolve< ^m, ^a, ^b, ^c when ^m :> CMonad and (^m or ^c): (static member bind: (^a -> ^b) * ^c -> ^b)> (f: ^a -> ^b, m: ^c) = ((^m or ^c): (static member bind: (^a -> ^b) * ^c -> ^b) (f, m)) let inline bind< ^a, ^b, ^c when (CMonad or ^c): (static member bind: (^a -> ^b) * ^c -> ^b)> (f: ^a -> ^b) (m: ^c) : ^b = CMonad.bind_resolve<CMonad, _, _, _> (f, m) let m1 = Some 1 |> Monad.bind (fun x -> Some (string x)) 46
  15. 参考:さらにSRTPを駆使すると…… FSharpPlus:高度抽象の実験場 Semigroup (+) x y Comonad extract x (=>>)

    s g | extend s g duplicate x Monoid zero (+) x y {Appends both monoids} Seq.sum x Functor map f x unzip x Contravariant contramap f x Applicative return x ( < * > ) f x map f x lift2 f x y Alternative empty ( < | > ) f x mfilter p x ZipApplicative pur x ( < . > ) f x map f x map2 f x y Monad return x ( > > = ) x f map f x join x Bifunctor bimap f g x first f x second f x Foldable toSeq x Bifoldable bifoldMap f g x bifold f g z x bifoldBack f g x z bisum x Traversable traverse f x sequence x Bitraversable bitraverse f x bisequence x Profunctor dimap f g x lmap f x rmap f x Category catId ( < < < ) f g ( > > > ) f g Arrow arr f arrFirst f g arrSecond f g (***) f g (&&&) f g 47