How to structure modules providing instances

I have a few modules providing functions working on a datatype called Pattern, and am making a new similar-but-different datatype called Sequence. Patterns are like signals - a function from time to elements. Sequences are like lists of elements. But they’re both different representations for (musical) patterns.

The thing is though that I’d like to use the same function names for both datatypes, like rev for reversing them fast for speeding them up, etc. This seems like a job for typeclasses/instances.

Here’s our current attempt:
https://github.com/tidalcycles/Tidal/blob/topic-sequences/src/Sound/Tidal/Context.hs

Importing the Pattern modules (Pattern, Core and UI) and the Sequence module (just Sequence), hiding all the common functions which are then combined using a typeclass called Transformable.

It’s working but as you can see, involves a lot of qualification and hiding which I think is going to become impractical as we add more common functions.

I’m struggling to find a better way - maybe renaming all the instance-specific functions in terms of their type (patFast, seqFast etc) which makes some sense but is a little bit messy.

Or perhaps I’m going about this the wrong way. Any advice much appreciated!

1 Like

In this case, I would suggest moving Transformable into its own module, then importing it into the modules which define Sequence and Pattern:

  • As more methods are added to Transformable, it may be possible to make some methods into top-level definitions, which are defined using other Transformable methods - in time, you’ll (ideally) end up with a small set of Transformable methods, and a library’s worth of dependent definitions (the classic example of this approach being the Monad type class - only two basic methods, and yet so many ways they can be used).

  • If that many methods really are needed, having each Transformable instance co-located with its type means any changes to just one type are unlikely to require recompiling the instances for both types (if there were no changes to Transformable).

3 Likes

Thanks for the insights @atravers, that makes sense.

It would be great if that was the case with a small set of transformablemethods emerging, I think that won’t happen to a great extent though as the underlying representations are so different… But I’m prepared to be pleasantly surprised :slight_smile:

It’s just occurred to me that the Monad analogy can be extended - in Haskell, it originally had four methods. Over time, some of those methods were split off into their own type classes e.g. fail and MonadFail.

This could also be a possibility for Transformable (I’ll leave it to you to decide the names for the extra type classes…). If they end up being large enough, they can then be moved into their own modules.

2 Likes

Thanks again @atravers, I’m experimenting a bit with this approach and it’s going well.

(Pattern is now the typeclass, with Signal and Sequence implementing it)

As you predicted, quite a few functions are already moved up and defined only once in terms of the typeclass, much tidier!

1 Like