mlhaufe / brevity

Brevity is a library that enables Feature-Oriented Programming (FOP) and solves the expression problem in a manner that makes data and operation declarations trivial to define and compose.
GNU Affero General Public License v3.0
1 stars 0 forks source link

Change `data` to no longer combine factory and variant declaration #69

Open mlhaufe opened 1 year ago

mlhaufe commented 1 year ago

data currently defines a class hierarchy as well as a factory for constructing instances of that hierarchy:

const colorData = data({ Red: {}, Green: {}, Blue: {} })

const PointData = data({
    Point2: {x: Number, y: Number},
    Point3: {x: Number, y: Number, z: Number}
})

const PeanoData = data(() => ({
    Zero: {},
    Succ: {pred: PeanoData}
}))

const ListData = data((T) => ({
    Nil: {},
    Cons: {head: T, tail: ListData(T) }
}))

This requires extra effort in type checking, complection, and declaration (due to the fixpoint requirement).

If data did not declare a factory then the above forms can be represented as:

const ColorData = data({}),
    Red = data(ColorData, {}),
    Green = data(ColorData, {})

const PointData = data({}),
    Point2 = data(PointData, {x: Number, y: Number}),
    Point3 = data(PointData, {x: Number, y: Number})

const PeanoData = data({}),
    Zero = data(PeanoData, {}),
    Succ = data(PeanoData, {pred: PeanoData})

const ListData = (T) => {
    const ListData = data({ of: T }),
        Nil = data(ListData, {}),
        Cons = data(ListData, { head: T, tail: ListData })
    return List
}

The syntactic burden is comparable to the original forms except for the parameterized recursive form.

For complect to work as desired, each data declaration has a [children] symbol that returns the extensions:

Peano[children] // [Zero, Succ]

Which enables:

const Peano = complect(PeanoData, [...])

This should have the added benefit of making type declarations easier as they are non-trivial in typescript and very challenging to express in jsdoc.

mlhaufe commented 1 year ago

The parameterized recursive form will cause an issue with strict equality tests:

const NumListData1 = ListData(Number),
    NumListData2 = ListData(Number)

NumListData1 !== NumListData2
mlhaufe commented 1 year ago
const ListData = memofix((T) => {
    const _ListData = data({ of: T }),
        Nil = data(_ListData, {}),
        Cons = data(_ListData, { head: T, tail: ListData(T) })
    return _ListData
})

To derive a general solution I don't see how the splitting out of data to one per class helps. The recursive form implies that an owning object needs to be returned. Add that to the requirement of a [children] reference and it looks like we've come full-circle back to a factory-like object. abstracting the memofix form and generalizing for the family looks exactly like the original data decl with a semantic distinction that doesn't seem to make much of a difference...

mlhaufe commented 1 year ago

The Enumeration class pattern seems to be a reasonable counter-point:

https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types