Module

Harmonia.Graded

Package
purescript-harmonia
Repository
afcondon/purescript-harmonia

Harmonia.Graded — grade-gated harmonic operations, honest about partiality.

Two type classes over anything harmonic:

  • Harmonicgrade (what leverage does this afford?) and transpose (the always-available floor; never blames).
  • Reflavourable — the operations that consult the reading and may fail to apply: modulate (needs a key) and reflavour (needs a diatonic reading). They are total functions — you can always call them — but each returns a Graded, carrying both a result and any blame.

This is the design inversion made concrete. In code we make illegal states unrepresentable; a chord that has left its scale is not illegal, it is expressive, so we make it representable and let the operation report where its reading ran out. Building it against the real types settled a design question left open in the sketch: every operation is total-with- annotations — the input always survives as a fallback (worst case, passed through untouched) — so the honest result shape is a value carrying blame, not a These with an uninhabited "nothing came back" case. The blame is the leverage: Both-style partial results force the caller to see the boundaries rather than silently getting a mangled chord.

The classes instance for both a single Anchor and a Phrase (a sequence of readings) — the fractal the design turns on: a progression is the same kind of object as a chord, one level up, and its grade is emergent (the meet of its members in v1), not a property of any single chord.

The "lock" from the design discussion turns out to be a value, not a class: every Reflavourable thing may attempt every operation; the grade decides which succeed and which come back blamed. Locked = high grade, unlocked = low grade — checked at the value level, exactly so the illegal move stays representable.

Pure Prelude/Data.*; builds unchanged on JS and purerl.

#BlameReason Source

data BlameReason

Why a chord could not follow an operation. Closed, and richer than a bare reference on purpose: the reason is exactly what a renderer needs to mark the boundary (a passed-through chord in a reflavoured phrase, say) — but the choice of how to mark it stays with the caller.

Constructors

Instances

#Blame Source

type Blame = { index :: Int, reason :: BlameReason }

A single blame: which position (0 for a lone chord; the phrase index for a member of a Phrase) and why.

#Graded Source

newtype Graded a

The outcome of a grade-gated operation: a value plus the blame accrued applying it. Empty blame = a clean, fully-applied result; non-empty blame = a partial result whose listed positions were passed through untouched.

Constructors

Instances

#value Source

value :: forall a. Graded a -> a

The result value (always present).

#blame Source

blame :: forall a. Graded a -> Array Blame

The blame accrued (empty when the operation applied cleanly).

#clean Source

clean :: forall a. a -> Graded a

A clean result — the operation applied with nothing passed through.

#blamed Source

blamed :: forall a. Int -> BlameReason -> a -> Graded a

A blamed result — the value (typically the input, unchanged) plus one reason it could not be transformed at the given position.

#Harmonic Source

class Harmonic a  where

Everything harmonic reports its grade and can be transposed. Transpose is the floor: pure semitone arithmetic, always total, never blames.

Members

Instances

#Reflavourable Source

class (Harmonic a) <= Reflavourable a  where

Operations that consult the reading and may not apply. modulate moves the tonic (needs a key); reflavour re-reads the same degree in a new mode (needs a diatonic reading). Both return Graded, blaming what they cannot move.

Members

Instances

#Phrase Source

newtype Phrase

An ordered sequence of chord readings. Its grade is emergent: in v1 the meet of its members, so a single foreign chord drops the whole phrase to what all of it can uniformly do. (The intended growth is to absorb subordinate chromaticism — a secondary dominant read against a phrase degree shouldn't lower the phrase — which is why this is the meet for now, with a clearly-marked seam rather than a claim that the meet is the last word.)

Constructors

Instances