Reputation: 8997
I am using Marten as an Event Store, in particular to fetch a stream of events.
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
being IReadOnlyList<IEvent>
and IEvent
defined as:
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();
}
I would like to convert each IEvent
to AccountEvents
, if the underlying type of the Data
property is AccountEvents
(if not the item is not yielded in the resulting sequence).
In C# I would simply use the keyword as
to achieve that, but in F# I am not sure what is the fastest F#-ish way(in terms of performance) to get that.
I ended up on the following code:
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)
But this seems quite "expensive", and I an wondering whether the step of converting .Data
to the Option<AccountEvents>
+ filter the ones that IsSome
can be done all at once.
Upvotes: 2
Views: 108
Reputation: 10624
The Seq.choose
function mentioned in rmunn's answer is very useful to know for this kind of situation, but for this exact situation I would recommend using the built in .NET method Enumerable.OfType<'T>
, which does exactly what you want and is probably quite optimised:
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>
Upvotes: 6
Reputation: 36708
Seq.choose
is the function you've been looking for. You give it a function that takes an 'A
and returns a 'B option
, and it yields the 'B
value of the ones that were Some
. For your usage scenario, it would look like this:
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>
Upvotes: 3