Skip to content

Housekeeping#25

Open
edwisdom wants to merge 12 commits intodevelopfrom
housekeeping
Open

Housekeeping#25
edwisdom wants to merge 12 commits intodevelopfrom
housekeeping

Conversation

@edwisdom
Copy link
Owner

I knocked down all the TODO's that were littered throughout the repo except for 2, adding smart constructors and adjusting downstream code accordingly. All the changes were fairly local.

Highlights

  1. Mode has a smart smart constructor now
  2. Aug and dim interval qualities can only be up to 11 now -- the interval math doesn't get affected by too many maybes because with the full interval, we can just be cyclical, i.e. an 11-augmented fifth plus 1 semitone is just a perfect fifth.
  3. Added non-heptatonic modes (can't not have the blues scale, even if it's the first release lol...)

(I know I've neglected to add docs for a couple straightforward utility functions, sorry)

Things Left as TODO

  1. Adding "Maj" and "Min" to the parser, since it directly relates to More organized/robust parsing with Alex and Happy - Preliminary Work #6
  2. Giving the user warnings when they're dumb, again because that's more for More organized/robust parsing with Alex and Happy - Preliminary Work #6

…m or Aug get args that aren't > 0 and < 12, changed interval calculations to match
…oIntervals function so that it easily allows for that
… -- the change proposed wasn't unambiguously better and creates a lot of work
@edwisdom edwisdom self-assigned this Sep 13, 2020
@edwisdom edwisdom added the refactor Code shape (but not semantics) changed label Sep 13, 2020
@ChrisEPhifer
Copy link
Collaborator

Sorry I'm so late getting to this; I'll try to do some review tonight, I've been swamped with work stuff / had a bit of an emergency this past weekend.

Copy link
Collaborator

@ChrisEPhifer ChrisEPhifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall there's a lot of good stuff here :) I like some of the function improvements, they're certainly more idiomatic.

My main complaint is the functions that have types like Maybe Quality -> Maybe Quality; it's a bit odd to see a type like this in the wild. Infinitely more useful, I think, would be functions that look like Quality -> Maybe Quality: These can be used very naturally in monadic contexts, and are built to indicate "I'm sure the thing I'm given is well-formed, but I might be producing an ill-formed result." I'll push some changes that take what you've done here closer to what I think would be idiomatic.

@ChrisEPhifer ChrisEPhifer mentioned this pull request Sep 21, 2020
7 tasks
Copy link
Collaborator

@ChrisEPhifer ChrisEPhifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I left some comments on the changes I made so far; there's a little bit more to do, then you can do a review and knock off whatever else you wanted to get done for this PR, and then I'll do one more review before we merge into develop :)

Comment on lines +38 to +40
instance Show ChordShape where
show c = show (getQuality c) ++ show (getHighestNatural c)
++ concat (show <$> getExtensions c) ++ show (getSus c)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified this per the suggestion I made to your original commits.

import Base.Core.Note
import Base.Core.Quality.CQuality as CQ
import Base.Core.Quality.IQuality as IQ
import Base.Core.Quality.IQuality as IQ hiding (major)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary since I added true smart constructors for the Quality type in this module, that is, functions correspondent to each of the constructors for the type. For the time being, the 'real' constructors are still exposed, but I'll probably commit that fix before we merge this in.

Comment on lines 53 to 66
major :: Quality
major = Major

perfect :: Quality
perfect = Perfect

minor :: Quality
minor = Minor

diminished :: Int -> Maybe Quality
diminished x = if x > 0 && x < 12 then Just $ Diminished x else Nothing

augmented :: Int -> Maybe Quality
augmented x = if x > 0 && x < 12 then Just $ Augmented x else Nothing
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when I say "smart constructors", for a sum type like Quality, this is what I mean: One function per constructor in the data declaration that enforces invariants of that constructor. Your original smart constructor, iQualFrom, would have been appropriate if this was a product type, such as any of the various record types we've introduced so far; since it's a type with variants, you need at least one function per variant.

Comment on lines 78 to 108
raisePerfect :: Quality -> Maybe Quality
raisePerfect Perfect = Just $ Augmented 1
raisePerfect (Augmented x) = augmented $ x + 1
raisePerfect (Diminished 1) = Just Perfect
raisePerfect (Diminished x) = diminished $ x - 1

-- | Given an interval quality, this raises that quality by a semitone
-- assuming that its base quality is Major.
raiseMajor :: Quality -> Quality
raiseMajor Major = Augmented 1
raiseMajor (Augmented x) = Augmented $ x + 1
raiseMajor Minor = Major
raiseMajor (Diminished 1) = Minor
raiseMajor (Diminished x) = Diminished $ x - 1
raiseMajor :: Quality -> Maybe Quality
raiseMajor Major = Just $ Augmented 1
raiseMajor (Augmented x) = augmented $ x + 1
raiseMajor Minor = Just Major
raiseMajor (Diminished 1) = Just Minor
raiseMajor (Diminished x) = diminished $ x - 1

-- | Given an interval quality, this lowers that quality by a semitone
-- assuming that its base quality is Perfect.
lowerPerfect :: Quality -> Quality
lowerPerfect Perfect = Diminished 1
lowerPerfect (Diminished x) = Diminished $ x + 1
lowerPerfect (Augmented 1) = Perfect
lowerPerfect (Augmented x) = Augmented $ x-1
lowerPerfect :: Quality -> Maybe Quality
lowerPerfect Perfect = Just $ Diminished 1
lowerPerfect (Diminished x) = diminished $ x + 1
lowerPerfect (Augmented 1) = Just Perfect
lowerPerfect (Augmented x) = augmented $ x-1

-- | Given an interval quality, this lowers that quality by a semitone
-- assuming that its base quality is Major.
lowerMajor :: Quality -> Quality
lowerMajor Major = Minor
lowerMajor Minor = Diminished 1
lowerMajor (Diminished x) = Diminished $ x + 1
lowerMajor (Augmented 1) = Major
lowerMajor (Augmented x) = Augmented $ x - 1
lowerMajor :: Quality -> Maybe Quality
lowerMajor Major = Just Minor
lowerMajor Minor = Just $ Diminished 1
lowerMajor (Diminished x) = diminished $ x + 1
lowerMajor (Augmented 1) = Just Major
lowerMajor (Augmented x) = augmented $ x - 1 No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite a bit cleaner, no? :)

@ChrisEPhifer
Copy link
Collaborator

Grr. Well, I've got some irritating news: I'm not sure there's a great way to fully protect the Quality type defined in the IQuality module while still being able to use it reasonably where it's needed.

The way I see it, our two options are:

  1. Export a set of predicates that can take the place of pattern-matching
  2. Suck it up, export all the normal constructors alongside the smart constructors, and carefully document the risk of violating invariants when using the normal constructors instead of smart constructors.

I'm slightly leaning towards option 1, since we've been really good about protecting invariants just about everywhere else - it's a bit annoying for us, but results in what I think is a more robust API. I'm also sort of considering end-users here, and it's a little annoying for them, too, if they're using these libraries and want to pattern-match.

I'll try out the predicates approach, and if it's kinda bad, we can always roll back that/those commit(s).

@ChrisEPhifer
Copy link
Collaborator

Grr. Well, I've got some irritating news: I'm not sure there's a great way to fully protect the Quality type defined in the IQuality module while still being able to use it reasonably where it's needed.

The way I see it, our two options are:

1. Export a set of predicates that can take the place of pattern-matching

2. Suck it up, export all the normal constructors alongside the smart constructors, and carefully document the risk of violating invariants when using the normal constructors instead of smart constructors.

I'm slightly leaning towards option 1, since we've been really good about protecting invariants just about everywhere else - it's a bit annoying for us, but results in what I think is a more robust API. I'm also sort of considering end-users here, and it's a little annoying for them, too, if they're using these libraries and want to pattern-match.

I'll try out the predicates approach, and if it's kinda bad, we can always roll back that/those commit(s).

So I've added the predicates, but updating the code using IQuality to not pattern match is... not going to be even a little bit fun.

What I'd really like here is the ability to expose the representation to only other modules in this library, but prevent end-users from seeing those representations. I'll do some digging to find out if there's anything like that we can do.

@ChrisEPhifer
Copy link
Collaborator

Aha! I think I found exactly what we want: There's a language extension called ViewPatterns which is used for lightweight pattern-matching on abstract types. It may actually be prudent to see if there's a way we can use these across the codebase, and in fact, I'm going to wait until tomorrow to try and implement this in the IQuality module.

@ChrisEPhifer
Copy link
Collaborator

Also just found PatternSynonyms, which might be even better since it lets you export patterns explicitly and separately from constructors - I'll play with this stuff tomorrow and see what works well :)

Copy link
Collaborator

@ChrisEPhifer ChrisEPhifer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! I've solved the smart constructor problem with the language extension PatternSynonyms; left some comments for you to check out, let me know if you have questions about this.

Definitely going to think about whether we can use this elsewhere to make our smart constructors nicer; I have a feeling the answer to that is 'yes', but need to spend some more time on it to be sure. I'm hesitant to go too crazy with language extensions for now, if only to protect portability. For what it's worth, though, so far we've only used two extensions, and they're both fairly ubiquitous in Haskell code (the second being FlexibleInstances, which let me instantiate Invertible for every functorial type in one go.)

Comment on lines 149 to +154
case iQual of
Major -> Minor
Minor -> Major
Perfect -> Perfect
(Augmented x) -> Diminished x
(Diminished x) -> Augmented x
Major -> minor
Minor -> major
Perfect -> perfect
(Augmented x) -> fromJust $ diminished x
(Diminished x) -> fromJust $ augmented x
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check it out! We can pattern match using the old constructor names, but we can't actually use those constructors to make new values of IQuality! :) Exactly what we were looking for.

Comment on lines +18 to +22
, pattern Major
, pattern Minor
, pattern Perfect
, pattern Diminished
, pattern Augmented
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These exports expose only pattern forms of the constructors for this type; this way, we and other users can pattern-match in a natural way, but can't use the underlying representation. Note that I removed (..) from the export of Quality; this keeps all the actual constructors hidden.

Comment on lines 44 to +55
data Quality
= Major
| Perfect
| Minor
| Diminished Int
| Augmented Int
= RealMajor
| RealPerfect
| RealMinor
| RealDiminished Int
| RealAugmented Int

pattern Major <- RealMajor
pattern Perfect <- RealPerfect
pattern Minor <- RealMinor
pattern Diminished x <- RealDiminished x
pattern Augmented x <- RealAugmented x
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the magic: I renamed all the actual constructors, and made unidirectional (meaning only usable as patterns) pattern synonyms that have the original names.

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

Labels

refactor Code shape (but not semantics) changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants