Beginner in F# here
I want to create a type, which is a sequence of another concrete type (Event) with at least one element. Any other elements can be added anytime later. Normally in C# I would create a class with a private List<Event> and public methods.
But I want to do it with a functional approach and not imitate the C# approach. Or at least try.
My train of thought:
Let's create a type "of seq" and give it a constructor requiring instance of the Event type
type Event = Event of string
type PublishedEvents = EventList of seq<Event> with
static member create (event:Event) = EventList(Seq.singleton event)
Now let's add an "add" method for adding another optional Event instances
type PublishedEvents with
member this.add(event:Event) = Seq.append this [event]
But that doesn't work, F# complains that "this" is not compatible with seq<'a>.
So I tried this:
type PublishedEvents with
member this.add (event:Event) : PublishedEvents = EventList(Seq.append this [event])
Now it complains that "this" is not compatible with seq<Event>...which is confusing me now since few lines above it says EventList of seq<Event>
... so I guess I need to somehow convert EventList
back to seq<Event>
so I can then use Seq.append
?
let convertFunction (eventList:PublishedEvents) : seq<Event> = ???
But I have no idea how to do this.
Am I even going the right direction? Is it better for this to mimic a C# class with a backing field? Or am I missing something?
The actual sequence of events is wrapped inside an EventList
discriminated union case.
You can unwrap it and re-wrap it like this:
type PublishedEvents with
member this.add(event:Event) =
match this with
| EventList events -> Seq.append events [event] |> EventList
However, I have to question the value of creating this PublishedEvents
type in the first place, if it's just a single EventList
case containing a sequence that requires you to wrap and unwrap values repeatedly.
Also, please be aware that this add
method doesn't change the existing PublishedEvents
. It creates a new one with a new sequence of events, because of the way that Seq.append
works, because seq<'a>
is actually just F#'s name for System.Collections.Generic.IEnumerable<'a>)
.
Furthermore, your approach does not prevent creation of a non-empty event sequence. EventList
is a public constructor for PublishedEvents
so you can just write:
EventList []
A simple way to make the type system enforce a non-empty sequence is this:
type NonEmptySeq<'a> = { Head : 'a; Tail : seq<'a> } with
static member Create (x:'a) = { Head = x; Tail = [] }
member this.Add x = { this with Tail = Seq.append this.Tail [x] }
let a = NonEmptySeq.Create (Event "A")
let b = a.Add (Event "B")
But again, these sequences are immutable. You could do something similar with a C# List<'a>
if you need mutation. In F# it's called a ResizeArray<'a>
:
type NonEmptyResizeArray<'a> = { Head : 'a; Tail : ResizeArray<'a> } with
static member Create (x:'a) = { Head = x; Tail = ResizeArray [] }
member this.Add x = this.Tail.Add x
let a = NonEmptyResizeArray.Create (Event "A")
a.Add (Event "B")
I propose that you go even more functional and not create members for your types - have it done in your functions. For example this would achieve the same and I would argue it's more idiomatic F#:
type Event = Event of string
type PublishedEvents = EventList of Event * Event list
let create e = EventList (e,[])
let add (EventList(head,tail)) e = EventList(e,head::tail)
let convert (EventList(head,tail)) = head::tail |> Seq.ofList
let myNewList = create (Event "e1")
let myUpdatedList = add myNewList (Event "e2")
let sequence = convert myUpdatedList
val sequence : seq = [Event "e2"; Event "e1"]
On the other hand if your aim is to interop with C# your approach would be easier to consume on C# side.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With