Hesaplama İfadeleri

F# içinde hesaplama ifadeleri, denetim akışı yapıları ve bağlamaları kullanılarak sıralan ve bir araya gelen hesaplamalar yazmak için kullanışlı bir söz dizimi sağlar. Hesaplama ifadesinin türüne bağlı olarak bunlar;ds, monoid'ler, dönüştürücüler ve uygulamalı functor'ları ifade etmek için bir yol olarak düşünebilirsiniz. Ancak, haskell'de do-notation gibi diğer dillerden farklı olarak, bunlar tek bir soyutlamaya bağlı değildir ve kullanışlı ve bağlama duyarlı bir söz dizimi gerçekleştirmek için makrolara veya diğer meta programlama biçimlerine güvenmez.

Genel Bakış

Hesaplamalar birçok farklı biçime sahip olabilir. Hesaplamanın en yaygın biçimi, kolay anlaşılır ve değiştirilen tek iş parçacıklı yürütmedir. Ancak, tüm hesaplama biçimleri tek iş parçacıklı yürütme kadar kolay değildir. Bazı örnekler:

  • Belirleyici olmayan hesaplamalar
  • Zaman uyumsuz hesaplamalar
  • Etkili hesaplamalar
  • Genlik hesaplamaları

Daha genel olarak, uygulamanın belirli kısımlarında gerçekleştirmeniz gereken bağlama duyarlı hesaplamalar vardır. Bağlama duyarlı kod yazmak zor olabilir çünkü soyutlamalar olmadan hesaplamaları bir bağlam dışında "sızdırabilir" ve bunu engellemez. Bu soyutlamalar genellikle kendiniz yazmak zordur ve bu nedenle F# bunu işlem ifadeleri olarak adlandırılan genelleştirilmiş bir yolla yapar.

Hesaplama ifadeleri bağlama duyarlı hesaplamaları kodlamak için tekdüz söz dizimi ve soyutlama modeli sağlar.

Her hesaplama ifadesi bir oluşturucu türü tarafından de desteklene. Oluşturucu türü, hesaplama ifadesi için kullanılabilen işlemleri tanımlar. Özel hesaplama ifadesi oluşturmayı gösteren YeniTür Hesaplama İfadesi Oluşturma'ya bakın.

Söz dizimi genel bakışı

Tüm hesaplama ifadeleri aşağıdaki biçime sahip olur:

builder-expr { cexper }

Bu formda, hesaplama ifadesini tanımlayan bir oluşturucu türünün adıdır ve builder-expr cexper hesaplama ifadesinin ifade gövdesidir. Örneğin, async hesaplama ifadesi kodu aşağıdaki gibi olabilir:

let fetchAndDownload url =
    async {
        let! data = downloadData url

        let processedData = processData data

        return processedData
    }

Önceki örnekte gösterildiği gibi hesaplama ifadesinde özel ve ek bir söz dizimi vardır. Aşağıdaki ifade formları hesaplama ifadeleriyle mümkündür:

expr { let! ... }
expr { do! ... }
expr { yield ... }
expr { yield! ... }
expr { return ... }
expr { return! ... }
expr { match! ... }

Bu anahtar sözcüklerin ve diğer standart F# anahtar sözcüklerinin her biri, yalnızca arka oluşturucu türünde tanımlanmışsa hesaplama ifadesinde kullanılabilir. Bunun tek istisnası, sonucun üzerinde bir desen eşleşmesi tarafından takip edilen kullanımı için tek özel match! let! durumdur.

Oluşturucu türü, hesaplama ifadesinin parçalarının birleştirileme yöntemini yöneten özel yöntemleri tanımlayan bir nesnedir; başka bir ifadeyle, yöntemleri hesaplama ifadesinin nasıl davranacağını kontrol ediyor. Oluşturucu sınıfını açıklamanın bir diğer yolu da döngüler ve bağlamalar gibi birçok F# yapısına yönelik işlemi özelleştirmenize olanak olduğunu söylemektir.

let!

anahtar let! sözcüğü, başka bir hesaplama ifadesine yapılan çağrının sonucundan bir ad bağlar:

let doThingsAsync url =
    async {
        let! data = getDataAsync url
        ...
    }

Çağrıyı bir hesaplama ifadesine ile let bağlarsanız, hesaplama ifadesinin sonucu elde olmaz. Bunun yerine, gerçekleşmemiş çağrının değerini bu hesaplama ifadesine bağlı hale geleceksiniz. Sonucu let! bağlamak için kullanın.

let! , oluşturucu Bind(x, f) türüne üye tarafından tanımlanır.

do!

anahtar sözcüğü, benzer bir tür (oluşturucuda üye tarafından do! unit tanımlanır) döndüren bir hesaplama Zero ifadesini çağırmaya göredir:

let doThingsAsync data url =
    async {
        do! submitData data url
        ...
    }

Zaman uyumsuz iş akışı içinbu tür Async<unit> olur. Diğer hesaplama ifadeleri için türü büyük olasılıkla CExpType<unit> olur.

do! , oluşturucu türü Bind(x, f) üzerinde üye tarafından tanımlanır ve burada bir f unit üretir.

yield

anahtar yield sözcüğü, hesaplama ifadesinde bir değer döndürerek bir olarak kullanılamalarını IEnumerable<T> sağlar:

let squares =
    seq {
        for i in 1..10 do
            yield i * i
    }

for sq in squares do
    printfn $"%d{sq}"

Çoğu durumda, çağrıyı yapanlar tarafından atlanabilir. Atlanın en yaygın yolu yield -> işleciyledir:

let squares =
    seq {
        for i in 1..10 -> i * i
    }

for sq in squares do
    printfn $"%d{sq}"

Birçok farklı değer getireb daha karmaşık ifadeler için ve belki de koşullu olarak anahtar sözcüğünü yoksayarak şunları yapabiliriz:

let weekdays includeWeekend =
    seq {
        "Monday"
        "Tuesday"
        "Wednesday"
        "Thursday"
        "Friday"
        if includeWeekend then
            "Saturday"
            "Sunday"
    }

C# içinde yield anahtar sözcüğündeolduğu gibi, hesaplama ifadesinde her öğe yinelenmiş olarak geri döner.

yield , oluşturucu türü Yield(x) üzerinde üye tarafından tanımlanır; x burada, geri verilen öğedir.

yield!

anahtar yield! sözcüğü, bir hesaplama ifadesinde yer alan değer koleksiyonunu düze eklemek için kullanılır:

let squares =
    seq {
        for i in 1..3 -> i * i
    }

let cubes =
    seq {
        for i in 1..3 -> i * i * i
    }

let squaresAndCubes =
    seq {
        yield! squares
        yield! cubes
    }

printfn $"{squaresAndCubes}"  // Prints - 1; 4; 9; 1; 8; 27

Değerlendirince, tarafından çağrılan hesaplama ifadesi öğelerinin tek tek sonuç elde ederek sonucu yield! düzletir.

yield! , oluşturucu türü YieldFrom(x) üyesi tarafından tanımlanır; burada x değer koleksiyonudur.

'den yield yield! farklı olarak açıkça belirtilmelidir. Bu davranışı hesaplama ifadelerinde örtülü değildir.

return

anahtar return sözcüğü, hesaplama ifadesine karşılık gelen türe bir değer sarmalar. kullanan hesaplama ifadeleri yield dışında, bir hesaplama ifadesini "tamamlamak" için kullanılır:

let req = // 'req' is of type 'Async<data>'
    async {
        let! data = fetch url
        return data
    }

// 'result' is of type 'data'
let result = Async.RunSynchronously req

return , oluşturucu türü Return(x) üzerinde üye tarafından tanımlanır; burada x sarmalan öğedir.

return!

anahtar sözcüğü bir hesaplama ifadesinin değerini hayata alır ve hesaplama ifadesine karşılık gelen return! türle sonuçlandırarak sarmalar:

let req = // 'req' is of type 'Async<data>'
    async {
        return! fetch url
    }

// 'result' is of type 'data'
let result = Async.RunSynchronously req

return! , oluşturucu türü ReturnFrom(x) üyesi tarafından tanımlanır; burada başka x bir hesaplama ifadesidir.

match!

anahtar match! sözcüğü, başka bir hesaplama ifadesine yapılan bir çağrının satır içi olarak ve desen eşleşmesinde elde edilen sonucu sağlar:

let doThingsAsync url =
    async {
        match! callService url with
        | Some data -> ...
        | None -> ...
    }

ile bir hesaplama ifadesi match! çağrılırken, gibi bir çağrının sonucu elde let! olur. Bu genellikle sonucun isteğe bağlı olduğu bir hesaplama ifadesi çağrılır.

Yerleşik hesaplama ifadeleri

F# çekirdek kitaplığı dört yerleşik hesaplama ifadesi tanımlar: Sıra İfadeleri,Zaman uyumsuz ifadeler, Görev ifadelerive Sorgu İfadeleri.

Yeni Tür Hesaplama İfadesi Oluşturma

Bir oluşturucu sınıfı oluşturarak ve sınıfta belirli özel yöntemler tanımlayarak kendi hesaplama ifadelerinizi tanımlayabilirsiniz. Builder sınıfı isteğe bağlı olarak yöntemleri aşağıdaki tabloda listelenmiş şekilde tanımlayabilir.

Aşağıdaki tabloda, bir iş akışı oluşturucu sınıfında kullanılmaktadır yöntemleri açık almaktadır.

Yöntem Tipik imzalar Açıklama
Bind M<'T> * ('T -> M<'U>) -> M<'U> Hesaplama let! do! ifadelerinde ve için çağrılır.
Delay (unit -> M<'T>) -> Delayed<'T> Hesaplama ifadesini işlev olarak sarmalar. Delayed<'T> , yaygın olarak veya kullanılan M<'T> herhangi bir unit -> M<'T> tür olabilir. Varsayılan uygulama bir M<'T> döndürür.
Return 'T -> M<'T> Hesaplama return ifadelerinde için çağrılır.
ReturnFrom M<'T> -> M<'T> Hesaplama return! ifadelerinde için çağrılır.
Run Delayed<'T> -> M<'T> veya

M<'T> -> 'T
Bir hesaplama ifadesi yürütür.
Combine M<'T> * Delayed<'T> -> M<'T> veya

M<unit> * M<'T> -> M<'T>
Hesaplama ifadelerinde sequencing için çağrılır.
For seq<'T> * ('T -> M<'U>) -> M<'U> veya

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>
Hesaplama for...do ifadelerinde ifadeler için çağrılır.
TryFinally Delayed<'T> * (unit -> unit) -> M<'T> Hesaplama try...finally ifadelerinde ifadeler için çağrılır.
TryWith Delayed<'T> * (exn -> M<'T>) -> M<'T> Hesaplama try...with ifadelerinde ifadeler için çağrılır.
Using 'T * ('T -> M<'U>) -> M<'U> when 'T :> IDisposable Hesaplama use ifadelerinde bağlamalar için çağrılır.
While (unit -> bool) * Delayed<'T> -> M<'T>veya

(unit -> bool) * Delayed<unit> -> M<unit>
Hesaplama while...do ifadelerinde ifadeler için çağrılır.
Yield 'T -> M<'T> Hesaplama yield ifadelerinde ifadeler için çağrılır.
YieldFrom M<'T> -> M<'T> Hesaplama yield! ifadelerinde ifadeler için çağrılır.
Zero unit -> M<'T> Hesaplama else ifadelerinde if...then ifadelerin boş dalları için çağrılır.
Quote Quotations.Expr<'T> -> Quotations.Expr<'T> Hesaplama ifadesinin üyeye tırnak olarak Run geçir olduğunu gösterir. Bir hesaplamanın tüm örneklerini tırnak içine çevirir.

Oluşturucu sınıfındaki yöntemlerin çoğu, genellikle birleştirilmiş hesaplamaların türünü (örneğin, zaman uyumsuz ifadeler ve sıra iş akışları için) karakterize eden ayrı tanımlanmış bir tür olan bir yapıyı kullanır ve M<'T> Async<'T> geri Seq<'T> döner. Bu yöntemlerin imzaları, bir yapıdan döndürülen iş akışı nesnesinin bir sonrakine geçirilene kadar birleştir ve iç içe yerleştirmelerini sağlar.

Birçok işlev, sonucu bağımsız Delay değişken olarak kullanır: Run , , , ve While TryWith TryFinally Combine . türü, Delayed<'T> dönüş t t Delay değeridir ve sonuç olarak bu işlevlerin parametresidir. Delayed<'T> , ile ilgili olması gerek bırakmayan rastgele bir tür M<'T> olabilir; yaygın olarak M<'T> veya (unit -> M<'T>) kullanılır. Varsayılan uygulama şu M<'T> şekildedir: . Daha ayrıntılı bir görünüm için buraya bakın.

Derleyici, bir hesaplama ifadesini ayrıştırarak önceki tablodaki yöntemleri ve hesaplama ifadesinde yer alan kodu kullanarak ifadeyi bir dizi iç içe işlev çağrısına dönüştürür. İç içe geçmiş ifade aşağıdaki formdadır:

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

Yukarıdaki kodda, Run ve çağrıları Delay Hesaplama ifadesi Oluşturucu sınıfında tanımlanmamışsa atlanır. Burada olarak belirtilen hesaplama ifadesinin gövdesi, {| cexpr |} Aşağıdaki tabloda açıklanan Çeviriler tarafından Oluşturucu sınıfının yöntemlerini içeren çağrılara çevrilir. Hesaplama ifadesi {| cexpr |} expr bir F # ifadesi olduğu ve cexpr bir hesaplama ifadesi olduğu bu çevirilerine göre yinelemeli olarak tanımlanır.

Expression Çeviri
{ 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(expr, (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 builder.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 -> System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exn).Throw())))
{ 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()

Önceki tabloda, other-expr tabloda başka bir şekilde listelenmeyen bir ifade açıklanmaktadır. Bir Oluşturucu sınıfının tüm yöntemleri uygulaması ve önceki tabloda listelenen tüm çevirileri desteklemesi gerekmez. Uygulanmayan yapılar, bu türün hesaplama ifadelerinde kullanılamaz. Örneğin, use Hesaplama ifadelerinizin anahtar sözcüğünü desteklemek istemiyorsanız, Use Oluşturucu sınıfınızdaki tanımını atlayabilirsiniz.

Aşağıdaki kod örneği, bir hesaplamayı tek seferde bir adım değerlendirilebilecek bir dizi adım olarak kapsülleyen bir hesaplama ifadesi gösterir. Ayrılmış bir birleşim türü OkOrException olan, ifadenin hata durumunu şu ana kadar değerlendirilen şekilde kodluyor. Bu kod, bazı Oluşturucu metotlarının ortak uygulamaları gibi hesaplama ifadelerinizi kullanabileceğiniz birkaç tipik deseni gösterir.

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

module Eventually =

    /// Bind a computation using 'func'.
    let rec bind func expr =
        match expr with
        | Done value -> func value
        | NotYetDone work -> NotYetDone (fun () -> bind func (work()))

    /// Return the final value
    let result value = Done value

    /// 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
                | Error exn -> result (Error 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 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
            | Error 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 | Error 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.
    /// This is boilerplate in terms of "tryFinally" and "Dispose".
    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 "Done 7"
comp |> step |> step |> step |> step

Hesaplama ifadesinde, ifadenin döndürdüğü temel bir tür vardır. Temel alınan tür, gerçekleştirilebilecek bir sonucu veya bir Gecikmeli hesaplamayı temsil edebilir veya bazı koleksiyon türleri arasında yineleme yapmak için bir yol sağlayabilir. Önceki örnekte, temel alınan tür idi Eventually<_> . Bir dizi ifadesi için, temel alınan tür olur System.Collections.Generic.IEnumerable<T> . Bir sorgu ifadesi için, temel alınan tür olur System.Linq.IQueryable . Zaman uyumsuz bir ifade için, temel alınan tür olur Async . AsyncNesnesi, sonucu hesaplamak için gerçekleştirilecek işi temsil eder. Örneğin, Async.RunSynchronously bir hesaplama yürütmek ve sonucu döndürmek için öğesini çağırın.

Özel İşlemler

Hesaplama ifadesinde özel bir işlem tanımlayabilir ve bir hesaplama ifadesinde bir işleç olarak özel bir işlem kullanabilirsiniz. Örneğin, sorgu ifadesine bir sorgu işleci ekleyebilirsiniz. Özel bir işlem tanımladığınızda, hesaplama ifadesinde yield ve yöntemleri tanımlamanız gerekir. Özel bir işlem tanımlamak için, bunu hesaplama ifadesi için bir Oluşturucu sınıfına koyun ve ardından öğesini uygulayın CustomOperationAttribute . Bu öznitelik bir dizeyi bir özel işlemde kullanılacak olan bir bağımsız değişken olarak alır. Bu ad, hesaplama ifadesinin açma küme ayracı başlangıcında kapsama girer. Bu nedenle, bu bloktaki özel bir işlemle aynı ada sahip tanımlayıcılar kullanmamanız gerekir. Örneğin, all sorgu ifadelerinde veya gibi tanımlayıcıların kullanılmasını önleyin last .

Mevcut oluşturucuları yeni özel Işlemlerle genişletme

Zaten bir Oluşturucu sınıfınız varsa, özel işlemleri bu Oluşturucu sınıfının dışından genişletilebilir. Uzantılar modüllerde bildirilmelidir. Ad alanları, aynı dosya ve türün tanımlandığı aynı ad alanı bildirim grubu dışında uzantı üyeleri içeremez.

Aşağıdaki örnek, varolan sınıfının uzantısını gösterir FSharp.Linq.QueryBuilder .

open System
open FSharp.Linq

type QueryBuilder with

    [<CustomOperation("existsNot")>]
    member _.ExistsNot (source: QuerySource<'T, 'Q>, predicate) =
        System.Linq.Enumerable.Any (source.Source, Func<_,_>(predicate)) |> not

Özel işlemler aşırı yüklenebilir. Daha fazla bilgi için bkz. F # RFC FS-1056-hesaplama ifadelerinde özel anahtar sözcüklerin aşırı yüklenmesine Izin ver.

Hesaplama deyimlerini verimli bir şekilde derleme

Yürütmeyi askıya alan F # hesaplama ifadeleri, sürdürülebilir Code adlı düşük düzeyli bir özelliğin dikkatli kullanımı aracılığıyla yüksek oranda verimli durum makinelerine derlenebilir. Sürdürülebilir kodu, F # RFC FS-1087 ' de belgelenmiştir ve görev ifadelerindekullanılır.

Zaman uyumlu olan F # hesaplama ifadeleri (yani yürütmeyi askıya alma), alternatif olarak, öznitelik dahil satır içi işlevler kullanılarak verimli durum makinelerine derlenebilir InlineIfLambda . Örnek, F # RFC FS-1098içinde verilmiştir.

Liste ifadelerine, dizi ifadelerine ve sıra ifadelerine, yüksek performanslı kodun oluşturulmasını sağlamak için F # derleyicisi tarafından özel bir işleme verilir.

Ayrıca bkz.