Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rethink Chord toList functions so their behavior is more intuitive. #64

Open
cianoc opened this issue Jul 27, 2021 · 6 comments
Open

Rethink Chord toList functions so their behavior is more intuitive. #64

cianoc opened this issue Jul 27, 2021 · 6 comments

Comments

@cianoc
Copy link
Member

cianoc commented Jul 27, 2021

>>> toList $ scale c dorian
[c]
>>> toList $ chord c minor
[c]

Not sure what the correct behavior should be here, but this feels wrong. I feel it should probably return the generator set:

>>> toList $ scale c dorian
[c, d, eb, f, g, a, bb]
>>> toList $ chord c minor
[c, eb, g]

Obviously this may have repercussions I may not be aware of, which is why I'm raising here, but maybe it would help with this:

-- TODO semantically suspect!
scaleToList :: AffinePair v p => Scale v p -> [p]

and

-- TODO semantically suspect!
chordToList :: AffinePair v p => Chord v p -> [p]

As these could be replaced by toList which feels a little more general and consistent.

I'd be happy to do a PR for this if it makes sense.

@hanshoglund
Copy link
Member

Sadly this won't work, as the AffinePair constraint is not compatible with the types of foldMap/toList/traverse. You can however use the existing functions for this:

>>> scaleToList $ scale c dorian
[c, d, eb, f, g, a, bb]

>>> chordToList $ chord c minorTriad 
[c,eb,g]

Thinking over this I regret marking these as "suspect": they are perfectly valid folds, just not compatible with the standard library definitions (which require the traversed type to be fully parametric). Some things that could make sense:

  • Remove the "suspect" warning
  • Document, test and possible rename the ...toList functions
  • Optionally provide traversals.

The last option is a mainly a question on how general we want to go, e.g. compare

generator :: Traversal' (Chord Interval Pitch) Pitch
generator :: AffinePair v p => Traversal' (Chord v p) p
generator :: AffinePair v p => Traversal' (Chord v p) p
generator :: (AffinePair v p, AffinePair v p') => Traversal (ScaleChord o r v p) (ScaleChord o r v p')

@cianoc
Copy link
Member Author

cianoc commented Aug 4, 2021

I guess the thing that mostly threw me here was that toList isn't really documented. Maybe if there's an explanation of what it does, maybe with why it could be useful, that could be sufficient. I think also saying that you probably want to use scaleToList, or chordToList instead would be useful.

Renaming it could help, though honestly not really understanding its intended purpose I couldn't say what it should be used for.

@hanshoglund
Copy link
Member

hanshoglund commented Aug 5, 2021

Note that toList is from standard library (defined for the Foldableclass). As always when using these very generic classes is that though the behavior of instances might make sense theoretically, it is not always "obvious".

In the User Guide I tend to avoid type-classes in favor of explicit module prefixes, e.g. Voice.map (+ 1) rather than fmap (+ 1). We could do something similar here, e.g. having ScaleChord.toList, Scale.toList, Chord.toList etc. To support this, we would move ScaleChord to its own module, and the provide separate Scale and Chord modules (mostly reexporting).

@cianoc
Copy link
Member Author

cianoc commented Aug 6, 2021

Yeah I have mixed feelings about usual type classes the way that Haskell does. It tends to make libraries quite opaque even if you know the typeclasses.

As an aside, Voice.map doesn't work when you use the prelude. So actually I've been using fmap. I actually assumed that the documentation was just lagging the library.

@cianoc cianoc added bug and removed question labels Oct 12, 2021
@cianoc cianoc changed the title Foldable instance for ScaleChord 'feels' wrong Rethink Chord toList functions so their behavior is more intuitive. Oct 12, 2021
@cianoc
Copy link
Member Author

cianoc commented Oct 12, 2021

Just renaming this to reflect the discussion above. I feel a couple of things here are low hanging fruit (e.g. remove suspect comments and documenting what toList actually does in this case).

I don't feel I have enough familiarity with the library yet to comment on the other suggestions.

@hanshoglund
Copy link
Member

hanshoglund commented Jan 22, 2023

This also affects the Voicing type.

A straightforward solution is to remove the Foldable/Traversable instances from the types, or move them to newtype wrappers if desired.

The documentation should probably also explain that the recommended way to convert a chord to a list of pitches is to:

  1. Voice the chord using one of the functions in Music.Pitch.Voicing.
  2. Extract the pitch list with getVoiced.

We could have wrappers for the most common cases in Music.Pitch.Scale, e.g.

toNonEmptyCloseVoicing :: Chord v p -> NonEmpty p
toListCloseVoicing :: Chord v p -> [p]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants