F# sequence with at least one element


F# sequence with at least one element



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 ?


EventList of seq<Event>


EventList


seq<Event>


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?




2 Answers
2



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.



The actual sequence of events is wrapped inside an EventList discriminated union case.


EventList



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.


PublishedEvents


EventList



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


add


PublishedEvents


Seq.append


seq<'a>


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


PublishedEvents


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


List<'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")






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

api-platform.com Unable to generate an IRI for the item of type

How to set up datasource with Spring for HikariCP?

Display dokan vendor name on Woocommerce single product pages