다음을 통해 공유


활성 패턴

활성 패턴을 사용하면 입력 데이터를 세분화하는 명명된 파티션을 정의할 수 있으며, 이를 구별된 합집합과 마찬가지로 패턴 매칭 식에서 사용할 수 있습니다. 활성 패턴을 사용하여 각 파티션에 대해 사용자 지정된 방식으로 데이터를 분해할 수 있습니다.

통사론

// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression

// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression

// Partial active pattern definition.
// Can use FSharp.Core.option<_>, FSharp.Core.voption<_> or bool to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression

비고

이전 구문에서 식별자는인수로 표현되는 입력 데이터의 파티션에 대한 이름이거나, 즉 인수의 모든 값 집합 하위 집합에 대한 이름입니다. 활성 패턴 정의에는 최대 7개의 파티션이 있을 수 있습니다. 은 데이터를 분해할 형태를 나타냅니다. 활성 패턴 정의를 사용하여 인수로 지정된 값이 속한 명명된 파티션을 결정하는 규칙을 정의할 수 있습니다. (| 및 |) 기호는 바나나 클립이라고 하며, 이 유형의 let 바인딩에서 생성된 함수는 활성 인식기라고 합니다.

예를 들어, 인수가 있는 다음과 같은 활성 패턴을 고려해 보십시오.

let (|Even|Odd|) input = if input % 2 = 0 then Even else Odd

다음 예제와 같이 패턴 일치 식에서 활성 패턴을 사용할 수 있습니다.

let TestNumber input =
   match input with
   | Even -> printfn "%d is even" input
   | Odd -> printfn "%d is odd" input

TestNumber 7
TestNumber 11
TestNumber 32

이 프로그램의 출력은 다음과 같습니다.

7 is odd
11 is odd
32 is even

활성 패턴의 또 다른 사용은 동일한 기본 데이터에 다양한 가능한 표현이 있는 경우와 같은 여러 가지 방법으로 데이터 형식을 분해하는 것입니다. 예를 들어 Color 개체는 RGB 표현 또는 HSB 표현으로 분해될 수 있습니다.

open System.Drawing

let (|RGB|) (col : System.Drawing.Color) =
     ( col.R, col.G, col.B )

let (|HSB|) (col : System.Drawing.Color) =
   ( col.GetHue(), col.GetSaturation(), col.GetBrightness() )

let printRGB (col: System.Drawing.Color) =
   match col with
   | RGB(r, g, b) -> printfn " Red: %d Green: %d Blue: %d" r g b

let printHSB (col: System.Drawing.Color) =
   match col with
   | HSB(h, s, b) -> printfn " Hue: %f Saturation: %f Brightness: %f" h s b

let printAll col colorString =
  printfn "%s" colorString
  printRGB col
  printHSB col

printAll Color.Red "Red"
printAll Color.Black "Black"
printAll Color.White "White"
printAll Color.Gray "Gray"
printAll Color.BlanchedAlmond "BlanchedAlmond"

위의 프로그램의 출력은 다음과 같습니다.

Red
 Red: 255 Green: 0 Blue: 0
 Hue: 360.000000 Saturation: 1.000000 Brightness: 0.500000
Black
 Red: 0 Green: 0 Blue: 0
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.000000
White
 Red: 255 Green: 255 Blue: 255
 Hue: 0.000000 Saturation: 0.000000 Brightness: 1.000000
Gray
 Red: 128 Green: 128 Blue: 128
 Hue: 0.000000 Saturation: 0.000000 Brightness: 0.501961
BlanchedAlmond
 Red: 255 Green: 235 Blue: 205
 Hue: 36.000000 Saturation: 1.000000 Brightness: 0.901961

이러한 두 가지 방법으로 활성 패턴을 사용하면 데이터를 적절한 형식으로 분할 및 분해하고 계산에 가장 편리한 형식의 적절한 데이터에 대해 적절한 계산을 수행할 수 있습니다.

결과 패턴 일치 식을 사용하면 매우 읽기 쉬운 편리한 방식으로 데이터를 작성할 수 있으므로 잠재적으로 복잡한 분기 및 데이터 분석 코드가 크게 간소화됩니다.

부분 활성 패턴

경우에 따라 입력 공간의 일부만 분할해야 합니다. 이 경우 각각 일부 입력과 일치하지만 다른 입력과 일치하지 않는 부분 패턴 집합을 작성합니다. 항상 값을 생성하지 않는 활성 패턴은 부분 활성 패턴 으로 불리며, 이 패턴의 반환 값은 옵션 형식입니다. 부분 활성 패턴을 정의하려면 바나나 클립 내의 패턴 목록 끝에 와일드카드 문자(_)를 사용합니다. 다음 코드에서는 부분 활성 패턴의 사용을 보여 줍니다.

let (|Integer|_|) (str: string) =
   let mutable intvalue = 0
   if System.Int32.TryParse(str, &intvalue) then Some(intvalue)
   else None

let (|Float|_|) (str: string) =
   let mutable floatvalue = 0.0
   if System.Double.TryParse(str, &floatvalue) then Some(floatvalue)
   else None

let parseNumeric str =
   match str with
   | Integer i -> printfn "%d : Integer" i
   | Float f -> printfn "%f : Floating point" f
   | _ -> printfn "%s : Not matched." str

parseNumeric "1.1"
parseNumeric "0"
parseNumeric "0.0"
parseNumeric "10"
parseNumeric "Something else"

이전 예제의 출력은 다음과 같습니다.

1.100000 : Floating point
0 : Integer
0.000000 : Floating point
10 : Integer
Something else : Not matched.

부분 활성 패턴을 사용하는 경우 개별 선택이 서로 분리되거나 상호 배타적일 수 있지만, 그렇지 않아도 됩니다. 다음 예제에서는 일부 숫자가 제곱과 큐브(예: 64)이므로 패턴 Square 및 패턴 큐브는 분리되지 않습니다. 다음 프로그램은 AND 패턴을 사용하여 정사각형 및 큐브 패턴을 결합합니다. 1,000 이하의 정수 중 완전제곱수이면서도 세제곱수인 것과, 세제곱수만인 것을 출력합니다.

let err = 1.e-10

let isNearlyIntegral (x:float) = abs (x - round(x)) < err

let (|Square|_|) (x : int) =
  if isNearlyIntegral (sqrt (float x)) then Some(x)
  else None

let (|Cube|_|) (x : int) =
  if isNearlyIntegral ((float x) ** ( 1.0 / 3.0)) then Some(x)
  else None

let findSquareCubes x =
   match x with
   | Cube x & Square _ -> printfn "%d is a cube and a square" x
   | Cube x -> printfn "%d is a cube" x
   | _ -> ()
   

[ 1 .. 1000 ] |> List.iter (fun elem -> findSquareCubes elem)

출력은 다음과 같습니다.

1 is a cube and a square
8 is a cube
27 is a cube
64 is a cube and a square
125 is a cube
216 is a cube
343 is a cube
512 is a cube
729 is a cube and a square
1000 is a cube

매개 변수가 있는 활성 패턴

활성 패턴은 항상 일치하는 항목에 대해 하나 이상의 인수를 사용하지만 추가 인수도 사용할 수 있습니다. 이 경우 매개 변수가 있는 활성 패턴에 이름이 적용됩니다. 추가 인수를 사용하면 일반 패턴을 특수화할 수 있습니다. 예를 들어, 정규식을 사용하여 문자열을 구문 분석하는 활성 패턴은 종종 이전 코드 예제에서 정의된 부분 활성 패턴 Integer을 사용하는 다음 코드에서 볼 수 있듯이, 정규식을 추가 매개변수로 포함합니다. 이 예제에서는 다양한 날짜 형식에 정규식을 사용하는 문자열을 지정하여 일반 ParseRegex 활성 패턴을 사용자 지정합니다. 정수 활성 패턴은 일치하는 문자열을 DateTime 생성자에 전달할 수 있는 정수로 변환하는 데 사용됩니다.

open System.Text.RegularExpressions

// ParseRegex parses a regular expression and returns a list of the strings that match each group in
// the regular expression.
// List.tail is called to eliminate the first element in the list, which is the full matched expression,
// since only the matches for each group are wanted.
let (|ParseRegex|_|) regex str =
   let m = Regex(regex).Match(str)
   if m.Success
   then Some (List.tail [ for x in m.Groups -> x.Value ])
   else None

// Three different date formats are demonstrated here. The first matches two-
// digit dates and the second matches full dates. This code assumes that if a two-digit
// date is provided, it is an abbreviation, not a year in the first century.
let parseDate str =
   match str with
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{1,2})$" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y + 2000, m, d)
   | ParseRegex "(\d{1,2})/(\d{1,2})/(\d{3,4})" [Integer m; Integer d; Integer y]
          -> new System.DateTime(y, m, d)
   | ParseRegex "(\d{1,4})-(\d{1,2})-(\d{1,2})" [Integer y; Integer m; Integer d]
          -> new System.DateTime(y, m, d)
   | _ -> new System.DateTime()

let dt1 = parseDate "12/22/08"
let dt2 = parseDate "1/1/2009"
let dt3 = parseDate "2008-1-15"
let dt4 = parseDate "1995-12-28"

printfn "%s %s %s %s" (dt1.ToString()) (dt2.ToString()) (dt3.ToString()) (dt4.ToString())

이전 코드의 출력은 다음과 같습니다.

12/22/2008 12:00:00 AM 1/1/2009 12:00:00 AM 1/15/2008 12:00:00 AM 12/28/1995 12:00:00 AM

활성 패턴은 패턴 일치 식으로만 제한되지 않으며 let-bindings에서도 사용할 수 있습니다.

let (|Default|) onNone value =
    match value with
    | None -> onNone
    | Some e -> e

let greet (Default "random citizen" name) =
    printfn "Hello, %s!" name

greet None
greet (Some "George")

이전 코드의 출력은 다음과 같습니다.

Hello, random citizen!
Hello, George!

그러나 단일 사례 활성 패턴만 매개 변수화할 수 있습니다.

// A single-case partial active pattern can be parameterized
let (| Foo|_|) s x = if x = s then Some Foo else None
// A multi-case active patterns cannot be parameterized
// let (| Even|Odd|Special |) (s: int) (x: int) = if x = s then Special elif x % 2 = 0 then Even else Odd

부분 활성 패턴의 반환 형식

부분 활성 패턴은 일치 항목을 나타내기 위해 Some ()를 반환하고, 그렇지 않으면 None을 반환한다.

이 경기를 고려하십시오.

match key with
| CaseInsensitive "foo" -> ...
| CaseInsensitive "bar" -> ...

부분 활성 패턴은 다음과 같습니다.

let (|CaseInsensitive|_|) (pattern: string) (value: string) =
    if String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase) then
        Some ()
    else
        None

F# 9부터 이러한 패턴은 bool반환할 수도 있습니다.

let (|CaseInsensitive|_|) (pattern: string) (value: string) =
    String.Equals(value, pattern, StringComparison.OrdinalIgnoreCase)

부분 활성 패턴에 대한 구조체 표현

기본적으로, 부분 활성 패턴이 option을 반환하면, 성공적인 매치가 이루어질 때 Some 값에 대한 할당이 포함됩니다. 이를 방지하기 위해, Struct 특성을 사용하여 값 옵션을 반환 값으로 사용할 수 있습니다.

open System

[<return: Struct>]
let (|Int|_|) str =
   match Int32.TryParse(str) with
   | (true, n) -> ValueSome n
   | _ -> ValueNone

구조체 반환의 사용은 단순히 반환 형식을 ValueOption변경에서 유추되지 않으므로 특성을 지정해야 합니다. 자세한 내용은 RFC FS-1039참조하세요.

참조