Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I attach optional attributes to values?

I want to store a list of "things" which can have some optional extra attributes attached to them. Each thing can have one or more attributes. And different attributes are of different types.

I want to be able to create literal lists of these things concisely in code. But I'm having trouble seeing how to get this past the type system because tuples allow mixtures of types but are fixed length, while lists are variable length but one type.

This is a toy example of what I want to be able to do :

things = [
   Thing 1 RED,
   Thing 2 RED LARGE,
   Thing 3 BLUE SMALL,
   Thing 4 SMALL,
   Thing 5 BLUE DOTTED
]

etc.

What's the right way to do this?

like image 929
interstar Avatar asked Dec 08 '22 09:12

interstar


2 Answers

Let's say Chords are collections of Notes.

data Note = A | Bb | B | C | ...

But also with optional sets of Annotations

data Ann = Seventh | Sus2 | Split | ...

We can model Chords thus as

data Chord = Chord { notes :: [Note]
                   , anns :: [Ann]
                   }

We can build an entire vocabulary this way

maj :: Note -> Chord
ann :: Ann -> Chord -> Chord
transpose :: Int -> Note -> Note
transposeChord :: Int -> Chord -> Chord

And then build our list like so

chords = [
  ann Seventh (maj C)
, ann Split (ann Sus2 (maj A))
]
like image 136
J. Abrahamson Avatar answered Dec 15 '22 18:12

J. Abrahamson


Basically, rather than storing the attributes as given, you should store the resultant properties of a chord with these attributes. One simple (but not really nice, musically) solution would be, storing only the final pitches:

newtype Pitch = Pitch {midiNote :: Int}

a, as, bb, b, bs, c, cs, db, d, ds, eb, e, es, f, fs, gb, g, gs, ab :: Pitch
[ a, as,bb, b,bs, c,cs,db, d,ds,eb, e,es, f,fs,gb, g,gs,ab] = map Pitch
 [55,56,56,57,58,58,59,59,60,61,61,62,63,63,64,64,65,66,66]

type Chord = [Pitch]

minor :: Pitch -> Chord
minor (Pitch fund) = map (Pitch . (fund+)) [0, 3, 7]

seventh :: Pitch -> Chord
seventh (Pitch fund) = map (Pitch . (fund+)) [0, 4, 7, 10]

spread :: Chord -> Chord
spread = sort
 . zipWith (\octShift (Pitch note) -> Pitch $ note + 12 * octShift) $ cycle [0,1]

To be used as e.g.

chords :: [Chord]
chords = [ minor e, seventh d, minor e, minor a, seventh b, spread $ minor e ]

A more sophisticated approach might actually store the information about a chord in a more musically meaningful way:

data Chord = Chord { fundamental :: Pitch
                   , gender :: Maybe ChordGender
                   , ExtraNotes :: [AddNote]
                   , OctaveShifts :: [Int]
                   }

data ChordGender = Major | Minor
data AddNote = AddNote { roughInterval :: Int, intervalIsMajor :: Bool }

major :: Pitch -> Chord
major fund = Chord fund (Just Major) [] []

sus4 :: Pitch -> Chord
sus4 fund = Chord fund Nothing [AddNote 4 False] []

spread :: Chord -> Chord
spread ch@(Chord _ _ _ shifts)
  = ch{shifts = cycle [0,1]}

This can be used in much the same way, but is more versatile.

If you don't like giving the attributes as prefix functions, you can do as the diagrams package, with

infixl 8 #
(#) :: a -> (a -> b) -> b
(#) = flip ($)

to write

chords = [ c # major
         , g # sus4
         , g # major
         , a # minor
         , f # major # spread
         , g # sus4  # spread
         , g # major # spread
         , c # major # spread
         ]
like image 45
leftaroundabout Avatar answered Dec 15 '22 16:12

leftaroundabout