Is This Programming Paradigm New? [closed]

preface: to define what a programming paradigm is in context of this post, I will use the definition used in wikipedia A programming paradigm is a relatively high-level way to conceptualize and structure the implementation of a computer program. In this post the Declarative Domain Paradigm (DDP) uses DSLs (based on type declarations) to define and implement behaviour, logic and relationships. I also want to clarify that the question is not, if this is a paradigm. It is. The question is, if this is new or if similar things have been publish before. In the last 5 years I have explored what I think is a new programming paradigm that I call "Declarative Domain Programming". It uses type declarations to model logic, behaviour and relationships. The language I am using is Swift, as it offers nested types, enums with associated values and type inference. Let us look at an example, a simple todo item. struct TodoItem { let title : String let state : State let dueDate : DueDate let location : Location let collaborators: [Collaborator] } extension TodoItem { enum State { case unfinished case inProgress case finished } } extension TodoItem { enum DueDate { case none case date(Date) } } enum Location { case unknown case coordinate(Coordinate) case address(Address) } struct Address { let street : String let city : String let country: String let zipCode: String } struct Coordinate { let latitude : Double let longitude: Double } struct Collaborator { init(name: String) { self.init(UUID(), name) } private init (_ i: UUID, _ n: String) { id = i name = n } let id : UUID let name: String } TodoItem has a title, a state (which can be unfinished, in progress or finished), a due date (which can be none or a given date), a location (which can be unknown, a coordinate of latitude and longitude or an address) and a list of collaborators. Now let us define TodoItem's Change DSL to change any of the attributes: struct TodoItem { enum Change { case setting(Setting); enum Setting { case title (to: String) case state (to: State) case dueDate (to: DueDate) case location(to: Location) } case adding(Adding); enum Adding { case collaborator(Collaborator) } case removing(Removing); enum Removing { case collaborator(Collaborator) } } // ... } Following commands can be encoded by this DSL: .setting(.title(to: )) .setting(.state(to: )) .setting(.dueDate(to: )) .setting(.location(to: )) .adding(.collaborator()) .removing(.collaborator()) Now we add a method that pattern-matches over these values and creates a new object with the reflected changes: struct TodoItem { //... init(_ t: String, _ s:State, _ d:DueDate, _ l:Location, _ c:[Collaborator]) { title = t state = s dueDate = d location = l collaborators = c } func alter(by c: Change) -> Self { switch c { case let .setting(.title(to: t)) : Self(t , state, dueDate, location, collaborators ) case let .setting(.state(to: s)) : Self(title, s , dueDate, location, collaborators ) case let .setting(.dueDate(to: d)) : Self(title, state, d , location, collaborators ) case let .setting(.location(to: l)) : Self(title, state, dueDate, l , collaborators ) case let .adding (.collaborator(c)): Self(title, state, dueDate, location, collaborators + [c] ) case let .removing(.collaborator(c)): Self(title, state, dueDate, location, collaborators.filter{$0.id != c.id}) } } } By switching over the Change value we decode the command and write associated values to local constants. We use these constants to overwrite an existing value when creating a new object. We can now use it like: var buyBeer = TodoItem(title: "Buy beer") let cal = Calendar.current let tomorrowNoon = cal.date(byAdding: .hour, value: 12, to: cal.date(byAdding: .day, value: 1, to: cal.startOfDay(for: .now))!)! let alice = Collaborator(name: "Alice") let beerShop = Location.address(Address(street: "Kreuzbergstrasse 78", city: "Berlin", country: "Germany", zipCode: "10965")) buyBeer = buyBeer .alter(by: .setting(.title(to: "Buy beer — ASAP!"))) .alter(by: .setting(.state(to: .inProgress))) .alter(by: .setting(.dueDate(to: .date(tomorrowNoon)))) .alter(by: .setting(.location(to: beerShop))) .alter(by: .adding(.collaborator(alice))) It becomes apparent, that TodoItem's Change DSL follows spoken English quite closely: buyBeer: alter by setting title to "Buy beer — ASAP!" alter by setting state

Apr 23, 2025 - 19:34
 0
Is This Programming Paradigm New? [closed]

preface: to define what a programming paradigm is in context of this post, I will use the definition used in wikipedia

A programming paradigm is a relatively high-level way to conceptualize and structure the implementation of a computer program.

In this post the Declarative Domain Paradigm (DDP) uses DSLs (based on type declarations) to define and implement behaviour, logic and relationships.

I also want to clarify that the question is not, if this is a paradigm. It is. The question is, if this is new or if similar things have been publish before.


In the last 5 years I have explored what I think is a new programming paradigm that I call "Declarative Domain Programming". It uses type declarations to model logic, behaviour and relationships.

The language I am using is Swift, as it offers nested types, enums with associated values and type inference.

Let us look at an example, a simple todo item.

struct TodoItem {
    let title        : String
    let state        : State
    let dueDate      : DueDate
    let location     : Location
    let collaborators: [Collaborator]
}

extension TodoItem {
    enum State {
        case unfinished
        case inProgress
        case finished
    }
}

extension TodoItem {
    enum DueDate {
        case none
        case date(Date)
    }
}

enum Location {
    case unknown
    case coordinate(Coordinate)
    case address(Address)
}

struct Address {
    let street : String
    let city   : String
    let country: String
    let zipCode: String
}

struct Coordinate {
    let latitude : Double
    let longitude: Double
}

struct Collaborator {
    init(name: String) {
        self.init(UUID(), name)
    }
    private init (_ i: UUID, _ n: String) {
        id   = i
        name = n
    }
    let id  : UUID
    let name: String
}

TodoItem has a title, a state (which can be unfinished, in progress or finished), a due date (which can be none or a given date), a location (which can be unknown, a coordinate of latitude and longitude or an address) and a list of collaborators.

Now let us define TodoItem's Change DSL to change any of the attributes:

struct TodoItem {
    enum Change {
        case setting(Setting); enum Setting {
            case title   (to: String)
            case state   (to: State)
            case dueDate (to: DueDate)
            case location(to: Location)
        }
        case adding(Adding); enum Adding   {
            case collaborator(Collaborator)
        }
        case removing(Removing); enum Removing {
            case collaborator(Collaborator)
        }
    }
    // ...
}

Following commands can be encoded by this DSL:

  • .setting(.title(to: ))
  • .setting(.state(to: ))
  • .setting(.dueDate(to: ))
  • .setting(.location(to: ))
  • .adding(.collaborator())
  • .removing(.collaborator())

Now we add a method that pattern-matches over these values and creates a new object with the reflected changes:

struct TodoItem {
    //...

    init(_ t: String, _ s:State, _ d:DueDate, _ l:Location, _ c:[Collaborator]) {
        title         = t
        state         = s
        dueDate       = d
        location      = l
        collaborators = c
    }
    
    func alter(by c: Change) -> Self {
        switch c {
        case let .setting(.title(to: t))    : Self(t    , state, dueDate, location, collaborators                      )
        case let .setting(.state(to: s))    : Self(title, s    , dueDate, location, collaborators                      )
        case let .setting(.dueDate(to: d))  : Self(title, state, d      , location, collaborators                      )
        case let .setting(.location(to: l)) : Self(title, state, dueDate, l       , collaborators                      )
        case let .adding  (.collaborator(c)): Self(title, state, dueDate, location, collaborators + [c]                )
        case let .removing(.collaborator(c)): Self(title, state, dueDate, location, collaborators.filter{$0.id != c.id})
        }
    }
}

By switching over the Change value we decode the command and write associated values to local constants. We use these constants to overwrite an existing value when creating a new object.

We can now use it like:

var buyBeer = TodoItem(title: "Buy beer")

let cal = Calendar.current
let tomorrowNoon = cal.date(byAdding: .hour, value: 12, to: cal.date(byAdding: .day, value: 1, to: cal.startOfDay(for: .now))!)!

let alice = Collaborator(name: "Alice")

let beerShop = Location.address(Address(street: "Kreuzbergstrasse 78", city: "Berlin", country: "Germany", zipCode: "10965"))


buyBeer =
    buyBeer
        .alter(by: .setting(.title(to: "Buy beer — ASAP!")))
        .alter(by: .setting(.state(to: .inProgress)))
        .alter(by: .setting(.dueDate(to: .date(tomorrowNoon))))
        .alter(by: .setting(.location(to: beerShop)))
        .alter(by: .adding(.collaborator(alice)))

It becomes apparent, that TodoItem's Change DSL follows spoken English quite closely:

buyBeer:

  • alter by setting title to "Buy beer — ASAP!"
  • alter by setting state to "in progress"
  • alter by adding collaborator "Alice"
  • ...

We can overload the alter method to accept a list of changes:

struct TodoItem {
    //...
    
    func alter(by c:[Change])  -> Self { c.reduce(self) { $0.alter(by:$1) } }
    func alter(by c:Change...) -> Self { alter(by: c) }
    private func alter(by c: Change) -> Self {
        switch c {
        case let .setting(.title(to: t))    : Self(t    , state, dueDate, location, collaborators                      )
        case let .setting(.state(to: s))    : Self(title, s    , dueDate, location, collaborators                      )
        case let .setting(.dueDate(to: d))  : Self(title, state, d      , location, collaborators                      )
        case let .setting(.location(to: l)) : Self(title, state, dueDate, l       , collaborators                      )
        case let .adding  (.collaborator(c)): Self(title, state, dueDate, location, collaborators + [c]                )
        case let .removing(.collaborator(c)): Self(title, state, dueDate, location, collaborators.filter{$0.id != c.id})
        }
    }
}

Now this is possible:

let alice = Collaborator(name: "Alice")
let bob = Collaborator(name: "Bob")

let berlin = Location.coordinate(Coordinate(latitude: 52.520008, longitude: 13.404954))

let buyChips = TodoItem(title: "Buy chips")
    .alter(by:
            .setting(.state(to: .inProgress)),
            .setting(.dueDate(to: .date(tomorrowNoon))),
            .adding (.collaborator(alice)),
            .adding (.collaborator(bob)),
            .setting(.location(to: berlin))

You will find a Xcode playground on GitLab. If you don't have access to Xcode you'll find an online playground on SwiftFiddle.


The following example is an implementation of Conway's Game of Life. This has significance as it proves the Turing completeness of this coding paradigm.

life glider cannon

struct Life:Codable {
    enum Change {
        case process
        case pause, unpause
        case set (_Set); enum _Set {
            case cells([Cell])
        }
    }
    let paused             : Bool
    let step               : Int
    let size               : Int
    let stateForCoordiantes: [Life.Cell.Coordinate : Life.Cell.State]
    
    init(coordinates: [Cell.Coordinate]) {
        self.init(cells:coordinates.map { Cell(coordinate:$0) })
    }
    init(cells: [Cell]) {
        self.init(0,cells,false, 0)
    }
    func alter(_ cs: [Change] ) -> Self { cs.reduce(self) { $0.alter($1) } }
    func alter(_ cs: Change...) -> Self { cs.reduce(self) { $0.alter($1) } }
    func alter(_ c : Change   ) -> Self {
        if paused && !unpausing(c) { return self }
        switch c {
                                            //     |step|,|<----------------------- cells --------------------->|,paused,|<------- size -------->|
        case let .set(.cells(cells)): return .init(step+1,cells                                                  ,paused,stateForCoordiantes.count)
        case .pause                 : return .init(step+1,stateForCoordiantes.map{ Cell(coordinate:$0,state:$1) },  true,stateForCoordiantes.count)
        case .unpause               : return .init(step+1,stateForCoordiantes.map{ Cell(coordinate:$0,state:$1) }, false,stateForCoordiantes.count)
    case .process               : return alter(.set(.cells(applyRules())))
        }
    }
}

extension Life {
    struct Cell: Hashable, Equatable, Codable {
        init(coordinate: Coordinate, state:State = .alive) {
            self.coordinate = coordinate
            self.state = state
        }
        struct Coordinate: Equatable, Codable {
            let x, y: Int
        }
        enum State: Hashable, Codable {
            case alive, dead
        }
        let coordinate: Coordinate
        let state     : State
    }
}

private extension Life {
    init(_ step: Int,_ cells: [Cell] = [],_ paused:Bool,_ size:Int) {
        self.step                = step
        self.paused              = paused
        self.stateForCoordiantes = cells.reduce([:]) { var a = $0; a[$1.coordinate] = $1.state; return a }
        self.size                = size
    }
    func applyRules() -> [Life.Cell] {
        Array(Set(self.stateForCoordiantes.map { k,v in Cell(coordinate: k, state: v)}.flatMap { neighbors(for:$0) }))
            .compactMap {
                let neigbours = aliveNeighbors(for: $0)
                let isAlive:Bool
                switch (neigbours.count, $0.state) {
                case (0...1, .alive): isAlive = false
                case (2...3, .alive): isAlive = true
                case (4...8, _     ): isAlive = false
                case (3,      .dead): isAlive = true
                case (_,      _    ): isAlive = false
                }
                return isAlive ? .init(coordinate: .init(x: $0.coordinate.x, y: $0.coordinate.y), state:.alive) : nil
            }
    }
    func neighbors(for cell: Life.Cell) -> [Life.Cell] {
        aliveNeighbors(for: cell) + deadNeighbors(for: cell)
    }
    func aliveNeighbors(for cell: Life.Cell) -> [Life.Cell] {
        allNeigbourCoordinates(cell).map {
            Cell(coordinate: $0, state: stateForCoordiantes[$0] ?? .dead)
        }.filter { $0.state == .alive }
    }
    func allNeigbourCoordinates(_ cell: Life.Cell) -> [Life.Cell.Coordinate] {
        [
            .init(x:cell.coordinate.x-1, y:cell.coordinate.y-1),
            .init(x:cell.coordinate.x-1, y:cell.coordinate.y  ),
            .init(x:cell.coordinate.x-1, y:cell.coordinate.y+1),
            .init(x:cell.coordinate.x  , y:cell.coordinate.y-1),
            .init(x:cell.coordinate.x  , y:cell.coordinate.y+1),
            .init(x:cell.coordinate.x+1, y:cell.coordinate.y-1),
            .init(x:cell.coordinate.x+1, y:cell.coordinate.y  ),
            .init(x:cell.coordinate.x+1, y:cell.coordinate.y+1),
        ]
    }
    func deadNeighbors(for cell: Life.Cell) -> [Life.Cell] {
        Set(allNeigbourCoordinates(cell))
            .subtracting(aliveNeighbors(for:cell).map{ $0.coordinate })
            .map { Cell(coordinate:$0,state:.dead) }
    }
}
private func unpausing(_ c:Life.Change) -> Bool {
    switch c {
    case .unpause: return true
    default      : return false
    }
}
extension Life.Cell.Coordinate: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(x)
        hasher.combine(y)
    }
}

You will find the project including a macOS UI on GitLab.


The next example is "Snake", which the older among us will most likely know from old Nokia phones.

snake

struct Snake {
    enum Change {
        case move(Move); enum Move {
            case forward
            case right
            case left
        }
        case grow
    }
    enum Facing {
        case north
        case east
        case south
        case west
    }
    
    let head  : Coordinate
    let tail  : [Coordinate]
    let facing: Facing
    var body  : [Coordinate] { [head] + tail }
    
    init(head:Coordinate) {
        self.init(head, [], .north)
    }
    func alter(_ c:Change) -> Self {
        switch c {
        case let .move(d): move(d)
        case     .grow   : grow()
        }
    }
}

private extension Snake {
    init(_ h:Coordinate,_ t:[Coordinate],_ f:Facing) { head = h; tail = t; facing = f }
    func move(_ move:Change.Move) -> Self {
        var newTail: [Coordinate] { Array(body.prefix(tail.count)) }
        switch (facing, move) { //         |<-------------- head -------------->|  tail  |facing|
        case (.north,.forward): return Self(Coordinate(x:head.x    ,y:head.y - 1),newTail,.north)
        case (.east ,.forward): return Self(Coordinate(x:head.x + 1,y:head.y    ),newTail,.east )
        case (.south,.forward): return Self(Coordinate(x:head.x    ,y:head.y + 1),newTail,.south)
        case (.west ,.forward): return Self(Coordinate(x:head.x - 1,y:head.y    ),newTail,.west )
        case (.north,   .left): return Self(Coordinate(x:head.x - 1,y:head.y    ),newTail,.west )
        case (.east ,   .left): return Self(Coordinate(x:head.x    ,y:head.y - 1),newTail,.north)
        case (.south,   .left): return Self(Coordinate(x:head.x + 1,y:head.y    ),newTail,.east )
        case (.west ,   .left): return Self(Coordinate(x:head.x    ,y:head.y + 1),newTail,.south)
        case (.north,  .right): return Self(Coordinate(x:head.x + 1,y:head.y    ),newTail,.east )
        case (.east ,  .right): return Self(Coordinate(x:head.x    ,y:head.y + 1),newTail,.south)
        case (.south,  .right): return Self(Coordinate(x:head.x - 1,y:head.y    ),newTail,.west )
        case (.west ,  .right): return Self(Coordinate(x:head.x    ,y:head.y - 1),newTail,.north)
        }
    }
    func grow() -> Self {
        //  |head|<----------------- tail ----------------->|facing|
        Self(head,!tail.isEmpty ? tail+[tail.last!] : [head],facing)
    }
}

Snake's Change DSL encodes the commands

  • .move(.forward)
  • .move(.right)
  • .move(.left)
  • .grow

In the move method all combinations of a direction command and the currently facing compass directions are mapped to new Snake instances with new head, new tail and the new compass direction.

grow method grows the snake by adding the last tail element.

You will find the code on GitLab.


So far I have shown you some model types that are coded in Declarative Domain Coding. But I want to point out, that whole apps can be coded this way. A general purpose architecture I have implemented several times is centred around UseCases, an idea taken from Robert C. Martin's "Clean Architecture" I describe it in detail in this post, including BBD-style testing.


Now the question why I walked you through all of this: Is this indeed a new programming paradigm or did I reinvent the wheel? If reinvented: what elements in what languages would be used to achieve something similar?

Thanks for your patience.