다음을 통해 공유


계산 식(F#)

F#의 계산 식에서 제공하는 구문을 사용하면 제어 흐름 구문과 바인딩을 통해 순서 지정과 결합이 가능한 계산을 쉽게 작성할 수 있습니다.함수형 프로그램의 데이터, 컨트롤 및 파생 작업을 관리하는 데 사용할 수 있는 함수형 프로그래밍 기능인 모나드에 대한 구문에도 이러한 계산 식을 편리하게 사용할 수 있습니다.

기본 제공 워크플로

비동기 워크플로 및 쿼리 식은 마찬가지로 시퀀스 식은 계산 식의 예입니다.자세한 내용은 시퀀스, 비동기 워크플로, 및 쿼리 식.

몇몇 기능은 시퀀스 식과 비동기 워크플로에 모두 공통으로 사용되며 여기에는 계산 식의 기본 구문이 적용됩니다.

builder-name { expression }

위 구문에 주어진 식은 builder-name으로 지정된 형식의 계산 식입니다.계산 식은 seq 또는 async 같은 기본 제공 워크플로가 될 수도 있고 사용자가 정의한 워크플로가 될 수도 있습니다.builder-name은 작성기 형식이라고 하는 특수한 형식의 인스턴스에 대한 식별자입니다.작성기 형식은 계산 식의 각 부분이 결합되는 방식을 관장하는 특수 메서드를 정의하는 클래스 형식, 즉 식의 실행 방식을 제어하는 코드입니다.또는 루프나 바인딩 등과 같은 여러 가지 F# 구문의 연산을 사용자 지정하는 데 사용할 수 있는 것으로 작성기 형식을 이해할 수도 있습니다.

계산 식에서 몇몇 공용 언어 구문에 대해 사용할 수 있는 형식에는 두 가지가 있습니다.특정 키워드에 !(느낌표) 접미사를 사용하여 variant 구문을 호출할 수 있습니다.예를 들면 let!, do! 등과 같은 형식입니다.이와 같은 특수 형식을 사용하면 작성기 클래스에 정의되어 있는 특정 함수로 해당 연산의 일반적인 기본 동작을 바꿀 수 있습니다.이러한 형식은 시퀀스 식에 사용되는 yield 키워드의 yield! 형식과 비슷합니다.자세한 내용은 시퀀스를 참조하십시오.

계산 식의 새 형식 만들기

작성기 클래스를 만들고 클래스에 대한 특수 메서드를 정의하여 고유한 계산 식의 특성을 정의할 수 있습니다.필요한 경우 작성기 클래스에서 다음 표에 나와 있는 것과 같은 메서드를 정의할 수 있습니다.

다음 표에서는 워크플로 작성기 클래스에 사용할 수 있는 메서드를 설명합니다.

메서드

형식 시그니처

설명

Bind

M<'T> * ('T -> M<'U>) -> M<'U>

계산 식의 let! 및 do!에 대해 호출됩니다.

Delay

(unit -> M<'T>) -> M<'T>

계산 식을 함수로 래핑합니다.

Return

'T -> M<'T>

계산 식의 return에 대해 호출됩니다.

ReturnFrom

M<'T> -> M<'T>

계산 식의 return!에 대해 호출됩니다.

Run

M<'T> -> M<'T> 또는

M<'T> -> 'T

계산 식을 실행합니다.

Combine

M<'T> * M<'T> -> M<'T> 또는

M<unit> * M<'T> -> M<'T>

계산 식의 시퀀스에 대해 호출됩니다.

For

seq<'T> * ('T -> M<'U>) -> M<'U> 또는

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>

계산 식의 for...do 식에 대해 호출됩니다.

TryFinally

M<'T> * (unit -> unit) -> M<'T>

계산 식의 try...finally 식에 대해 호출됩니다.

TryWith

M<'T> * (exn -> M<'T>) -> M<'T>

계산 식의 try...with 식에 대해 호출됩니다.

Using

'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable

계산 식의 use 바인딩에 대해 호출됩니다.

While

(unit -> bool) * M<'T> -> M<'T>

계산 식의 while...do 식에 대해 호출됩니다.

Yield

'T -> M<'T>

계산 식의 yield 식에 대해 호출됩니다.

YieldFrom

M<'T> -> M<'T>

계산 식의 yield! 식에 대해 호출됩니다.

Zero

unit -> M<'T>

계산 식에서 if...then 식의 빈 else 분기에 대해 호출됩니다.

여러 작성기 클래스의 메서드를 사용 하 여 반환 된 M<'T> 일반적으로 예를 결합 하는 계산의 종류를 나타냅니다는 별도로 정의 된 형식인, 구조, Async<'T> 비동기 워크플로 및 Seq<'T> 워크플로 시퀀스에 대 한.이러한 메서드의 시그니처를 사용하면 메서드를 서로 결합하고 중첩할 수 있으므로 한 구문으로부터 반환된 워크플로 개체를 다음 구문에 전달할 수 있습니다.컴파일러는 계산 식을 구문 분석할 때 위 표에 나와 있는 메서드와 계산 식의 코드를 사용하여 해당 식을 일련의 중첩된 함수 호출로 변환합니다.

중첩된 식은 다음과 같은 형식입니다.

builder.Run(builder.Delay(fun () -> {| cexpr |}))

위 코드에서 Run 및 Delay에 대한 호출은 계산 식 작성기 클래스에 정의되어 있지 않은 경우 생략됩니다.계산 식의 본문(여기에서는 {| cexpr |}로 표시됨)은 다음 표에 설명된 변환에 의해 작성기 클래스의 메서드가 포함된 호출로 변환됩니다.계산 식 {| cexpr |}은 expr이 F# 식이고 cexpr이 계산 식인 이러한 변환에 따라 재귀적으로 정의됩니다.

변환

{| let binding in cexpr |}

let binding in {| cexpr |}

{| let! pattern = expr in cexpr |}

builder.Bind(expr, (fun pattern -> {| cexpr |}))

{| do! expr in cexpr |}

builder.Bind(expr1, (fun () -> {| cexpr |}))

{| yield expr |}

builder.Yield(expr)

{| yield! expr |}

builder.YieldFrom(expr)

{| return expr |}

builder.Return(expr)

{| return! expr |}

builder.ReturnFrom(expr)

{| use pattern = expr in cexpr |}

builder.Using(expr, (fun pattern -> {| cexpr |}))

{| use! value = expr in cexpr |}

builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {| cexpr |}))))

{| if expr then cexpr0 |}

if expr then {| cexpr0 |} else binder.Zero()

{| if expr then cexpr0 else cexpr1 |}

if expr then {| cexpr0 |} else {| cexpr1 |}

{| match expr with | pattern_i -> cexpr_i |}

match expr with | pattern_i -> {| cexpr_i |}

{| for pattern in expr do cexpr |}

builder.For(enumeration, (fun pattern -> {| cexpr }|))

{| for identifier = expr1 to expr2 do cexpr |}

builder.For(enumeration, (fun identifier -> {| cexpr }|))

{| while expr do cexpr |}

builder.While(fun () -> expr), builder.Delay({|cexpr |})

{| try cexpr with | pattern_i -> expr_i |}

builder.TryWith(builder.Delay({| cexpr |}), (fun value -> match value with | pattern_i -> expr_i | exn -> reraise exn)))

{| try cexpr finally expr |}

builder.TryFinally(builder.Delay( {| cexpr |}), (fun () -> expr))

{| cexpr1; cexpr2 |}

builder.Combine({|cexpr1 |}, {| cexpr2 |})

{| other-expr; cexpr |}

expr; {| cexpr |}

{| other-expr |}

expr; builder.Zero()

위 표에서 other-expr은 그 외의 경우에 표에 나열되지 않는 식에 대해 설명합니다.작성기 클래스는 모든 메서드를 구현하고 위 표에 나열된 모든 변환을 지원할 필요가 없습니다.구현되지 않는 구문은 해당 형식의 계산 식에서 사용할 수 없습니다.예를 들어 계산 식에서 use 키워드를 지원하지 않으려는 경우 작성기 클래스에서 Use에 대한 정의를 생략할 수 있습니다.

다음 코드 예제에서는 한 번에 한 단계 일련의 단계 수를 계산 하는 계산을 캡슐화 하는 계산 식을 보여 줍니다.A union 형식의 discriminated OkOrException, 지금까지 평가 된 대로 식의 오류 상태를 인코딩합니다.이 코드와 같은 상용 구현 작성기 메서드 중 일부를 계산 식에서 사용할 수 있는 몇 가지 일반적인 패턴을 보여 줍니다.

// Computations that can be run step by step
type Eventually<'T> =
    | Done of 'T
    | NotYetDone of (unit -> Eventually<'T>)

module Eventually =
    // The bind for the computations. Append 'func' to the
    // computation.
    let rec bind func expr =
        match expr with
        | Done value -> NotYetDone (fun () -> func value)
        | NotYetDone work -> NotYetDone (fun () -> bind func (work()))

    // Return the final value wrapped in the Eventually type.
    let result value = Done value

    type OkOrException<'T> =
    | Ok of 'T
    | Exception of System.Exception

    // The catch for the computations. Stitch try/with throughout
    // the computation, and return the overall result as an OkOrException.
    let rec catch expr =
        match expr with
        | Done value -> result (Ok value)
        | NotYetDone work ->
            NotYetDone (fun () ->
            let res = try Ok(work()) with | exn -> Exception exn
            match res with
            | Ok cont -> catch cont // note, a tailcall
            | Exception exn -> result (Exception exn))

    // The delay operator.
    let delay func = NotYetDone (fun () -> func())

    // The stepping action for the computations.
    let step expr =
        match expr with
        | Done _ -> expr
        | NotYetDone func -> func ()

    // The rest of the operations are boilerplate.
    // The tryFinally operator.
    // This is boilerplate in terms of "result", "catch", and "bind".
    let tryFinally expr compensation =
        catch (expr)
        |> bind (fun res -> compensation();
                            match res with
                            | Ok value -> result value
                            | Exception exn -> raise exn)

    // The tryWith operator.
    // This is boilerplate in terms of "result", "catch", and "bind".
    let tryWith exn handler =
        catch exn
        |> bind (function Ok value -> result value | Exception exn -> handler exn)

    // The whileLoop operator.
    // This is boilerplate in terms of "result" and "bind".
    let rec whileLoop pred body =
        if pred() then body |> bind (fun _ -> whileLoop pred body)
        else result ()

    // The sequential composition operator.
    // This is boilerplate in terms of "result" and "bind".
    let combine expr1 expr2 =
        expr1 |> bind (fun () -> expr2)
    
    // The using operator.
    let using (resource: #System.IDisposable) func =
        tryFinally (func resource) (fun () -> resource.Dispose())

    // The forLoop operator.
    // This is boilerplate in terms of "catch", "result", and "bind".
    let forLoop (collection:seq<_>) func =
        let ie = collection.GetEnumerator()
        tryFinally (whileLoop (fun () -> ie.MoveNext())
                     (delay (fun () -> let value = ie.Current in func value)))
                     (fun () -> ie.Dispose())

// The builder class.
type EventuallyBuilder() =
    member x.Bind(comp, func) = Eventually.bind func comp
    member x.Return(value) = Eventually.result value
    member x.ReturnFrom(value) = value
    member x.Combine(expr1, expr2) = Eventually.combine expr1 expr2
    member x.Delay(func) = Eventually.delay func
    member x.Zero() = Eventually.result ()
    member x.TryWith(expr, handler) = Eventually.tryWith expr handler
    member x.TryFinally(expr, compensation) = Eventually.tryFinally expr compensation
    member x.For(coll:seq<_>, func) = Eventually.forLoop coll func
    member x.Using(resource, expr) = Eventually.using resource expr

let eventually = new EventuallyBuilder()
    
let comp =
    eventually { for x in 1 .. 2 do
                    printfn " x = %d" x
                 return 3 + 4 }

// Try the remaining lines in F# interactive to see how this 
// computation expression works in practice.
let step x = Eventually.step x


// returns "NotYetDone <closure>"
comp |> step

// prints "x = 1"
// returns "NotYetDone <closure>"
comp |> step |> step


// prints "x = 1"
// prints "x = 2"
// returns "NotYetDone <closure>"
comp |> step |> step |> step |> step |> step |> step



// prints "x = 1"
// prints "x = 2"
// returns "Done 7"
comp |> step |> step |> step |> step |> step |> step |> step |> step

계산 식 식을 반환 하는 내부 형식이 있습니다.내부 형식이 계산된 결과 또는 수행할 수 있는 지연 된 계산으로 나타낼 수 있습니다 또는 일종의 컬렉션을 통해 반복 하는 방법을 제공할 수 있습니다.앞의 예제에서는 내부 형식이 되었습니다 Eventually.대 한 시퀀스 식을 내부 형식인 IEnumerable<T>.쿼리 식에 대 한 기본 형식인 IQueryable<T>.비동기 워크플로 기본 형식인 비동기.Async 개체 결과 계산을 수행 하는 작업을 나타냅니다.예를 들어, 전화 Async.RunSynchronously 계산을 실행 하 고 결과 반환 합니다.

사용자 지정 작업

계산 식에 대해 사용자 지정 작업을 정의 하 고 사용자 지정 작업을 사용 하 여 계산 하는 식의 연산자로 수 있습니다.예를 들어, 쿼리 식의 쿼리 연산자를 포함할 수 있습니다.사용자 지정 작업을 정의 하는 경우 수익률을 정의 해야 하 고 계산 식에서 방법에 대 한.사용자 지정 작업을 정의 하는 계산 식 작성기 클래스에 배치 및 다음 적용을 CustomOperationAttribute.이 특성 이름을 사용자 지정 작업에 사용 되는 인수로 문자열을 사용 합니다.이 이름에서 맨 처음 여는 중괄호 계산 식의 범위로 함께 제공 됩니다.따라서 이름이 같은 사용자 지정 작업은이 블록에 있는 식별자를 사용 하면 안 됩니다.예를 들어, 같은 식별자를 사용 하지 않도록 all 또는 last 쿼리 식에서입니다.

참고 항목

참조

비동기 워크플로 (F#)

시퀀스 (F#)

쿼리 식 (F#)

기타 리소스

F# 언어 참조