The Ultimate Domain Language: Declarative Swift | by Manuel Meyer | Nov, 2022

declarative domain pattern discovery

photo by Suleman Hussain Feather unsplash

I’d like to introduce you to Declarative Swift – a coding style that allows us to efficiently and effectively create domain-driven code that is easy to verify. This type of writing code has proven to be faster than traditional coding, while it provides fewer places for bugs to hide.

Let’s start with a simple example: the todo list.

The following code block contains a datatype for todo items and todo lists:

struct TodoItem:Identifiable 
enum Change
case title (to:String)
case due (to:TodoDate)
case location(to:Location)
case finish
case unfinish

// members
let id : UUID
let title : String
let completed: Bool
let created : Date
let due : TodoDate
let location : Location

// initialisers
init(title:String) self.init(UUID(),title,false,.unknown, Date(),.unknown)
private
init(_ i:UUID,_ t:String,_ c:Bool,_ d:TodoDate,_ cd:Date,_ l:Location) id=i; title=t; completed=c; due=d; created=cd; location=l

func alter(_ c:Change...) -> Self c.reduce(self) $0.alter($1)
private
func alter(_ c:Change ) -> Self
switch c
case let .title(to:t): return Self(id,t ,completed,due,created,location)
case .finish : return Self(id,title,true ,due,created,location)
case .unfinish : return Self(id,title,false ,due,created,location)
case let .location(to:l): return Self(id,title,false ,due,created,l )
case let .due(to:.timeSpan(.start(.from(b),.to(e)))):
return e > b //check for timespans if dates are in correct order
? Self(id,title,completed,.timeSpan(.start(.from(b),.to(e))),created,location)
: self
case let .due(to:d): return Self(id,title,completed,d ,created,location)



struct TodoList: Identifiable
enum Change
case add (Add ); enum Add case item(TodoItem)
case remove(Remove); enum Remove case item(TodoItem)
case update(Update); enum Update case item(TodoItem)

// members
let id : UUID
let items: [TodoItem]

// initializers
init() self.init(UUID(),[])
private
init(_ i:UUID,_ its:[TodoItem]) id = i; items = its

func alter(_ c:Change...) -> Self c.reduce(self) $0.alter($1)
private
func alter(_ c:Change ) -> Self
switch c
case let .add (.item(i)): return Self(id,items + [i])
case let .remove(.item(i)): return Self(id,items - [i])
case let .update(.item(i)): return
items.contains(where: $0.id == i.id )
? self
.alter(
.remove(.item(i)),
.add (.item(i)))
: self


all member properties are defined let, Hence, these datatypes are actually immutable. To reflect the changes, we need to regenerate a new object from the previous state and present the changes. here it is done by calling alter Each call to the method changes results in a new object – with one or more change values.

In the following example, we create a TodoItem And then change the title to something more pressing:

let t0 = TodoItem(title:"Get Coffee")
let t1 = t0.alter(.title("Get Coffee — ASAP"))

The next call will complete one item:

let t1 = t0.alter(.finish)

While this will make it incomplete:

let t1 = t0.alter(.unfinish)

It’s trivial to change the title and eliminate/finish an item. Let’s look at some more interesting examples.

The following example code contains the declaration for TodoDate,
It might be unknownOne Dateor a TimeSpan,

a TimeSpan Can be defined as a start and end or duration.

enum TodoDate {
case unknown
case date(Date)
case timeSpan(TimeSpan)
var timeSpan:TimeSpan? { switch self { case let .timeSpan

Leave a Reply