クラスを使用した Functional Powershell は矛盾ではありません。それは保証します

おい、ハブル! 記事の翻訳をご紹介します 「クラスを備えた機能的な PowerShell。
それは撞着語ではないと約束します」
クリストファー・キューク著。

オブジェクト指向と関数型プログラミングのパラダイムは互いに対立しているように見えるかもしれませんが、Powershell ではどちらも同様にサポートされています。 関数型かどうかにかかわらず、ほとんどすべてのプログラミング言語には、拡張された名前と値のバインディングの機能があります。 クラスは、構造体やレコードと同様、アプローチの XNUMX つにすぎません。 クラスの使用を名前と値のバインディングに限定し、継承、ポリモーフィズム、可変性などの重いオブジェクト指向プログラミングの概念を避ければ、コードを複雑にすることなくその利点を活用できます。 さらに、不変の型変換メソッドを追加することで、クラスを使用して関数コードを強化できます。

カーストの魔法

カーストは、Powershell の最も強力な機能の XNUMX つです。 値をキャストするときは、環境によってアプリケーションに追加される暗黙的な初期化機能と検証機能に依存することになります。 たとえば、[xml] に文字列をキャストするだけで、パーサー コードを通じて文字列が実行され、完全な XML ツリーが生成されます。 同じ目的でコード内でクラスを使用できます。

キャストハッシュテーブル

コンストラクターがない場合は、クラス型にハッシュテーブルをキャストすることで、コンストラクターなしで続行できます。 このパターンを最大限に活用するには、検証属性を使用することを忘れないでください。 同時に、クラスの型付きプロパティを使用して、さらに詳細な初期化および検証ロジックを実行できます。

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index
}

[Cluster]@{
    Service       = "MyService"
    FlightingRing = "PROD"
    Region        = "EastUS"
    Index         = 2
}

さらに、キャストはクリーンな出力を得るのに役立ちます。 Format-Table に渡されたクラスター ハッシュテーブル配列の出力を、最初にこれらのハッシュテーブルをクラスにキャストした場合に得られる出力と比較してください。 クラスのプロパティは、常にそこで定義されている順序でリストされます。 結果に表示したくないすべてのプロパティの前に、hidden キーワードを追加することを忘れないでください。

クラスを使用した Functional Powershell は矛盾ではありません。それは保証します

意味のキャスト

XNUMX つの引数を持つコンストラクターがある場合、値をクラス型にキャストすると、その値がコンストラクターに渡され、そこでクラスのインスタンスを初期化できます。

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index

    Cluster([string] $id) {
        $this.Service, $this.FlightingRing, $this.Region, $this.Index = $id -split "-"
    }
}

[Cluster]"MyService-PROD-EastUS-2"

ラインにキャスト

[string] ToString() クラス メソッドをオーバーライドして、文字列補間の使用など、オブジェクトの文字列表現の背後にあるロジックを定義することもできます。

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index

    [string] ToString() {
        return $this.Service, $this.FlightingRing, $this.Region, $this.Index -join "-"
    }
}

$cluster = [Cluster]@{
    Service       = "MyService"
    FlightingRing = "PROD"
    Region        = "EastUS"
    Index         = 2
}

Write-Host "We just created a model for '$cluster'"

キャストシリアル化インスタンス

キャストにより安全な逆シリアル化が可能になります。 データがクラスターの仕様を満たしていない場合、以下の例は失敗します。

# Валидация сериализованных данных

[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"

関数コード内のカースト

関数型プログラムは、まずデータ構造を定義し、次に不変データ構造に対する一連の変換としてプログラムを実装します。 矛盾した印象にもかかわらず、クラスは型変換メソッドのおかげで関数コードを書くのに非常に役立ちます。

私が作成している Powershell は機能しますか?

C# または同様のバックグラウンドを持った多くの人が、C# に似た Powershell を作成しています。 これを行うことで、関数型プログラミングの概念の使用から離れ、Powershell でオブジェクト指向プログラミングに深く取り組むか、関数型プログラミングについてさらに学習することで恩恵を受ける可能性があります。

パイプライン (|)、Where-Object、ForEach-Object、Select-Object、Group-Object、Sort-Object などを使用した不変データの変換に大きく依存している場合は、より機能的なスタイルが得られ、Powershell を使用するとメリットが得られます。機能的なスタイルのクラス。

クラスの機能的使用

カーストは、代替構文を使用しますが、XNUMX つのドメイン間のマッピングにすぎません。 パイプラインでは、ForEach-Object を使用して値の配列をマップできます。

以下の例では、Datum がキャストされるたびに Node コンストラクターが実行されるため、かなりの量のコードを記述することができなくなります。 その結果、パイプラインは宣言的なデータのクエリと集計に重点を置き、クラスはデータの解析と検証を担当します。

# Пример комбинирования классов с конвейерами для separation of concerns в конвейерах

class Node {
    [ValidateLength(3, 7)]
    [string] $Name
    [ValidateSet("INT", "PPE", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope", "WestEurope")]
    [string] $Region
    Node([string] $Name) {
        $Name -match "([a-z]+)(INT|PPE|PROD)([a-z]+)"
        $_, $this.Service, $this.FlightingRing, $this.Region = $Matches
        $this.Name = $Name
    }
}

class Datum {
    [string] $Name
    [int] $Value
    [Node] $Computer
    [int] Severity() {
        $this.Name -match "[0-9]+$"
        return $Matches[0]
    }
}

Write-Host "Urgent Security Audit Issues:"
Import-Csv "./audit-results.csv" `
    | ForEach-Object {[Datum]$_} `
    | Where-Object Value -gt 0 `
    | Group-Object {$_.Severity()} `
    | Where-Object Name -lt 2 `
    | ForEach-Object Group `
    | ForEach-Object Computer `
    | Where-Object FlightingRing -eq "PROD" `
    | Sort-Object Name, Region -Unique

再利用のためのクラスのパッケージ化

見た目ほど良いものはありません

残念ながら、クラスは関数や変数と同じ方法でモジュールからエクスポートできません。 しかし、いくつかのコツがあります。 クラスがファイル ./my-classes.ps1 で定義されているとします。

  • クラスを使用してファイルをドットソースできます:。 ./my-classes.ps1。 これにより、現在のスコープで my-classes.ps1 が実行され、そこにあるファイルからすべてのクラスが定義されます。

  • すべてのカスタム API (コマンドレット) をエクスポートする Powershell モジュールを作成し、モジュール マニフェストで ScriptsToProcess = "./my-classes.ps1" 変数を設定すると、同じ結果が得られます。 ./my-classes.ps1 は次のように実行されます。あなたの環境。

どちらのオプションを選択する場合でも、Powershell の型システムは、異なる場所から読み込まれた同じ名前の型を解決できないことに注意してください。
同じプロパティを持つ XNUMX つの同一のクラスを別の場所からロードした場合でも、問題が発生する危険があります。

これからの道

型解決の問題を回避する最善の方法は、クラスをユーザーに公開しないことです。 ユーザーがクラス定義型をインポートすることを期待するのではなく、モジュールから関数をエクスポートすると、クラスに直接アクセスする必要がなくなります。 クラスターの場合、使いやすいパラメーター セットをサポートし、クラスターを返す New-Cluster 関数をエクスポートできます。

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index
}

function New-Cluster {
    [OutputType([Cluster])]
    Param(
        [Parameter(Mandatory, ParameterSetName = "Id", Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Id,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $Service,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $FlightingRing,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $Region,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [int] $Index
    )

    if ($Id) {
        $Service, $FlightingRing, $Region, $Index = $Id -split "-"
    }

    [Cluster]@{
        Service       = $Service
        FlightingRing = $FlightingRing
        Region        = $Region
        Index         = $Index
    }
}

Export-ModuleMember New-Cluster

他に何を読むべきか

授業について
防御的な PowerShell
PowerShell での関数型プログラミング

出所: habr.com

コメントを追加します