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 ]