<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>The Nex Blog</title>
  <subtitle>Notes on Nex: how to use it, how it is designed, and what is changing.</subtitle>
  <link href="https://schemer.in/nex/blog/feed.xml" rel="self"/>
  <link href="https://schemer.in/nex/blog/"/>
  <id>https://schemer.in/nex/blog/</id>
  <updated>2026-10-18T00:00:00Z</updated>
  <author><name>Vijay Mathew</name></author>
  <entry>
    <title>Music with Nex, Part 1 — Pitch and Interval</title>
    <link href="https://schemer.in/nex/blog/music-with-nex-part-1.html"/>
    <id>https://schemer.in/nex/blog/music-with-nex-part-1.html</id>
    <published>2026-10-18T00:00:00Z</published>
    <updated>2026-10-18T00:00:00Z</updated>
    <summary>The first part of a series that builds a small music sequencer from scratch in Nex.</summary>
    <category term="design"/>
    <category term="contracts"/>
    <content type="html" xml:base="https://schemer.in/nex/blog/">&lt;p&gt;The goal of this series is to build a small music sequencer from scratch in Nex. By the last part it will let you sketch phrases on a piano roll, generate melodies, and play them back. But the real aim is to show you the Nex way of writing object-oriented code where every class states what it promises, and Nex checks the promises while the program runs.&lt;/p&gt;
&lt;p&gt;Music is a good test bed for this. It is full of rules that are easy to state and easy to break. For instance, a bar must add up to its time signature and a transposed melody must stay inside the instrument&amp;rsquo;s range. As we build the sequencer in small steps, we will see how contracts help up express these facts in a clear and enforceable way.&lt;/p&gt;
&lt;p&gt;Nex already ships an audio library with a General MIDI synthesizer, a score you can fill note by note, and a player. So we will not write a single line of sound plumbing. Our job is the layer above it: the musical ideas captured as classes with contracts.&lt;/p&gt;
&lt;p&gt;We start with two basic musical ideas: the &lt;strong&gt;pitch&lt;/strong&gt; and &lt;strong&gt;interval&lt;/strong&gt;. A &lt;strong&gt;pitch&lt;/strong&gt; is how high a note sounds. An &lt;strong&gt;interval&lt;/strong&gt; is the distance between two pitches. By the end of this part you will have two small classes, &lt;code&gt;Pitch&lt;/code&gt; and &lt;code&gt;Interval&lt;/code&gt;, and a C major scale playing through your speakers.&lt;/p&gt;
&lt;h2 id=&quot;the-trouble-with-raw-integers&quot;&gt;The trouble with raw integers&lt;/h2&gt;
&lt;p&gt;Here is how the audio library wants a note:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;intern audio/Midi_Song

let score := create Midi_Score.make(4)      -- 4 ticks to a beat
score.add_note(0, 60, 90, 0, 4)            -- channel, pitch, velocity, start, length
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That call is already guarded by contracts. &lt;code&gt;add_note&lt;/code&gt; requires the channel to be in 0..15, the pitch and velocity in 0..127, the start not negative and the length positive. Break any of those and you get a precondition violation naming the rule.&lt;/p&gt;
&lt;p&gt;But look at what the guard cannot see. Every argument is an &lt;code&gt;Integer&lt;/code&gt;. Swap the pitch and the velocity and the call still passes every check:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;score.add_note(0, 90, 60, 0, 4)            -- meant middle C at velocity 90
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That plays the F# two and a half octaves above middle C, quietly. Nothing complains, because 90 is a perfectly good pitch and 60 an equally good velocity.&lt;/p&gt;
&lt;p&gt;The second problem is distance. Suppose a melody is stored as integers and we transpose it up an octave by adding 12. A note at 120 becomes 132. Nothing notices until rendering, when &lt;code&gt;add_note&lt;/code&gt; rejects it, far from the line that made the mistake. The error names &lt;code&gt;add_note&lt;/code&gt;, not the transposition.&lt;/p&gt;
&lt;p&gt;Both problems have the same cure: give the idea its own class. A &lt;code&gt;Pitch&lt;/code&gt; cannot be passed where a velocity is expected, and a &lt;code&gt;Pitch&lt;/code&gt; that would leave the MIDI range cannot be created. The check moves from the edge of the program to the moment the mistake is made.&lt;/p&gt;
&lt;h2 id=&quot;pitch-a-value-that-cannot-be-wrong&quot;&gt;Pitch: a value that cannot be wrong&lt;/h2&gt;
&lt;p&gt;A pitch is a value, like a number or a colour. Two middle Cs are the same pitch, and a pitch never changes once it exists. Here is the whole class. Read the contracts first; then the bodies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;intern Interval

class Pitch
  note &amp;quot;How high a note sounds: one of the 128 MIDI pitches, middle C = 60.&amp;quot;
inherit Comparable

feature
  LOWEST = 0
  HIGHEST = 127
  LETTER_STEPS = {&amp;quot;C&amp;quot;: 0, &amp;quot;D&amp;quot;: 2, &amp;quot;E&amp;quot;: 4, &amp;quot;F&amp;quot;: 5, &amp;quot;G&amp;quot;: 7, &amp;quot;A&amp;quot;: 9, &amp;quot;B&amp;quot;: 11}
  SHARP_NAMES = [&amp;quot;C&amp;quot;, &amp;quot;C#&amp;quot;, &amp;quot;D&amp;quot;, &amp;quot;D#&amp;quot;, &amp;quot;E&amp;quot;, &amp;quot;F&amp;quot;, &amp;quot;F#&amp;quot;, &amp;quot;G&amp;quot;, &amp;quot;G#&amp;quot;, &amp;quot;A&amp;quot;, &amp;quot;A#&amp;quot;, &amp;quot;B&amp;quot;]

  once midi: Integer

create
  from_midi(n: Integer)
  require
    in_midi_range: n &amp;gt;= LOWEST and n &amp;lt;= HIGHEST
  do
    midi := n
  end

  -- Scientific pitch notation: middle C is (&amp;quot;C&amp;quot;, 0, 4).
  -- accidental counts sharps (+) or flats (-).
  named(letter: String, accidental, oct: Integer)
  require
    one_letter: letter.length = 1 and &amp;quot;CDEFGAB&amp;quot;.contains(letter)
    plain_accidental: accidental &amp;gt;= -2 and accidental &amp;lt;= 2
    octave_in_range: oct &amp;gt;= -1 and oct &amp;lt;= 9
    not_below_c_minus_1: oct &amp;gt; -1 or LETTER_STEPS.get(letter) + accidental &amp;gt;= 0
    not_above_g9: oct &amp;lt; 9 or LETTER_STEPS.get(letter) + accidental &amp;lt;= 7
  do
    this.from_midi((oct + 1) * 12 + LETTER_STEPS.get(letter) + accidental)
  end

feature
  -- Which of the twelve notes this is, C = 0 up to B = 11.
  pitch_class: Integer
  do
    result := midi % 12
  ensure
    one_of_twelve: result &amp;gt;= 0 and result &amp;lt;= 11
  end

  octave: Integer
  do
    result := midi / 12 - 1
  ensure
    rebuilds_midi: (result + 1) * 12 + pitch_class() = midi
  end

  name: String
  do
    result := SHARP_NAMES.get(pitch_class()) + octave()
  end

  -- Equal temperament, tuned to A4 = 440 Hz.
  frequency: Real
  do
    result := 440.0 * (2.0 ^ ((midi - 69).to_real / 12.0))
  ensure
    positive: result &amp;gt; 0.0
  end

  transposed(by: Interval): Pitch
  require
    stays_in_range: midi + by.semitones &amp;gt;= LOWEST
      and midi + by.semitones &amp;lt;= HIGHEST
  do
    result := create Pitch.from_midi(midi + by.semitones)
  ensure
    moved_by: result.midi - midi = by.semitones
  end

  interval_to(other: Pitch): Interval
  do
    result := create Interval.make(other.midi - midi)
  ensure
    lands_on_other: transposed(result) = other
  end

  compare(other: Any): Integer
  do
    if convert other to p: Pitch then
      if midi &amp;lt; p.midi then
        result := -1
      elseif midi &amp;gt; p.midi then
        result := 1
      else
        result := 0
      end
    end
  end

  to_string: String do
    result := name()
  end

invariant
  in_midi_range: midi &amp;gt;= LOWEST and midi &amp;lt;= HIGHEST
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things are worth slowing down for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The invariant and the precondition say the same thing, on purpose.&lt;/strong&gt; The invariant is the class&amp;rsquo;s promise: no &lt;code&gt;Pitch&lt;/code&gt; anywhere in the program is outside 0..127. The precondition on &lt;code&gt;from_midi&lt;/code&gt; is the caller&amp;rsquo;s side of that bargain: don&amp;rsquo;t ask for one that is. If a caller breaks the rule, the error blames the caller, right at the call site:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;&amp;gt; create Pitch.from_midi(128)
Error: Precondition violation: in_midi_range
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;named&lt;/code&gt; speaks the musician&amp;rsquo;s language and checks it in the musician&amp;rsquo;s terms.&lt;/strong&gt; The range rule becomes three clauses about octaves: an octave must be -1..9, nothing goes below C-1, and nothing goes above G9. Each clause has a name, so a failure tells you which rule you broke, not just that some arithmetic went out of bounds. Note &lt;code&gt;one_letter&lt;/code&gt;: &lt;code&gt;&quot;CDEFGAB&quot;.contains(&quot;&quot;)&lt;/code&gt; is true, so without the length check an empty string would slip through.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Postconditions document the arithmetic.&lt;/strong&gt; &lt;code&gt;octave&lt;/code&gt; uses integer division, and its postcondition says exactly what that division must get right: put the octave and the pitch class back together and you get the MIDI number again. A reader who doubts &lt;code&gt;midi / 12 - 1&lt;/code&gt; does not have to work it out. The class checks it every time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Comparable&lt;/code&gt; buys ordering.&lt;/strong&gt; Inheriting it and defining &lt;code&gt;compare&lt;/code&gt; gives &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;gt;=&lt;/code&gt; on pitches. We will lean on that when we check that a melody stays inside an instrument&amp;rsquo;s range.&lt;/p&gt;
&lt;p&gt;Try it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;&amp;gt; let c4 := create Pitch.named(&amp;quot;C&amp;quot;, 0, 4)
&amp;gt; c4.midi
60
&amp;gt; let a4 := create Pitch.named(&amp;quot;A&amp;quot;, 0, 4)
&amp;gt; a4.frequency
440.0
&amp;gt; c4 &amp;lt; a4
true
&amp;gt; c4 = create Pitch.from_midi(60)
true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;interval-distance-and-direction&quot;&gt;Interval: distance and direction&lt;/h2&gt;
&lt;p&gt;An interval is a signed number of semitones. Up a fifth is +7; down a minor third is -3. Like &lt;code&gt;Pitch&lt;/code&gt;, it is a value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;class Interval
  note &amp;quot;A signed distance between two pitches, in semitones.&amp;quot;
inherit Comparable

feature
  once semitones: Integer

create
  make(n: Integer)
    require
      within_midi_span: n &amp;gt;= -127 and n &amp;lt;= 127
    do
      semitones := n
    end

feature
  UNISON = create Interval.make(0)
  MINOR_SECOND = create Interval.make(1)
  MAJOR_SECOND = create Interval.make(2)
  MINOR_THIRD = create Interval.make(3)
  MAJOR_THIRD = create Interval.make(4)
  PERFECT_FOURTH = create Interval.make(5)
  PERFECT_FIFTH = create Interval.make(7)
  OCTAVE = create Interval.make(12)

  is_ascending(): Boolean do result := semitones &amp;gt; 0 end
  is_descending(): Boolean do result := semitones &amp;lt; 0 end

  -- The same distance in the other direction.
  reversed(): Interval
  do
    result := create Interval.make(-semitones)
  ensure
    opposite: result.semitones = -semitones
  end

  plus(other: Interval): Interval
  alias &amp;quot;+&amp;quot;
  require
    stays_in_span: (semitones + other.semitones).abs &amp;lt;= 127
  do
    result := create Interval.make(semitones + other.semitones)
  ensure
    adds_up: result.semitones = semitones + other.semitones
  end

  -- Folded into one octave, as an upward step of 0..11 semitones.
  -- A tenth (16) folds to a major third (4).
  simple: Interval
  do
    result := create Interval.make(semitones % 12)
  ensure
    within_octave: result.semitones &amp;gt;= 0 and result.semitones &amp;lt;= 11
    same_class: (semitones - result.semitones) % 12 = 0
  end

  compare(other: Any): Integer
  do
    if convert other to i: Interval then
      if semitones &amp;lt; i.semitones then
        result := -1
      elseif semitones &amp;gt; i.semitones then
        result := 1
      else
        result := 0
      end
    end
  end

  to_string: String
  do
    result := &amp;quot;Interval(&amp;quot; + semitones + &amp;quot;)&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;plus&lt;/code&gt; is bound to &lt;code&gt;+&lt;/code&gt; with &lt;code&gt;alias&lt;/code&gt;, so &lt;code&gt;Interval.OCTAVE + Interval.PERFECT_FIFTH&lt;/code&gt; reads like music theory and is still exactly a call to &lt;code&gt;plus&lt;/code&gt;, precondition and all.&lt;/p&gt;
&lt;p&gt;If you look carefully at &lt;code&gt;simple&lt;/code&gt; you might see a problem: when we ask whether a note belongs to a chord, we care about the interval from the root folded into one octave. The body &lt;code&gt;semitones % 12&lt;/code&gt; looks right, and for upward intervals it is. Try a downward one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;&amp;gt; let tenth := create Interval.of(16)
&amp;gt; tenth.simple
Interval(4)
&amp;gt; let down := create Interval.of(-15)
&amp;gt; down.simple
Error: Postcondition violation: within_octave
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nex&amp;rsquo;s &lt;code&gt;%&lt;/code&gt; keeps the sign of the dividend, as Java does, so &lt;code&gt;-15 % 12&lt;/code&gt; is &lt;code&gt;-3&lt;/code&gt;, not &lt;code&gt;9&lt;/code&gt;. Without the postcondition, &lt;code&gt;simple&lt;/code&gt; would quietly return &lt;code&gt;Interval(-3)&lt;/code&gt;, and the chord check two posts from now would decide that a perfectly good chord tone was wrong. With it, the routine that broke its promise is named at the moment it breaks it. The fix is one line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;      result := create Interval.make((semitones % 12 + 12) % 12)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;same_class&lt;/code&gt; clause is as important as &lt;code&gt;within_octave&lt;/code&gt;. A body that returned &lt;code&gt;Interval.UNISON&lt;/code&gt; every time would satisfy the range check. &lt;code&gt;same_class&lt;/code&gt; rules that out: the folded interval must differ from the original by whole octaves.&lt;/p&gt;
&lt;h3 id=&quot;pitch-meets-interval&quot;&gt;Pitch meets Interval&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s add two methods to &lt;code&gt;Pitch&lt;/code&gt; to tie it together with &lt;code&gt;Interval&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;  transposed(by: Interval): Pitch
  require
    stays_in_range: midi + by.semitones &amp;gt;= LOWEST
      and midi + by.semitones &amp;lt;= HIGHEST
  do
    result := create Pitch.from_midi(midi + by.semitones)
  ensure
    moved_by: result.midi - midi = by.semitones
  end

  interval_to(other: Pitch): Interval
  do
    result := create Interval.make(other.midi - midi)
  ensure
    lands_on_other: transposed(result) = other
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The precondition on &lt;code&gt;transposed&lt;/code&gt; is the cure for the second problem from the start of the post. Transposing a melody past the top of the range now fails on the line that transposes, not inside the renderer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;&amp;gt; let top_c := create Pitch.named(&amp;quot;C&amp;quot;, 0, 9)
&amp;gt; top_c.transposed(Interval.OCTAVE)
Error: Precondition violation: stays_in_range
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The postcondition on &lt;code&gt;interval_to&lt;/code&gt; is a different kind of promise. It does not restate the body; it ties two features together. Whatever &lt;code&gt;interval_to&lt;/code&gt; returns, &lt;code&gt;transposed&lt;/code&gt; must undo it. If either routine changes later, this one line keeps them in sync.&lt;/p&gt;
&lt;h3 id=&quot;why-not-a-mutable-transpose&quot;&gt;Why not a mutable &lt;code&gt;transpose&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;It is tempting to write a command instead, one that changes the pitch in place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;  transpose(by: Interval)
  require
    stays_in_range: midi + by.semitones &amp;gt;= LOWEST
      and midi + by.semitones &amp;lt;= HIGHEST
  do
    midi := midi + by.semitones
  ensure
    moved_by: midi = old midi + by.semitones
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The contract is fine; &lt;code&gt;old midi&lt;/code&gt; is exactly the right tool for a command. Nex rejects the class anyway, because &lt;code&gt;midi&lt;/code&gt; is a &lt;code&gt;once&lt;/code&gt; field. This is a deliberate design choice. If pitches could change, then a &lt;code&gt;Phrase&lt;/code&gt; holding the same &lt;code&gt;Pitch&lt;/code&gt; object in two places would have both notes move when you transpose one. Values that cannot change can be shared freely, and that will make every collection class later in the series simpler. We will have appropriate use for &lt;code&gt;old&lt;/code&gt; later when we implement &lt;code&gt;Phrase&lt;/code&gt; and &lt;code&gt;Voice&lt;/code&gt; which are things that do change.&lt;/p&gt;
&lt;h2 id=&quot;making-it-sound&quot;&gt;Making it sound&lt;/h2&gt;
&lt;p&gt;A major scale is a pattern of steps: whole, whole, half, whole, whole, whole, half. In our terms that is a list of intervals, and building the scale is a walk from the tonic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;let whole := Interval.MAJOR_SECOND
let half := Interval.MINOR_SECOND
let major_steps: Array[Interval] := [whole, whole, half, whole, whole, whole, half]

let scale: Array[Pitch] := [create Pitch.named(&amp;quot;C&amp;quot;, 0, 4)]
across major_steps as step do
  scale.add(scale.get(scale.length - 1).transposed(step))
end

across scale as p do print(p.name) end
-- C4 D4 E4 F4 G4 A4 B4 C5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To hear it, we hand the pitches to the audio library. This is the only place in this part where a &lt;code&gt;Pitch&lt;/code&gt; turns back into an &lt;code&gt;Integer&lt;/code&gt;, and it does so at the last possible moment, inside one small function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;intern audio/Midi_Song

function play(pitches: Array[Pitch], beats_each: Integer)
require
  something_to_play: pitches.length &amp;gt; 0
  positive_length: beats_each &amp;gt; 0
do
  let ticks_per_beat := 4
  let ticks := beats_each * ticks_per_beat
  let score := create Midi_Score.make(ticks_per_beat)
  score.set_instrument(0, Midi.PIANO, 0)
  from let k := 0 until k = pitches.length do
    score.add_note(0, pitches.get(k).midi, 90, k * ticks, ticks)
    k := k + 1
  end
  let song := create Midi_Song.from_score(score)
  song.play()
  from until song.is_stopped() do
    song.update()
    sleep(20)
  end
  song.close()
end

play(scale, 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice what can no longer go wrong at the &lt;code&gt;add_note&lt;/code&gt; call. The pitch argument comes from a &lt;code&gt;Pitch&lt;/code&gt;, so it is in range by the class invariant; &lt;code&gt;add_note&lt;/code&gt;&amp;rsquo;s own &lt;code&gt;valid_pitch&lt;/code&gt; check can never fire from here. The velocity and pitch can still be swapped by hand in this one function, but there is now exactly one place to look. In a later part of the series we will replace this function with a proper &lt;code&gt;Renderer&lt;/code&gt; class and a contract that checks the score against the model.&lt;/p&gt;
&lt;p&gt;No speakers, or running on a server? Set &lt;code&gt;NEX_AUDIO=off&lt;/code&gt;. The library plays silently and the contracts are checked all the same.&lt;/p&gt;
&lt;h2 id=&quot;what-the-contracts-bought-us&quot;&gt;What the contracts bought us&lt;/h2&gt;
&lt;p&gt;Two small classes, and already three kinds of protection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Types keep ideas apart.&lt;/strong&gt; A &lt;code&gt;Pitch&lt;/code&gt; cannot be passed where a velocity or a channel is expected. No contract could catch the swapped arguments from the start of this post; a class does it for free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preconditions put the blame at the call.&lt;/strong&gt; An out-of-range pitch or transposition fails on the line that asked for it, with a rule name that says what was wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Postconditions catch the code we were sure of.&lt;/strong&gt; &lt;code&gt;simple&lt;/code&gt; was one line, it looked right, and it was wrong for every descending interval. The postcondition found it the first time a descending interval came through.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the take away is: types say what a thing &lt;em&gt;is&lt;/em&gt;; contracts say what must be &lt;em&gt;true&lt;/em&gt; of it. Each catches what the other cannot.&lt;/p&gt;
&lt;h2 id=&quot;next-duration-and-meter&quot;&gt;Next: Duration and Meter&lt;/h2&gt;
&lt;p&gt;Pitch is half of a note. Part 2 adds the other half: how long it lasts. We will represent durations as exact fractions of a whole note rather than MIDI ticks, give &lt;code&gt;Meter&lt;/code&gt; an invariant that every bar is full, and meet the first contract that a test suite would almost certainly miss.&lt;/p&gt;
&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try it yourself&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;Pitch.named_flat&lt;/code&gt; that spells pitches with flats (Db rather than C#). What should its postcondition promise about &lt;code&gt;midi&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Give &lt;code&gt;Interval&lt;/code&gt; a &lt;code&gt;name&lt;/code&gt; query that returns &amp;ldquo;M3&amp;rdquo;, &amp;ldquo;P5&amp;rdquo; and so on for simple intervals. Which precondition does it need, and could &lt;code&gt;simple&lt;/code&gt; remove the need for one?&lt;/li&gt;
&lt;li&gt;Remove the &lt;code&gt;same_class&lt;/code&gt; clause from &lt;code&gt;simple&lt;/code&gt; and write a wrong body that still passes &lt;code&gt;within_octave&lt;/code&gt;. How many tests would you need to catch it instead?&lt;/li&gt;
&lt;/ol&gt;</content>
  </entry>
  <entry>
    <title>Handling command-line arguments in Nex</title>
    <link href="https://schemer.in/nex/blog/command-line-arguments.html"/>
    <id>https://schemer.in/nex/blog/command-line-arguments.html</id>
    <published>2026-10-10T00:00:00Z</published>
    <updated>2026-10-10T00:00:00Z</updated>
    <summary>A Nex program reads its arguments from the Process object. Here is how to get at them, parse them into usable formats, and deal with invalid input gracefully.</summary>
    <category term="how-to"/>
    <category term="cli"/>
    <category term="contracts"/>
    <content type="html" xml:base="https://schemer.in/nex/blog/">&lt;p&gt;A Nex program can ask for its command-line arguments via an instance of the &lt;code&gt;Process&lt;/code&gt; class.
&lt;code&gt;create Process&lt;/code&gt; gives you the process you are running in, and &lt;code&gt;command_line()&lt;/code&gt; returns its arguments as an &lt;code&gt;Array[String]&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;let proc: Process := create Process
let args: Array[String] := proc.command_line()
let con: Console := create Console

con.print_line(&amp;quot;got &amp;quot; + args.length + &amp;quot; argument(s)&amp;quot;)
across args as a do
  con.print_line(&amp;quot;  &amp;quot; + a)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save that as &lt;code&gt;args.nex&lt;/code&gt; and run it with a few arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nex args.nex report.txt &amp;quot;two words&amp;quot; --top 3
got 4 argument(s)
  report.txt
  two words
  --top
  3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things to notice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The array holds only your arguments.&lt;/strong&gt; It starts after the file name, so there is no &lt;code&gt;argv[0]&lt;/code&gt; to skip, and with no arguments it is empty.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The shell has already split the words.&lt;/strong&gt; &lt;code&gt;&quot;two words&quot;&lt;/code&gt; arrives as one element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything is a &lt;code&gt;String&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;3&lt;/code&gt; is text until you call &lt;code&gt;to_integer&lt;/code&gt; on it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;reserved-runtime-flags&quot;&gt;Reserved runtime flags&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;nex&lt;/code&gt; takes a few options of its own, such as &lt;code&gt;--interpret&lt;/code&gt;, &lt;code&gt;--skip-contracts&lt;/code&gt; and &lt;code&gt;--classpath&lt;/code&gt;. It removes them from the command line wherever they appear, so your program never sees them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nex args.nex one --interpret two --skip-contracts three
got 3 argument(s)
  one
  two
  three
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As those names are taken by the runtime, you cannot reuse them as your own options.&lt;/p&gt;
&lt;h2 id=&quot;a-small-tool&quot;&gt;A small tool&lt;/h2&gt;
&lt;p&gt;Here is a complete program that greets someone, with an optional repeat count and an option to shout.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;-- greet: say hello from the command line.
--
--   nex greet.nex &amp;lt;name&amp;gt; [--times n] [--shout]

class Options
feature
  name: String
  times: Integer
  shout: Boolean
create
  make(a_name: String, a_times: Integer, a_shout: Boolean)
  require
    has_name: name.length &amp;gt; 0
    positive_times: times &amp;gt; 0
  do
    name := a_name
    times := a_times
    shout := a_shout
  end
invariant
  has_name: name.length &amp;gt; 0
  positive_times: times &amp;gt; 0
end

function fail(message: String) do
  let con: Console := create Console
  con.error(&amp;quot;greet: &amp;quot; + message)
  con.error(&amp;quot;usage: nex greet.nex &amp;lt;name&amp;gt; [--times n] [--shout]&amp;quot;)
  exit(2)
end

function to_count(text: String): Integer
do
  result := text.to_integer()
  if result &amp;lt;= 0 then
    fail(&amp;quot;--times must be at least 1, got &amp;quot; + text)
  end
rescue
  fail(&amp;quot;--times needs a whole number, got &amp;quot; + text)
end

function parse(args: Array[String]): Options
do
  let name: String := &amp;quot;&amp;quot;
  let times: Integer := 1
  let shout: Boolean := false
  let i: Integer := 0
  from
    i := 0
  until
    i = args.length
  do
    let arg: String := args.get(i)
    if arg = &amp;quot;--shout&amp;quot; then
      shout := true
    elseif arg = &amp;quot;--times&amp;quot; then
      if i + 1 = args.length then
        fail(&amp;quot;--times needs a value&amp;quot;)
      end
      i := i + 1
      times := to_count(args.get(i))
    elseif arg.starts_with(&amp;quot;--&amp;quot;) then
      fail(&amp;quot;unknown option &amp;quot; + arg)
    elseif name = &amp;quot;&amp;quot; then
      name := arg
    else
      fail(&amp;quot;unexpected argument &amp;quot; + arg)
    end
    i := i + 1
  end
  if name = &amp;quot;&amp;quot; then
    fail(&amp;quot;missing &amp;lt;name&amp;gt;&amp;quot;)
  end
  result := create Options.make(name, times, shout)
end

function main() do
  let proc: Process := create Process
  let opts: Options := parse(proc.command_line())
  let con: Console := create Console
  let greeting: String := &amp;quot;Hello, &amp;quot; + opts.name + &amp;quot;!&amp;quot;
  if opts.shout then
    greeting := greeting.to_upper()
  end
  let n: Integer := 0
  from
    n := 0
  until
    n = opts.times
  do
    con.print_line(greeting)
    n := n + 1
  end
end

main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It behaves the way you would expect a command-line tool to behave:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nex greet.nex Ada
Hello, Ada!

$ nex greet.nex Ada --times 3 --shout
HELLO, ADA!
HELLO, ADA!
HELLO, ADA!

$ nex greet.nex Ada --times zero
greet: --times needs a whole number, got zero
usage: nex greet.nex &amp;lt;name&amp;gt; [--times n] [--shout]

$ nex greet.nex Ada --loud
greet: unknown option --loud
usage: nex greet.nex &amp;lt;name&amp;gt; [--times n] [--shout]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program is short, but there are some patterns worth copying.&lt;/p&gt;
&lt;h3 id=&quot;parse-once-at-the-edge&quot;&gt;Parse once, at the edge&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; does not pass the raw array around. It calls &lt;code&gt;parse&lt;/code&gt; once and from then on works with an &lt;code&gt;Options&lt;/code&gt; object. No other part of the program looks at strings, checks whether an index is in range, or wonders whether &lt;code&gt;times&lt;/code&gt; was converted yet.&lt;/p&gt;
&lt;h3 id=&quot;bad-input-is-not-a-bug&quot;&gt;Bad input is not a bug&lt;/h3&gt;
&lt;p&gt;Someone typing &lt;code&gt;--times zero&lt;/code&gt; has made a mistake, but the &lt;em&gt;program&lt;/em&gt; is fine. So &lt;code&gt;parse&lt;/code&gt; handles it with ordinary code: it prints a message that names the problem, prints the usage line, and stops. &lt;code&gt;to_integer()&lt;/code&gt; raises a &lt;code&gt;Conversion_Error&lt;/code&gt; on text that is not a number, and the &lt;code&gt;rescue&lt;/code&gt; clause in &lt;code&gt;to_count&lt;/code&gt; turns that into the same kind of message.&lt;/p&gt;
&lt;p&gt;Errors go to &lt;code&gt;Console.error&lt;/code&gt;, which writes to standard error, so they stay out of the way when the output is piped somewhere. &lt;code&gt;exit(2)&lt;/code&gt; sets the exit status. By convention &lt;code&gt;0&lt;/code&gt; means success and &lt;code&gt;2&lt;/code&gt; means the command was used wrongly, which lets a shell script tell the two apart.&lt;/p&gt;
&lt;h3 id=&quot;a-broken-parser-is-a-bug&quot;&gt;A broken parser is a bug&lt;/h3&gt;
&lt;p&gt;The contracts on &lt;code&gt;Options&lt;/code&gt; do a different job. They are not there to check the user. They state what the rest of the program may assume: there is a name, and &lt;code&gt;times&lt;/code&gt; is positive. &lt;code&gt;main&lt;/code&gt; relies on that when it loops &lt;code&gt;opts.times&lt;/code&gt; times without checking anything.&lt;/p&gt;
&lt;p&gt;That makes the constructor a checkpoint for the parser itself. Suppose a later edit drops the &lt;code&gt;result &amp;lt;= 0&lt;/code&gt; test from &lt;code&gt;to_count&lt;/code&gt;. Nothing about the type of &lt;code&gt;times&lt;/code&gt; changes, and the program still compiles. But the first run with a bad value stops at the boundary:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nex greet.nex Ada --times 0
Error: Precondition violation: positive_times
  The call at line 71, in parse does not meet the precondition `positive_times` of Options.make.
  called from line 76, in main
  called from line 93
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The message names the clause and blames the caller, &lt;code&gt;parse&lt;/code&gt;, which is where the missing check belongs. A user should never see this message. If one does, it is telling you about a hole in your validation, not about their typing.&lt;/p&gt;
&lt;h2 id=&quot;file-arguments-and-relative-paths&quot;&gt;File arguments and relative paths&lt;/h2&gt;
&lt;p&gt;One thing will surprise you the first time you take a file name as an argument. The &lt;code&gt;nex&lt;/code&gt; launcher starts your program from its own installation directory, so a relative path like &lt;code&gt;report.txt&lt;/code&gt; does not resolve against the directory you ran the command in. The launcher records that directory in the environment variable &lt;code&gt;NEX_USER_DIR&lt;/code&gt;, and you can use it to resolve the path yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nex&quot;&gt;function resolve(proc: Process, raw: String): String
do
  let base: ?String := proc.getenv(&amp;quot;NEX_USER_DIR&amp;quot;)
  if raw.starts_with(&amp;quot;/&amp;quot;) or base = nil or base = &amp;quot;&amp;quot; then
    result := raw
  else
    result := base + &amp;quot;/&amp;quot; + raw
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The check for a missing &lt;code&gt;NEX_USER_DIR&lt;/code&gt; is relevant for the next section: a compiled program does not go through the launcher, so the variable is not set and relative paths already work.&lt;/p&gt;
&lt;h2 id=&quot;shipping-it-as-a-jar&quot;&gt;Shipping it as a jar&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;nex compile jvm&lt;/code&gt; turns the program into a standalone jar. The arguments reach it the same way, so nothing in the code changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ nex compile jvm greet.nex
$ java -jar greet.jar Grace --times 2
Hello, Grace!
Hello, Grace!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Process&lt;/code&gt; can also read the environment and start child processes. The full list is in the &lt;a href=&quot;../docs/ref/system-classes.html&quot;&gt;system classes reference&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;require&lt;/code&gt;, &lt;code&gt;ensure&lt;/code&gt; and &lt;code&gt;invariant&lt;/code&gt; are new to you, the &lt;a href=&quot;../tutorial.html&quot;&gt;tour of Nex&lt;/a&gt; introduces them in a few minutes.&lt;/li&gt;
&lt;/ul&gt;</content>
  </entry>
</feed>
