In the world of software engineering, it's quite common for business logic, application logic, technical boilerplate, logging, and other cross-cutting concerns to be woven together within a unified source code. At a large scale, developers begin struggle delivering error-free code, adding new features in a timely manner, or troubleshooting complex problems. Worst yet, it can be a costly problem to fix, and a systems overhaul is likely to cause significant instability in the software, and a rewrite could set back a development team decades. Thus, "tech debt" tends to accumulate with no bounds.
Many programming languages have attempted to address semantic decoupling, with varying results. Languages like Haskell and OCaml have gained some popularity for their unconventional yet powerful compiler features. A major obstacle is transparency, and in more ways than one. In terms of language transparency, functional languages have an unusually steep learning curve for newcomers, primarily due to counter-intuitive design patterns and theory-heavy documentation. In terms of runtime transparency, performance issues in a functional language with a sophisticated compiler are notoriously difficult to troubleshoot effectively.
Symbols+Numbers ("Symbols Plus Numbers", SPN) attempts to resolve the issues of modern functional programming, without compromising runtime semantics or obscuring implementation. This is accomplished not by deferring all runtime semantics to the compiler like Haskell, but rather by fully decoupling runtime semantics from application logic.
Constrain the language, not the developer.
The first step in writing any application is planning. This is the primary motivation behind test-driven development (TDD) and behavior-driven development (BDD), both frameworks intended to improve software quality by encouraging architectural planning before application logic is written.
Symbols is the symbolic language of SPN - it defines what an SPN application or script is intended to do, but not how.
Some developers prefer mapping out a data structure first. Others prefer outlining a process or workflow, then refine the data model as development progresses. SPN enables developers to start in either place.
SPN enables the construction of custom algebraic types. Operations on these types are checked at compile time to ensure they match the declared domain. These types may declare additional non-numeric values, but must be accompanied by rules to satisfy the closure.
In the following example, a custom algebraic type PositiveIntegerOmega is defined. This type would gracefully handle division by zero with a distinguished element Omega, which reverts back to 1 when multiplied by zero. Underscore is used as an operational wildcard for convenience.
Object types are structured collections of relational data. They may be declared in a generic way (shown below) without specifying behavior, numeric precision, or mutability. SPN can use the same type across multiple contexts.
Text types operate as text with constraints and/or metadata. Text types automatically behave as regular text - or strings - and may overlay existing text at runtime, without impacting any other text-related features. This can be useful for writing parsers, formatters, or even byte streams.
A stream type is a stateful class that produces values. Streams are the backbone of iterators, useful for controlling program flow without defining specific procedures.
For this section, we'll be using the following data model as an example to illustrate pattern matching.
"When" is a keyword that defines a cause and effect.
Monadic decoupling lifts a computation into a context where auxiliary behaviors (like caching) can be introduced, modified, or removed independently of the core logic, possibly at runtime."
You'll find dozens of posts online of developers struggling to understand what a monad even is, let alone how to use them effectively. SPN sees this as a marketing failure - the feature has not been implemented in a way conducive to widespread understanding or adoption. The solution is to make monads a central focus of the language - a necessary component that must be defined for any logic to be evaluated. Then, a standard library offering a variety of common utility monads would be immediately available, justifying its existence and discouraging "re-inventing the wheel".
Some of these monads include:
These monads would essentially be plug-and-play, giving the developer complete control over runtime semantics without getting encumbered with boilerplate and cross-cutting concerns.