先日、F# を始めました。「PowerShell のあれは F# ではどうやるんだろう」という風に調べながら書いたので、わかったことをまとめておきます。随時更新します。
F# を始めた理由
- 新しいことを始めたかったから
- 関数型プログラミングに興味があったから
- .NET 上で動くから
- パイプラインが使えるから
- アイコンが綺麗だから
生徒に勉強せい勉強せいと言っておきながら自分が勉強しないのはいけないと思い、かねてより興味があった関数型プログラミングを始めることにしました。言語は、すでにパソコンに入っていた F# を選びました。.NET 上で動くので PowerShell の経験を活かせますし。
関数と聞くとExcel関数を思い浮かべるのではないでしょうか。カッコの中にカッコを入れて、さらにカッコをカッコするとわけわからんですよね。それに、思考と関数の順番が逆なのがしんどいです。先に処理する関数を内側に、後に処理する関数を外側に書き、読むときは内側から外側へ向かっていくという、あれです。
たとえばこの数式。検索して、置換して、スペースを削除して、数値化します。
=VALUE(TRIM(SUBSTITUTE(VLOOKUP(A1,$D$2:$G$100,2,FALSE),"'","")))
F# も関数がメインですが、パイプラインという機能を使って括弧迷宮からの脱出を図っています。先ほどのExcel の数式を例にするとこんなイメージです。
=VLOOKUP(A1,$D$2:$G$100,2,FALSE) |> SUBSTITUTE(,"'","") |> TRIM() |> VALUE()
検索して、置換して、スペースを削除して、数値化。順番どおりです。パイプラインが使えることも PowerShell との共通点となり、ますます入門のハードルが下がったのでした。
あとアイコンが青系統の色の組み合わせで綺麗。この感じ好き。スマホの壁紙と待受とカバーも青系統です。
画像は F# Software Foundation のサイトより
https://fsharp.org/
そういえばこの色合い、どこかで見たような……水色と青色……水色の髪…青いスカート……綾波?
画像は EVANGELION STORE ONLINE さんのツイッターより
https://twitter.com/eva_store/status/1316682824468983810
綾波じゃないか!
ということは 'F#' の 'F' は 'First children' の 'F' なのでは?1 拡張子2の '.fs' は 'FirSt' ですね!3 '#' はシャープではなく番号記号またはナンバーサインといって数字に付ける記号なので4、やはり 'F' は数を表しているわけですよ!5
……いかんいかん興奮してしまいました。一旦落ち着いて話をまとめましょう。
'F#' という名前は「ファーストチルドレン」という意味で、アイコンの水色と青色はそれぞれ綾波の髪と制服を表している。
作業環境
- Windows 10 20H2 (OS Build 19042.1348)
- F# 6.0
- F# インタラクティブ 12.0.0.0
- .NET 6.0.100
- Windows PowerShell 5.1.19041.1320
- PowerShell 7.2.0
PowerShell のあれは F# ではこうする
外国語を勉強する時、まずは母語で考えますよね。「日本語ではこう言うけど、英語ではなんて言うんだろう」みたいなことです。もちろん体系的に学ぶことも必要ですが、まずは自分の知っていることと結び付けるのがいいと思っています。
そういうわけで、「PowerShell ではこうするけど、F# ではどうやるんだろう」という風に調べたので備忘録として書いておきます。
「続きは、もう少し上手くなったら書く」(随時更新)
パイプライン
|
F#
|>
実際の使用例は後ほど。
2022/02/26 加筆ここから
F# には ||>
なんてのもあるそうです。なんだこの「シン・エヴァンゲリオン劇場版:||」みたいな演算子、と思っていたら、2つの引数を渡すためのものでした。PowerShell でずっと欲しかったやつ!
ちなみに :||
はないようです。残念。全てのエヴァンゲリオンにさよならしたかったのになー(棒)。
2つの引数をタプルにして渡すようです。
(1, "a") ||> (fun x y -> printfn "%d %s" x y) // 1 a ("シン", "エヴァンゲリオン劇場版") ||> (fun x y -> printfn "%s・%s:||" x y) // シン・エヴァンゲリオン劇場版:||
タプルとして渡しますが、ラムダ式や関数で受け取るときにタプルとして扱うとエラーになります。
(1, "a") ||> (fun (x, y) -> printfn "%d %s" x y) // fs_with_ps.fsx(1,19): error FS0001: この式に必要な型は // 'int' // ですが、ここでは次の型が指定されています // ''a * 'b' // (1, "a") がタプルとして `x` に渡って、型が違うと怒られています。
ラムダ式の場合は |>
で書いてもあまり変わりませんが、
(1, "a") |> (fun (x, y) -> printfn "%d %s" x y) // 1 a
関数で部分適用をする場合にはメリットを感じました。サンプルは List (fsharp-core-docs) の fold 関数より。
(0, [1..5]) ||> List.fold (fun s v -> s + v * v) |> printfn "%d" // 55
これを |>
で書くと大変。
(0, [1..5]) |> (fun (x, y) -> List.fold (fun s v -> s + v * v) x y) |> printfn "%d" // 55
これならちょっと楽。
[1..5] |> List.fold (fun s v -> s + v * v) 0 |> printfn "%d" // 55
これはエラー。
(0, [1..5]) |> List.fold (fun s v -> s + v * v) |> printfn "%d" // fs_with_ps.fsx(3,12): error FS0001: 型 '('a list -> int * int list)' は、printf 形式の書式指定文字列の使用によって生じる型 byte,int16,int32,int64,sbyte,uint16,uint32,uint64,nativeint,unativeint のいずれとも互換性がありません // 型が違うと怒られています。
(0, [1..5]) |> List.fold (fun s v -> s + v * v) |> printfn "%A" // fs_with_ps.fsx(2,28): error FS0071: 既定の型 '(int * int list)' を型推論の変数に適用するときに、型の制約が一致しませんでした。 演算子 '+' をサポートする型が必要ですが、タプル型が指定されました 型の制約を増やしてください // (0, [1..5]) がタプルとして `s` に渡っていて、足し算ができないと怒られています。
3つの引数を渡す |||>
もあるようです。ひぇー。
("シ", "・エヴァ", "ゲリオ") |||> (fun x y z -> printfn "%sン%sン%sン" x y z) // シン・エヴァンゲリオン
2022/02/26 加筆ここまで
1..10
# Array 1..10
F#
// List [1..10];; // Array [|1..10|];; // Seq {1..10};;
F# では配列的なものが何種類かあるようです。カッコで種類が変わるんですね。カッコいい。違いはまだよくわかっていません。List
は要素の追加要素への再代入ができなくて、Array
はできて、Seq
は頭がいいらしいです。この記事では (2021/11/18 訂正)(2022/02/26 訂正)List
を使います。
2021/11/18 加筆ここから
Array
も要素の追加はできないようで、できるのは要素への再代入でした。List
, Array
ともに、append
関数でリスト同士、あるいは配列同士をつなげることができるようです。List
の場合は ::
(cons) 演算子や @
演算子もあるんですね。
- https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-listmodule.html#append
- https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-arraymodule.html#append
- https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/lists#operators-for-working-with-lists
2021/11/18 加筆ここまで
スクリプトブロック
{ param ( [int]$Age ) $Age -eq 14 } # `&` 演算子で実行する $reiAge = 14; &{ param ( [int]$Age ) $Age -eq 14 } $reiAge # True
F#
fun age -> age = 14;; // 引数を渡すだけで実行できる let reiAge = 14;; (fun age -> age = 14) reiAge;; // val it: bool = true
F# ではスクリプトブロックの代わりにラムダ式を使うようです。
ForEach-Object
1..10 | ForEach-Object { $_ * 2 } # 変数を2つ使う $n = 2 1..10 | ForEach-Object { $_ * $n }
F#
[1..10] |> List.map (fun x -> x * 2);; // パラメータ2つ let n = 2;; [1..10] |> List.map ((fun x y -> x * y) n);;
ForEach-Object
は map
関数なんですね。
PowerShell は $_
自動変数を使って簡潔に書けるので楽です。一方 F# のラムダ式は複数のパラメータを持つ場合でも書きやすくていいと感じました。(fun x y -> x * y)
は x
と y
の2つのパラメータを持つラムダ式です。(fun x y -> x * y) n
という風に引数をひとつだけ渡すと、残りの引数を待っている状態のラムダ式が出来上がります(n
は x
に渡っています)。その残りのひとつをパイプラインで渡すことで式の評価が始まるわけですね(たぶん)。
Where-Object
1..10 | Where-Object { $_ % 2 -eq 0 }
F#
[1..10] |> List.filter (fun x -> x % 2 = 0);;
Where-Object
は filter
関数。filter という名前が処理の内容を的確に表していていいですね。
関数+関数
function Pen { param ( [Parameter(ValueFromPipeline = $true)] [int] $X ) $X * 10 } function Apple { param ( [int] $X ) $X + 2 } # Apple した結果を Pen に渡す関数 function ApplePen { param ( [int] $X ) # パイプラインで戻り値を渡す Apple $X | Pen }
F#
let pen x = x * 10 let apple x = x + 2 // パイプライン let applePen x = apple x |> pen // 関数合成 let applePen = apple >> pen applePen 5 // (5 + 2) * 10 = 70
PowerShell の場合は、function の parameter に ValueFromPipeline = $true
を設定したうえで、function | funcion
をラップする function を書くことになりそうです。
F# では、パイプラインの他に関数合成が使えるようです。関数合成では、束縛する際にはパラメータを書かないようです。let applePen = apple >> pen
を let applePen x = apple >> pen
とすると思うようにいきません。
一方で利用する時の引数の渡し方は同じのようです。
みんな大好き PPAP の続きはこちら↓
F# の関数合成がわかった気がする。#fsharp
— タイダログ (@taidalog) 2022年1月25日
関数と関数をつなげて新しい関数を作る←うん
新しい関数に渡した引数は1つ目の関数に渡る←new
let pen x =
x * 10;;
let apple x =
x + 2;;
let applePen =
apple >> pen;;
applePen 5;;
// apple 5
// |> pen
// (5 + 2) * 10 = 70
変数への代入
$myEva = 0
F#
let myEva = 0;;
F# では、というか関数型では代入ではなく「束縛」というんですね。そして変数への再代入は原則できません。どうしても必要ならば mutable
キーワードを付けます。
let myEva = 0;; myEva <- 2;; // この値は変更可能ではありません。というエラーが出る // 他人のエヴァには乗れません! let mutable myEva = 5;; // 再代入 myEva <- 2;; // できる。なんですって!?
function の定義
function CanPilotEva { Param ( [int] $Age ) $Age -eq 14 } # 呼び出す $reiAge = 14 CanPilotEva -Age $reiAge
F#
let canPilotEva age = age = 14;; // val canPilotEva: age: int -> bool // 呼び出す let reiAge = 14;; canPilotEva reiAge;; // val it: bool = true
パラメータの型は自動で推測してくれます(型推論)。この場合、14
と比較するということは age
も整数だろうということで int
だと判断してくれています。int -> bool
は、「int
をひとつ受け取って bool
を返す関数」という意味です。
.NET の静的メソッドの呼び出し
[Math]::Sqrt(42)
F#
System.Math.Sqrt(42);; // あるいは open する open System;; Math.Sqrt(42);;
PSCustomObject
[PSCustomObject]@{ Name = "Rei" Eva = 0 }
$pilots = @( [PSCustomObject]@{ Name = "Rei"; Eva = 0 } [PSCustomObject]@{ Name = "Asuka"; Eva = 2 } [PSCustomObject]@{ Name = "Shinji"; Eva = 1 } [PSCustomObject]@{ Name = "Kaworu"; Eva = 4 } [PSCustomObject]@{ Name = "Mari"; Eva = 8 } ) $pilots | ForEach-Object { "$($_.Name) is the pilot of Evangelion Unit-$($_.Eva.ToString('00'))" } # Rei is the pilot of Evangelion Unit-00 # Asuka is the pilot of Evangelion Unit-02 # Shinji is the pilot of Evangelion Unit-01 # Kaworu is the pilot of Evangelion Unit-04 # Mari is the pilot of Evangelion Unit-08
F#
{| Name = "Rei" Eva = 0 |}
let pilots = [| {| Name = "Rei"; Eva = 0 |} {| Name = "Asuka"; Eva = 2 |} {| Name = "Shinji"; Eva = 1 |} {| Name = "Kaworu"; Eva = 4 |} {| Name = "Mari"; Eva = 8 |} |] pilots |> Array.map (fun x -> printfn "%s is the pilot of Evangelion Unit-%s" x.Name (System.String.Format("{0:00}", x.Eva))) // Rei is the pilot of Evangelion Unit-00 // Asuka is the pilot of Evangelion Unit-02 // Shinji is the pilot of Evangelion Unit-01 // Kaworu is the pilot of Evangelion Unit-04 // Mari is the pilot of Evangelion Unit-08
任意のプロパティを持つ即興の構造体的なものが使いたい。一番近いのは構造体なのかな?匿名レコードが私のイメージに一番近いようです。
Twitter で lu-anago (@lululu63499233) さんに教えていただきました。ありがとうございました!
外部スクリプトやモジュールの読み込み
# 外部スクリプトの読み込み . .\script.ps1 # モジュールの読み込み Import-Module .\module.psm1
F#
#load "script.fsx";; open Script;;
open Script
が必須です。open <ファイル名の拡張子を除いた部分の先頭大文字>
の形式です。ファイル内で名前空間やモジュール名を付けている場合はそれを使うようです。
F# インタラクティブ (dotnet) リファレンス | Microsoft Docs#他のスクリプトの読み込み
続きは鋭意製作中。
結び
覚えたい英単語と日本語をペアにしてノートに書くように、PowerShell と F# をペアで書いてみました。今後調べて分かったことは追々書いていきます。
参考
- F# 関連のドキュメント - 概要、チュートリアル、リファレンス。 | Microsoft Learn
- 関数 - F# | Microsoft Learn
- ラムダ式: fun キーワード - F# | Microsoft Learn
- F# - 値の束縛
- F# - 関数の基本
- F# - 式と文
- F# - コレクション型
- Midoliy |> F# - パイプラインと関数合成
- Midoliy |> F# - ラムダ式
- List (fsharp-core-docs)
- F# インタラクティブ (dotnet) リファレンス | Microsoft Learn
- 匿名のレコード - F# | Microsoft Learn
- Operators (fsharp-core-docs)
- List (fsharp-core-docs)
- F# Tutorial => Pipe Forward and Backward
更新履歴
- 「1..10」 の
List
とArray
について誤りを訂正。「要素の追加ができる」は誤りで、正しくは「要素への再代入ができる」でした。 (2021/11/28) - 各項目の最後に目次へのリンクを追加 (2022/02/05)
- 「関数+関数」を追加 (2022/02/05)
- 「PSCustomObject」の PSCustomObject の例が間違っていたので訂正 (2022/02/05)
- 綾波が14歳かどうかわからなくなってきたので、「PSCustomObject」の例から年齢を削除 (2022/02/05)
- 「PSCustomObject」に匿名レコードについて追加 (2022/02/05)
- 「パイプライン」に
||>
についての記述を追加 (2022/02/26) - 「1..10」のから「この記事では
List
を使います。」の記述を削除 (2022/02/26) - 目次の下に「こちらもどうぞ」を追加 (2023/04/24)