【问题标题】:F#: Fastest way to convert a seq<'A> to seq<'B>F#:将 seq<'A> 转换为 seq<'B> 的最快方法
【发布时间】:2019-06-06 10:10:00
【问题描述】:

我将 Marten 用作事件存储,特别是用于获取事件流。

type AccountCreation = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type AccountEvents =
    | AccountCreated of AccountCreation
    | AccountCredited of Transaction
    | AccountDebited of Transaction

let settings = {
    Host = "localhost"
    DatabaseName = "postgres"
    UserName = "root"
    Password = "root"
    EventTypes = eventTypes
}
use store = createDocumentStore settings
use session = store.LightweightSession()

let khalidId = Guid.NewGuid()
let billId = Guid.NewGuid()

let khalid = AccountEvents.AccountCreated({
    Owner = "Khalid Abuhakmeh"
    AccountId = khalidId
    StartingBalance = 1000m
    CreatedAt = DateTimeOffset.UtcNow
})

let bill = {
    Owner = "Bill Boga"
    AccountId = billId
    StartingBalance = 0m
    CreatedAt = DateTimeOffset.UtcNow
}

session.Events.Append(khalidId, khalid) |> ignore
session.Events.Append(billId, bill) |> ignore

session.SaveChanges()

let stream = session.Events.FetchStream()

streamIReadOnlyList&lt;IEvent&gt;IEvent 定义为:

public interface IEvent
{
    Guid Id { get; set; }
    int Version { get; set; }
    long Sequence { get; set; }
    object Data { get; }
    Guid StreamId { get; set; }
    string StreamKey { get; set; }
    DateTimeOffset Timestamp { get; set; }
    string TenantId { get; set; }
    void Apply<TAggregate>(TAggregate state, IAggregator<TAggregate> aggregator) where TAggregate : class, new();
}

如果Data 属性的基础类型是AccountEvents,我想将每个IEvent 转换为AccountEvents(如果不是,则该项目不会在结果序列中产生)。

在 C# 中,我会简单地使用关键字 as 来实现这一点,但在 F# 中,我不确定什么是最快的 F#-ish 方式(就性能而言)。

我最终得到了以下代码:

let seqCastOption<'T> sequence =
    sequence
    |> Seq.map(fun x ->
        match box x with
        | :? 'T as value -> Some value
        | _ -> None)

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> seqCastOption<'T>
    |> Seq.filter (fun x -> x.IsSome)
    |> Seq.map(fun x -> x.Value)

但这似乎相当“昂贵”,我想知道将.Data 转换为Option&lt;AccountEvents&gt; + 过滤IsSome 的步骤是否可以一次完成。

【问题讨论】:

  • 快速和seq (IEnumerable) 几乎是矛盾的。话虽如此,直接在 F# 中使用 LINQ 通常比使用 Seq 模块提供更好的性能。为了提高性能Nessos 流可能是一个有趣的选择。

标签: casting f# marten


【解决方案1】:

rmunn 的回答中提到的Seq.choose 函数对于了解这种情况非常有用,但对于这种确切情况,我建议使用内置的.NET 方法Enumerable.OfType&lt;'T&gt;,它完全符合您的要求并且是可能非常优化:

open System.Linq

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> Enumerable.OfType<'T>

【讨论】:

  • 不知道Enumerable.OfType&lt;'T&gt;。在这种情况下,这确实比Seq.choose 要好。这应该是公认的答案。
【解决方案2】:

Seq.choose 是您一直在寻找的功能。你给它一个接受'A并返回'B option的函数,它产生'B的值Some。对于您的使用场景,它看起来像这样:

let castOption<'T> x =
    match box x with
    | :? 'T as value -> Some value
    | _ -> None

let fetchStream<'T> (session: IDocumentSession) (id: Guid) =
    let stream = session.Events.FetchStream(id)
    stream
    |> Seq.map(fun x -> x.Data)
    |> Seq.choose castOption<'T>

【讨论】:

    猜你喜欢
    • 2012-10-11
    • 2020-07-16
    • 2014-08-25
    • 2011-11-06
    • 2019-03-03
    • 2021-01-30
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    相关资源
    最近更新 更多