F #: Самый быстрый способ конвертировать seq <'A> в seq <' B> - PullRequest
2 голосов
/ 06 июня 2019

Я использую Мартена в качестве хранилища событий, в частности, для извлечения потока событий.

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()

stream означает IReadOnlyList<IEvent> и 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();
}

Я хотел бы преобразовать каждый IEvent в AccountEvents, если базовый тип свойства Data равен AccountEvents (если не элемент не получен в результирующей последовательности).

В C # я бы просто использовал ключевое слово as для достижения этого, но в F # я не уверен, какой самый быстрый F # -иш-способ (с точки зрения производительности), чтобы получить это.

Я закончилна следующий код:

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<AccountEvents> + для тех, которые IsSome можно сделатьвсе сразу.

Ответы [ 2 ]

6 голосов
/ 06 июня 2019

Функция Seq.choose, упомянутая в ответе rmunn, очень полезна для такой ситуации, но для этой конкретной ситуации я бы рекомендовал использовать встроенный метод .NET Enumerable.OfType<'T>, который делает именно то, что вы хотите и вероятно, довольно оптимизирован:

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>
3 голосов
/ 06 июня 2019

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>
...