タイダログ

もっと怠けますか? (y/n)

PowerShell 片手に F# を始める

F# のアイコン

先日、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# のアイコン

画像は 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#' という名前は「ファーストチルドレン」という意味で、アイコンの水色と青色はそれぞれ綾波の髪と制服を表している。

決まりですね。綾波です。これは F# を始めるしかない!6

作業環境

PowerShell のあれは F# ではこうする

外国語を勉強する時、まずは母語で考えますよね。「日本語ではこう言うけど、英語ではなんて言うんだろう」みたいなことです。もちろん体系的に学ぶことも必要ですが、まずは自分の知っていることと結び付けるのがいいと思っています。

そういうわけで、「PowerShell ではこうするけど、F# ではどうやるんだろう」という風に調べたので備忘録として書いておきます。

「続きは、もう少し上手くなったら書く」(随時更新)

パイプライン

PowerShell

|

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

PowerShell

# Array
1..10

F#

// List
[1..10];;

// Array
[|1..10|];;

// Seq
{1..10};;

F# では配列的なものが何種類かあるようです。カッコで種類が変わるんですね。カッコいい。違いはまだよくわかっていません。List要素の追加要素への再代入ができなくて、Array はできて、Seq は頭がいいらしいです。この記事では List を使います。(2021/11/18 訂正)(2022/02/26 訂正)

2021/11/18 加筆ここから

Array も要素の追加はできないようで、できるのは要素への再代入でした。List, Array ともに、append 関数でリスト同士、あるいは配列同士をつなげることができるようです。List の場合は :: (cons) 演算子@ 演算子もあるんですね。

2021/11/18 加筆ここまで

目次に戻る

スクリプトブロック

PowerShell

{ 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

PowerShell

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-Objectmap 関数なんですね。

PowerShell$_ 自動変数を使って簡潔に書けるので楽です。一方 F# のラムダ式は複数のパラメータを持つ場合でも書きやすくていいと感じました。(fun x y -> x * y)xy の2つのパラメータを持つラムダ式です。(fun x y -> x * y) n という風に引数をひとつだけ渡すと、残りの引数を待っている状態のラムダ式が出来上がります(nx に渡っています)。その残りのひとつをパイプラインで渡すことで式の評価が始まるわけですね(たぶん)。

目次に戻る

Where-Object

PowerShell

1..10 | Where-Object { $_ % 2 -eq 0 }

F#

[1..10] |> List.filter (fun x -> x % 2 = 0);;

Where-Objectfilter 関数。filter という名前が処理の内容を的確に表していていいですね。

目次に戻る

関数+関数

PowerShell

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 >> penlet applePen x = apple >> pen とすると思うようにいきません。

一方で利用する時の引数の渡し方は同じのようです。

みんな大好き PPAP の続きはこちら↓

目次に戻る

変数への代入

PowerShell

$myEva = 0

F#

let myEva = 0;;

F# では、というか関数型では代入ではなく「束縛」というんですね。そして変数への再代入は原則できません。どうしても必要ならば mutable キーワードを付けます。

let myEva = 0;;
myEva <- 2;;
// この値は変更可能ではありません。というエラーが出る
// 他人のエヴァには乗れません!

let mutable myEva = 5;;

// 再代入
myEva <- 2;;
// できる。なんですって!?

目次に戻る

function の定義

PowerShell

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 の静的メソッドの呼び出し

PowerShell

[Math]::Sqrt(42)

F#

System.Math.Sqrt(42);;

// あるいは open する
open System;;
Math.Sqrt(42);;

目次に戻る

PSCustomObject

PowerShell

[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) さんに教えていただきました。ありがとうございました!

目次に戻る

外部スクリプトやモジュールの読み込み

PowerShell

# 外部スクリプトの読み込み
. .\script.ps1

# モジュールの読み込み
Import-Module .\module.psm1

F#

#load "script.fsx";;
open Script;;

open Script が必須です。open <ファイル名の拡張子を除いた部分の先頭大文字> の形式です。ファイル内で名前空間やモジュール名を付けている場合はそれを使うようです。

F# インタラクティブ (dotnet) リファレンス | Microsoft Docs#他のスクリプトの読み込み

目次に戻る


続きは鋭意製作中。

結び

覚えたい英単語と日本語をペアにしてノートに書くように、PowerShell と F# をペアで書いてみました。今後調べて分かったことは追々書いていきます。

参考

更新履歴

  • 1..10」 の ListArray について誤りを訂正。「要素の追加ができる」は誤りで、正しくは「要素への再代入ができる」でした。 (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)

  1. ではない。
  2. 拡張子は他にも '.fsi' や '.fsx' があるそうです。
  3. ではない。
  4. 正しくは、 #123 のように数字の前につけます。
  5. ではない。
  6. 始めるしかない。