계산 식(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 쿼리 식에서입니다.