[Haskell-cafe] Proposal: Non-recursive let

Evan Laforge qdunkan at gmail.com
Sat Jul 20 21:36:33 CEST 2013

On Tue, Jul 16, 2013 at 5:20 PM, Richard A. O'Keefe <ok at cs.otago.ac.nz> wrote:
> Brian Marick sent me a couple of his stickers.
> The one I have on my door reads "to be less wrong than yesterday".
> The other one I keep free to bring out and wave around:
>         "An example would be handy about now."

Just by coincidence, I recently wrote this:

midi_to_pitch :: TheoryFormat.Format -> Maybe Pitch.Key
    -> Pitch.NoteNumber -> Maybe Theory.Pitch
midi_to_pitch fmt key nn =
    either (const Nothing) Just $
        TheoryFormat.fmt_to_absolute fmt key pitch
    -- TODO if I support frac I can use this for twelve too
    (semis, _frac) = properFraction (Pitch.nn_to_double nn)
    Theory.Pitch oct (Theory.Note pc accs) =
        Theory.semis_to_pitch_sharps TheoryFormat.piano_layout
            (Theory.nn_to_semis semis)
    (oct1, pc1) = adjust_octave (TheoryFormat.fmt_pc_per_octave fmt) 7 oct pc
    pitch = Theory.Pitch oct1 (Theory.Note pc1 accs)

kbd_to_pitch :: Theory.PitchClass -> Pitch.Octave -> Theory.PitchClass
    -> Theory.Accidentals -> Theory.Pitch
kbd_to_pitch pc_per_octave oct pc accidentals =
    Theory.Pitch (add_oct + oct1) (Theory.Note pc2 accidentals)
    (oct1, pc1) = adjust_octave pc_per_octave 10 oct pc
    -- If the scale is shorter than the kbd, go up to the next octave on the
    -- same row.
    (add_oct, pc2) = pc1 `divMod` pc_per_octave

adjust_octave :: Theory.PitchClass -> Theory.PitchClass -> Pitch.Octave
    -> Theory.PitchClass -> (Pitch.Octave, Theory.PitchClass)
adjust_octave pc_per_octave kbd_per_octave oct pc = (oct2, pc2)
    rows = ceiling $ fromIntegral pc_per_octave / fromIntegral kbd_per_octave
    (oct2, offset) = oct `divMod` rows
    pc2 = offset * kbd_per_octave + pc

Also, fragments like this are fairly common:

        Right pitch_ ->
            let pitch = pitch_
                    { Theory.pitch_note = (Theory.pitch_note pitch_)
                        { Theory.note_accidentals = 0 }
                accs = Theory.pitch_accidentals pitch_
            in Just $ ScaleDegree.scale_degree_just
                (smap_named_intervals smap)
                (smap_accidental_interval smap ^^ accs)
                (pitch_nn smap pitch) (pitch_note fmt pitch)

My convention is when I have a a series of transformations that have
to be named for whatever reason, I suffix with numbers.  When I have a
function argument (or case-bound variable as in this case) that has to
be "cooked" before it can be used, I suffix it with _.  That way code
inside the function is not likely to accidentally use the un-cooked
version (this has happened when I left the uncooked version normal and
suffixed the cooked version with a 1 or something).  In monadic style,
I use 'x <- return $ f x' a fair amount.

I'm just sending this to point out that it actually is a real issue.
And on the odd chance that someone wants to tell me that I'm doing it
wrong and here's a better idea :)  I'm not about to import Monad.State
and wrap the whole expression in a state call just to replace one or
two variables, both the syntactic overhead and the "conversion"
overhead make it not worth it.

However, I'm also not agitating for a non-recursive let, I think that
ship has sailed.  Besides, if it were added people would start
wondering about non-recursive where, and it would introduce an
exception to haskell's pretty consistently order-independent
declaration style.

More information about the Haskell-Cafe mailing list