SAS (9.4 / Viya)
This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].
1 Assessment
This page assesses SAS in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.
Core coupling 51/100 · Ecosystem coupling 54/100 · Engineering readiness 34/100
1.1 Core coupling
1.1.1 Type
- ❌ Dynamic typing with no static check (T1): A variable takes its type when the DATA step first builds the row, so a mismatch appears at run time
- ⚠️ Two types exist and a character length is fixed at first use: A variable is numeric or character, and a later longer value is truncated to the length already set
- ⚠️ The dataset carries the schema, not a declaration: A variable list comes from the dataset descriptor, so a variable’s type is read from data rather than from source
- ✅
LENGTHandATTRIBdeclare a variable explicitly: A length, a type, and a format can be fixed before the data arrives, which decouples the variable from the value that first creates it - ✅
FORMATandINFORMATseparate presentation from storage: A date is a number with aDATE9.format, so a reader sees a date and the value stays numeric - ❌ There is no type beyond a column in a dataset: SAS has no user-defined type, generic, or interface, so a richer value is encoded in columns
1.1.2 Construction
- ✅ The DATA step separates data handling from analysis: A DATA step reads, merges, filters, and derives; a PROC step analyses and reports, so the two are distinct statement kinds
- ✅ A PROC step names its analysis and takes a dataset:
PROC MEANS data=work.sales;performs one analysis on one input, so the analysis is not tangled with retrieval - ✅
SET,MERGE, andBYcompose datasets declaratively: A join is declared with aBYkey, so the key is the join condition rather than a hand-coded loop - ❌ No immutability by default (C2): A DATA step can overwrite the dataset it reads (
data work.x; set work.x;), and every variable is reassignable - ❌ A macro is the only module mechanism:
%MACROis text substitution, so an expansion depends on the macro symbol table and on the place of the invocation - ⚠️ The DATA step’s implicit loop carries state across rows:
RETAINand the automatic_N_follow the row stream, so row order becomes part of the result
1.1.3 Memory
- ✅ No manual memory management: The SAS supervisor allocates and frees the buffers of a step, and the program never names an address
- ✅ The program-data vector bounds per-row state: A step holds one row’s variables in a fixed vector, so the program does not size buffers
- ✅ The resource envelope is a configuration choice:
SORTSIZE,MEMSIZE(on the hosts that provide it), and the Viya CAS memory settings are set outside the program - ⚠️ A large sort spills to the work library: A sort or a hash join that exceeds its memory writes to the utility library, so the temporary footprint follows the data volume
- ⚠️ A macro variable persists for the whole session: A
%LETvalue stays until it is reassigned, so it is shared state that outlives the step that set it
1.1.4 Error
- ✅
OPTIONS ERRORABENDstops the session on an error: An error ends the run instead of continuing, which turns a silent failure into a visible one - ✅ A macro-level guard can abort the run:
%IF &SYSERR %THEN %ABORTreads the error status and stops the batch - ❌ No structured error path (Er1): SAS has no exception construct, so a DATA step records an error, writes a note to the log, and by default continues to the next observation
- ⚠️ An error is reported in the log, not to the caller: A step sets
&SYSERRand the automatic_ERROR_flag, so detection is by log inspection or a macro check - ⚠️ Error checking is opt-in per step: The default continues on many data errors, so a batch run can finish with a wrong dataset
1.1.5 Concurrency
- ✅ Procedures can use several threads:
OPTIONS THREADSletsPROC SORT,PROC MEANS, andPROC SQLuse more than one processor - ✅ SAS/CONNECT and MP Connect distribute work across sessions: A job can be split over several SAS sessions on one host or across hosts
- ✅ A dataset is locked per member: Two writers do not share a row buffer, so member-level locking prevents a concurrent overwrite in one library
- ⚠️ The language offers no lock or atomic primitive: Coordination is the platform’s library locking and the scheduler’s, not a construct in the DATA step
- ⚠️ The macro symbol table is shared session state: One step’s
%LETorCALL SYMPUTis visible to every later step in the session
1.1.6 Platform
- ❌ Cannot function outside one vendor (P1): The language needs a SAS installation, and a program’s unit of deployment is a SAS library on a SAS host
- ❌ SAS datasets are the interchange unit: The persistence format is a proprietary member of a SAS library
- ⚠️ Viya is a second runtime under the same vendor: Viya runs the language on the CAS engine with cloud deployment, but it is the same vendor’s platform, not an independent implementation
- ✅ A dataset can be written to the documented XPORT transport form: The XPORT engine with
PROC COPYproduces the openly documented transport format that the FDA requires for electronic submissions, and other tools can read it - ⚠️ Open-source interop exists from inside the platform:
PROC PYTHONandPROC Rcall R and Python, so part of a workflow can use an open tool
1.1.7 Trade-offs (not coupling)
- The DATA-step/PROC-step split is a deliberate separation of concerns: Retrieval and analysis are distinct statement kinds by design
- The dataset is the only data abstraction: One table shape for all data keeps the language uniform, at the cost of encoding a richer value in columns
- A macro is a text-substitution preprocessor: It keeps a repeated block in one place, at the cost of visibility at expansion time
- Point-and-click Enterprise Guide serves a non-programmer task: Hiding the language suits a code-free workflow, so the GUI’s coupling to the engine is the product’s purpose
- A proprietary platform is traded for a regulated-industry toolchain: One vendor with documented releases and validation packages suits a regulated deliverable
- Viya chose coexistence over replacement: Modernising the architecture while keeping SAS 9.4 running is a compatibility decision
1.2 Ecosystem coupling
1.2.1 Performance
- ✅ The DATA step and the procedures run compiled code: A step is compiled to a program-data-vector loop executed by the supervisor
- ✅ Multithreading and the CAS engine parallelise heavy work: Threaded procedures and distributed in-memory execution in Viya carry the large jobs
- ✅ An index or the SPD engine avoids a full scan: A lookup by an indexed key or by the multi-threaded SPD engine reads fewer pages than a sequential pass
- ⚠️ A sort can dominate a job’s time and space: A sort that exceeds memory spills to the work library, so a job of a given size needs a matching utility allocation
- ⚠️ A
BY-group analysis requires sorted input: Many procedures need the data sorted by theBYkey, which makes a sort a precondition of the analysis
1.2.2 Ecosystem
- ✅ The library is large and mature inside its industry: SAS ships statistical, econometric, clinical, and operations-research modules with long support windows
- ✅ Regulated statistical practice has decades of SAS convention: Clinical and regulated reporting follows CDISC standards and platform validation practice
- ✅ Viya adds open-source tooling: Jupyter, Python, R, and REST interfaces connect to a Viya deployment
- ❌ Proprietary (Ec4): The product is licensed by subscription, and its modules are licensed separately
- ❌ Small, niche, and increasingly legacy (Ec3): The language’s own market has shrunk against R and Python, so new analysts and new packages arrive more slowly
- ⚠️ The ecosystem is closed: A shared contribution is an in-house macro or user-defined format, not a package drawn from an open index
1.2.3 Evolution
- ✅ Long-term stability is a stated commitment: A program written for an earlier release of the same generation still runs on a later one
- ✅ The vendor carries validated code forward: Existing 9.4 language support continues in Viya, so a submission program is not stranded
- ⚠️ The language has evolved slowly: SAS 9.4 has been the reference release since 2013, and the DATA-step and PROC syntax is largely unchanged
- ⚠️ The modern architecture is a second platform: Viya changes deployment, engines, and administration, so a move to it is a platform migration rather than a language upgrade
- ⚠️ Evolution follows the vendor’s roadmap: A new feature appears at the vendor’s next release, not at a date the community fixes
1.2.4 Domain
- ✅ Excellent fit for regulated statistical reporting: A DATA step plus a PROC produce a documented, reproducible table
- ✅ Clinical and financial workflows have a fixed vocabulary:
PROC LIFETEST,PROC PHREG,PROC FREQ, andPROC LOGISTICname the standard analyses directly - ❌ Domain and vendor lock-in (D1): The language is built for statistical analysis on a SAS platform, so both the domain and the vendor are fixed
- ⚠️ The regulatory transport requirement has narrowed: The FDA still requires the open SAS V5 XPORT transport format for datasets, but CDISC standards define their content and other tools can write the format, so the language’s old regulatory moat has shrunk
- ⚠️ General-purpose programming sits outside the design centre: Text processing, user interfaces, and systems work are not the intended use
- ⚠️ Analysts increasingly use R and Python beside it: An open-source language is cheaper per seat and easier to hire for
1.2.5 Learning
- ✅ The DATA-step/PROC-step division is teachable: A beginner learns retrieval in a DATA step and analysis in a PROC, and the two do not mix
- ✅ Documentation and training are complete and vendor-provided: SAS documentation, courses, and certification cover every module
- ⚠️ Steep (L1): The DATA step’s implicit loop, the program-data vector, and the two-match step of
MERGEare unlike any other language’s execution model - ⚠️ DATA-step syntax is idiosyncratic: Statements are
KEYWORD variable=value;lines whose effect depends on their order in the step - ⚠️ Macro programming is a second language on top:
%MACRO,&var,%eval, and the quoting functions (%str,%bquote) have their own rules
1.2.6 Syntax
- ✅ Statements are keyword-led and read as English:
PROC SORT data=a; by x; run;states the operation and the key - ✅ A PROC block is small per analysis: An analysis reads as
PROC name data=...;plus a few statements - ❌ Verbose (S1): A typical step is a list of
KEYWORD option;lines, and necessary statements such asLENGTH,FORMAT, andLABELadd lines before any data is touched - ⚠️ Every step must be closed by
RUNorQUIT: A missingRUN;means the step does not execute, and a missingQUITleaves a procedure open - ⚠️ The semicolon and the
%sigil each carry meaning: A missing semicolon changes the statement, and a macro reference without%or&is treated as text - ⚠️ Formats and informats add a name to every value:
date9.,$char20., andbest12.decorate a declaration, so a reader learns a second vocabulary
1.2.7 Testing
- ✅
PROC COMPAREreports differences by variable and observation: A regression test is a dataset comparison with a named result - ✅ A check step can assert its own expectation: A step can read the result and
%ABORTon a wrong value, andOPTIONS ERRORABENDturns a data error into a stop - ✅ The log is the primary diagnostic instrument: With
MPRINTandMLOGICit names the step and the macro line that failed - ⚠️ No test framework ships with the language: A test is a SAS program, so the harness is a script or a scheduler around it
- ⚠️ Errors are runtime-only: A wrong type or a missing file appears when the step runs, not before
- ⚠️ A passing run can still contain errors: The default continues after many errors, so a clean exit code does not imply a clean run
1.2.8 Security
- ✅ No pointers and no manual memory access: The language offers no address arithmetic, so the memory-corruption class does not apply
- ✅ Access control is a platform feature: Library and table permissions, metadata-server roles, and Viya identities control who reads a dataset
- ✅ Viya records an audit trail: A deployment logs who ran which action against which resource
- ⚠️ A macro variable can build a statement:
&SYSFUNCand a resolved macro variable are expanded into syntax, so a value that carries user input can become code - ⚠️ Encryption and transport security are configuration choices: A library can be encrypted and a connection secured, but the default is the site’s decision
- ⚠️ A dataset member is a binary file with no per-row rule: A leaked member is a full data dump, so row-level restriction must be built above the format
1.3 Engineering readiness
1.3.1 Reproducibility
- ❌ Builds are not byte-reproducible by default.
- ❌ Dependencies resolve without a lock by default.
1.3.2 Supply chain
- ⚠️ Published packages carry no signed provenance by default.
1.3.3 Observability
- ⚠️ No stable tracing and metrics standard.
- ⚠️ The standard library has no structured logging.
1.3.4 Feedback
- ⚠️ No fast in-editor analysis.
1.3.5 Interop
- ❌ No general foreign-function interface.
- ⚠️ No maintained portable target.
1.3.6 Governance
- ⚠️ One vendor decides the language’s direction.
- ⚠️ The specification is not public.
1.4 Profile
Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.
1.4.1 Energy and cost
What this axis asks: what does a running program cost in energy and time?
- ⚠️ Runs inside a database engine: energy and time are the engine’s, not the program’s.
1.4.2 AI and agent readiness
What this axis asks: how well does a coding model and an agent work in this language?
- ❌ Sparse training data: models write it unreliably [5].
1.4.3 Safety certification
What this axis asks: is there a certified or standards-qualified path to a safety-critical system?
- ⚠️ No certified or standards-qualified path for safety-critical systems.
1.5 Scoring detail
1.5.1 Core coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Type | T1 |
dynamic no static check | ✅ | −15 |
T2 |
weak static | — | 0 | |
T3 |
dynamic/duck dispatch | — | 0 | |
T4 |
no sum types | — | 0 | |
T5 |
no generics | — | 0 | |
T6 |
no namespaces | — | 0 | |
T7 |
type erasure/reflection | — | 0 | |
T8 |
operator overloading | — | 0 | |
T9 |
inheritance required for polymorphism | — | 0 | |
T10 |
no null-safety | — | 0 | |
T11 |
no module boundary | — | 0 | |
T12 |
no effect tracking | ✅ | −8 | |
T13 |
async/sync colouring | — | 0 | |
T14 |
unchecked foreign boundary | — | 0 | |
| Type subtotal | −15 (−23) | |||
| Construction | C1 |
global mutable state by default | — | 0 |
C2 |
no immutability by default | ✅ | −4 | |
C3 |
constructors coupled to allocation | — | 0 | |
C4 |
no constructor/validation | — | 0 | |
| Construction subtotal | −4 | |||
| Memory | M1 |
manual deallocation | — | 0 |
M2 |
GC non-deterministic pauses | — | 0 | |
M3 |
no RAII | — | 0 | |
M4 |
no ownership model | — | 0 | |
| Memory subtotal | 0 | |||
| Error | Er1 |
no structured path | ✅ | −15 |
Er2 |
unchecked exceptions only | — | 0 | |
Er3 |
errors ignored/non-exhaustive | — | 0 | |
Er4 |
no composable result type | — | 0 | |
| Error subtotal | −15 | |||
| Concurrency | K1 |
GIL/no parallel | — | 0 |
K2 |
shared mutable state default | — | 0 | |
K3 |
manual sync no race detection | — | 0 | |
K4 |
no structured concurrency | — | 0 | |
K5 |
no primitives | — | 0 | |
| Concurrency subtotal | 0 | |||
| Platform | P1 |
cannot function outside one vendor/domain | ✅ | −15 |
P2 |
single-vendor but other uses | — | 0 | |
P3 |
legacy/discontinued platform | — | 0 | |
| Platform subtotal | −15 |
Core Dimensions score: 51/100 — total cost = −49 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.2 Ecosystem coupling Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Performance | R1 |
interpreted no AOT/JIT | — | 0 |
R2 |
GC pauses | — | 0 | |
R3 |
JIT warmup | — | 0 | |
| Performance subtotal | 0 | |||
| Ecosystem | Ec1 |
no package manager | — | 0 |
Ec2 |
fragmented tooling | — | 0 | |
Ec3 |
small/niche/discontinued | ✅ | −8 | |
Ec4 |
proprietary | ✅ | −8 | |
Ec5 |
platform-locked tooling | — | 0 | |
Ec6 |
monoculture | — | 0 | |
Ec7 |
legacy burden | — | 0 | |
| Ecosystem subtotal | −15 (−16) | |||
| Evolution | V1 |
breaking changes | — | 0 |
V2 |
frozen | — | 0 | |
V3 |
discontinued | — | 0 | |
V4 |
glacial standards | — | 0 | |
| Evolution subtotal | 0 | |||
| Domain | D1 |
domain/vendor lock-in | ✅ | −15 |
D2 |
poor domain fit | — | 0 | |
D3 |
framework forces architecture | — | 0 | |
| Domain subtotal | −15 | |||
| Learning | L1 |
steep | ✅ | −8 |
L2 |
large surface/multi-paradigm | — | 0 | |
L3 |
sparse docs | — | 0 | |
| Learning subtotal | −8 | |||
| Syntax | S1 |
verbose | ✅ | −8 |
S2 |
inconsistent | — | 0 | |
S3 |
punctuation-heavy | — | 0 | |
| Syntax subtotal | −8 | |||
| Testing | G1 |
weak tooling | — | 0 |
G2 |
poor debugging | — | 0 | |
G3 |
runtime-only errors | — | 0 | |
| Testing subtotal | 0 | |||
| Security | Q1 |
memory-unsafe | — | 0 |
Q2 |
dynamic/weak typing risk | — | 0 | |
Q3 |
known vulnerability/injection | — | 0 | |
| Security subtotal | 0 |
Meta Dimensions score: 54/100 — total cost = −46 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
1.5.3 Engineering readiness Dimensions
| Dimension | Mechanism | Description | Present | Cost |
|---|---|---|---|---|
| Reproducibility | Y1 |
build not reproducible | ✅ | −8 |
Y2 |
dependencies not pinned | ✅ | −8 | |
| Reproducibility subtotal | −15 (−16) | |||
| Supply chain | Y3 |
no signed provenance | ✅ | −8 |
Y4 |
registry without namespacing | — | 0 | |
| Supply chain subtotal | −8 | |||
| Observability | Y5 |
no stable tracing standard | ✅ | −8 |
Y6 |
no structured logging | ✅ | −4 | |
| Observability subtotal | −12 | |||
| Feedback | Y7 |
slow build | — | 0 |
Y8 |
no fast in-editor analysis | ✅ | −4 | |
| Feedback subtotal | −4 | |||
| Interop | Y9 |
no foreign-function interface | ✅ | −8 |
Y10 |
no portable target | ✅ | −4 | |
| Interop subtotal | −12 | |||
| Governance | Y11 |
single-vendor governance | ✅ | −8 |
Y12 |
closed specification | ✅ | −8 | |
| Governance subtotal | −15 (−16) |
Readiness Dimensions score: 34/100 — total cost = −66 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).
Verdict: SAS’s design yields moderate core coupling and a weaker platform position. The DATA step and the PROC step are genuinely separate concerns, and the language is strong for regulated statistical reporting. Main strengths: the retrieval/analysis split, a mature module library, and long-term stability that carries validated code forward. Main weaknesses: dynamic typing with no static check, no structured error path, a proprietary platform with a closed ecosystem, a shrinking market against R and Python, and an idiosyncratic DATA-step syntax that makes the learning curve steep.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122
- Coupling as a Consequence of Cohesion: Decomposition, the Coupling Floor, and Domain-Imposed Coupling — Zenodo, 2026 — doi:10.5281/zenodo.21925201
- On the Nature of Cohesion: Cohesion as Correctness Relative to a Partitioning Rule — Zenodo, 2026 — doi:10.5281/zenodo.21703193