Hi all, I'm struggling with an issue in TidalCycles, a DSL for (mostly musical) pattern, embedded in haskell. As an example, there's a function `density` of type `density :: Time -> Pattern a -> Pattern a`. `Time` is a type synonym for `Rational`. The `sine` function is of type `Pattern Double`, and returns a sine wave with a frequency of one per cycle. `density 3 sine` would then return a sine wave with a frequency of three. What I'd*love*to do is make it so functions like density could either take a number, or a pattern of numbers for its first argument. Indeed, this works fine: :set -XTypeSynonymInstances -XFlexibleInstances -XRankNTypes class Temporal a where toTimePattern :: a -> Pattern Time instance Temporal Time where toTimePattern = pure instance Temporal (Pattern Time) where toTimePattern = id let density' :: Temporal a => a -> Pattern b -> Pattern b density' t p = do t' <- toTimePattern t density t' p Then these are possible, great! density' (p "2 3" :: Pattern Time) "bd sn" density' (2 :: Time) "bd sn" However, type inference doesn't work - if I remove the type declarations there then the compiler breaks.[1] This is actually a dealbreaker, as tidal is designed for live coding, type inference is an essential timesaver. Asking on the #haskell irc channel, it seems this behaviour is expected, and that it's not possible to do inference across type classes. However, it is! But it's patchy.. In the following example, testA works (for String and Integer), and testB doesn't (for Int). class Foo a where testA :: a -> String instance Foo Integer where testA = show instance Foo String where testA = show testA "4" -- works testA 4 -- works class Bar a where testB :: a -> String instance Bar Int where testB = show testB 4 -- doesn't work Indeed, as far as I can tell the standard `show` function works just great with its polymorphic parameter.. Although I can't see anything in its definition to see why it works (and why it doesn't with my Temporal example). I'm really keen to get this to work as it would give a real step change in the flexibility of Tidal.. Any ideas much appreciated Cheers alex [1] Here's the error: 218:1: Could not deduce (Temporal a0) arising from a use of โdensity'โ from the context (Parseable b) bound by the inferred type of it :: Parseable b => Pattern b at <interactive>:218:1-20 The type variable โa0โ is ambiguous Note: there are several potential instances: instance Temporal Time -- Defined at <interactive>:107:10 instance Temporal (Pattern Time) -- Defined at <interactive>:96:10 In the expression: density' (2) "bd sn" In an equation for โitโ: it = density' (2) "bd sn" <interactive>:218:11: Could not deduce (Num a0) arising from the literal โ2โ from the context (Parseable b) bound by the inferred type of it :: Parseable b => Pattern b at <interactive>:218:1-20 The type variable โa0โ is ambiguous Note: there are several potential instances: instance Integral a => Num (Ratio a) -- Defined in โGHC.Realโ instance Num Integer -- Defined in โGHC.Numโ instance Num network-2.6.3.1:Network.Socket.Types.PortNumber -- Defined in โnetwork-2.6.3.1:Network.Socket.Typesโ ...plus five others In the first argument of โdensity'โ, namely โ(2)โ In the expression: density' (2) "bd sn" In an equation for โitโ: it = density' (2) "bd sn"

# Polymorphic parameters and type inference

Keywords:

*pattern**density**Num**instance**Temporal*

On Mon, 6 Mar 2017, Alex McLean wrote: > What I'd*love*to do is make it so functions like density could > either take a number, or a pattern of numbers for its first argument. > > Indeed, this works fine: > > :set -XTypeSynonymInstances -XFlexibleInstances -XRankNTypes Btw. you can omit TypeSynonymInstances if you have FlexibleInstances. Your posted code does not need RankNTypes.

> class Temporal a where > toTimePattern :: a -> Pattern Time > > instance Temporal Time where > toTimePattern = pure > > instance Temporal (Pattern Time) where > toTimePattern = id > > let density' :: Temporal a => a -> Pattern b -> Pattern b > density' t p = do t' <- toTimePattern t > density t' p > > Then these are possible, great! > > density' (p "2 3" :: Pattern Time) "bd sn" > density' (2 :: Time) "bd sn" An alternative would be to fix the type of the first parameter of 'density' to Pattern Time and define instance Num (Pattern time), such that the literal 2 is interpreted as Pattern Time. However, I consider it abuse of the Num class because you will be hardly able to define sensible arithmetic on "Pattern time". You can save the type annotation in the first case, if the result of p has fixed type "Pattern Time". If it has partly polymorphic type, i.e. "Pattern time", then you can still define the instance as: instance (time ~ Time) => Temporal (Pattern time) where toTimePattern = id This requires TypeFamilies instead of FlexibleInstances. Generally, my experience is that too much type hacks must be paid with more type annotations. I would certainly prefer a less type-hacked approach. E.g. using your 'pure' does not look worse than a type annotation: density (pure 2) "bd sn" Alternatively, you could define different versions of density'. If there are more functions that could have either pattern or simple time as first parameter, you could write a modifier like so density' "2 3" "bd sn" timed density' 2 "bd sn" with density' :: Pattern Time -> Pattern b -> Pattern b timed :: (Pattern Time -> f) -> (Time -> f) > Asking on the #haskell irc channel, it seems this behaviour is > expected, and that it's not possible to do inference across type > classes. However, it is! But it's patchy.. In the following example, > testA works (for String and Integer), and testB doesn't (for Int). > > class Foo a where > testA :: a -> String > > instance Foo Integer where > testA = show > > instance Foo String where > testA = show > > testA "4" -- works > testA 4 -- works It is not very nice though, since type defaulting jumps in here. It is controlled by the 'default' declaration. It is rarely used and there is a warning if the compiler has to use it.

On Mon, Mar 06, 2017 at 10:06:57AM +0000, Alex McLean wrote: > Hi all, > > I'm struggling with an issue in TidalCycles, a DSL for (mostly > musical) pattern, embedded in haskell. Hello Alex, this message won't be of much help, but if you post in haskell-cafe with an .hs attached I am sure more eyes will see it (and look at it). I didn't have time to test (yet!) but maybe there is a way to solve this using the monoidal trick many libraries use (or something else: I would be surprised if no-one encountered the same problem on -cafe). Happy live coding -F

Hi Francesco, > this message won't be of much help, but if you post in > haskell-cafe with an .hs attached I am sure more eyes will > see it (and look at it). I thought about posting to haskell-cafe, but thought people here would understand the problem better, and generally like smaller communnities! However I did also post it on the haskell reddit: https://www.reddit.com/r/haskell/comments/5xshvm/polymorphic_parameters_and_type_inference/ > I didn't have time to test (yet!) but maybe there is a way to > solve this using the monoidal trick many libraries use (or > something else: I would be surprised if no-one encountered > the same problem on -cafe). Thanks, my knowledge of Haskell can feel quite surface level when I read things about monoids etc but if there's a particular library doing this I'd like to take a look!

alex

Hi Hennig On 6 March 2017 at 10:35, Henning Thielemann <email obscured>> wrote: > Btw. you can omit TypeSynonymInstances if you have FlexibleInstances. > Your posted code does not need RankNTypes. Ah I've been collecting these while experimenting. Cargo cult programming ftw! > An alternative would be to fix the type of the first parameter of > 'density' to Pattern Time and define instance Num (Pattern time), such > that the literal 2 is interpreted as Pattern Time. However, I consider it > abuse of the Num class because you will be hardly able to define sensible > arithmetic on "Pattern time". Oh my ...*click*:set -XFlexibleInstances instance Num a => Num (Pattern a) where negate = fmap negate (+) = liftA2 (+) (*) = liftA2 (*) fromInteger = pure . fromInteger abs = fmap abs signum = fmap signum Thanks so much for all the other insightful thoughts but this makes total sense to me so far, seems to sidestep my issue with parameters and as a huge added bonus allows me to do things like `sine1 + (0.5 <~ sine1)`.. This is amazing! Thanks again!

alex

On Mon, 6 Mar 2017, Alex McLean wrote: > :set -XFlexibleInstances > > instance Num a => Num (Pattern a) where If there is sensible arithmetic on Patterns, then go this route. But it is plain Haskell 98 instance, no FlexibleInstances - which is good.

On 6 March 2017 at 11:25, Henning Thielemann <email obscured>> wrote: > On Mon, 6 Mar 2017, Alex McLean wrote: >> :set -XFlexibleInstances >> >> instance Num a => Num (Pattern a) where > > If there is sensible arithmetic on Patterns, then go this route. But it is > plain Haskell 98 instance, no FlexibleInstances - which is good. Aha! The only downer is that this only works with bare integers, so `density' 1.5 ...` doesn't work. Do you have any pointers for how I could get around this? Perhaps I need to make Pattern a an instance of Real and Rational as well? Best wishes

alex

On Mon, 6 Mar 2017, Alex McLean wrote: > On 6 March 2017 at 11:25, Henning Thielemann > <email obscured>> wrote: >> On Mon, 6 Mar 2017, Alex McLean wrote: >>> :set -XFlexibleInstances >>> >>> instance Num a => Num (Pattern a) where >> >> If there is sensible arithmetic on Patterns, then go this route. But it is >> plain Haskell 98 instance, no FlexibleInstances - which is good. > > Aha! The only downer is that this only works with bare integers, so > `density' 1.5 ...` doesn't work. Do you have any pointers for how I > could get around this? Perhaps I need to make Pattern a an instance of > Real and Rational as well? The type class for fraction literals is Fractional.

On 6 March 2017 at 11:37, Alex McLean <email obscured>> wrote: > On 6 March 2017 at 11:25, Henning Thielemann > <email obscured>> wrote: >> On Mon, 6 Mar 2017, Alex McLean wrote: >>> :set -XFlexibleInstances >>> >>> instance Num a => Num (Pattern a) where >> >> If there is sensible arithmetic on Patterns, then go this route. But it is >> plain Haskell 98 instance, no FlexibleInstances - which is good. > > Aha! The only downer is that this only works with bare integers, so > `density' 1.5 ...` doesn't work. Do you have any pointers for how I > could get around this? Perhaps I need to make Pattern a an instance of > Real and Rational as well? Hmm, of course this doesn't work as Real and Rational aren't type classes..

> The type class for fraction literals is Fractional. Aha! instance (Fractional a) => Fractional (Pattern a) where fromRational = pure . fromRational (/) = liftA2 (/) This seems to have done it! Hoping to not hit more snags. Thanks again Hennig, I have tears in my eyes

alex

Hmm, the only snag I've found is that this works: 1/3 :: Pattern Rational but this doesn't: (1%3) :: Pattern Rational

On 6 March 2017 at 11:53, Alex McLean <email obscured>> wrote: >> The type class for fraction literals is Fractional. > > Aha! > > instance (Fractional a) => Fractional (Pattern a) where > fromRational = pure . fromRational > (/) = liftA2 (/) > > This seems to have done it! Hoping to not hit more snags. Thanks again > Hennig, I have tears in my eyes > > alex -- blog: http://slab.org/ music: http://yaxu.org/ crowdfund: http://www.pledgemusic.com/projects/spicule/

On Mon, Mar 06, 2017 at 11:02:21AM +0000, Alex McLean wrote: > Thanks, my knowledge of Haskell can feel quite surface level when I > read things about monoids etc but if there's a particular library > doing this I'd like to take a look! Ah, the trick is pretty simple, a function which returns a monoid is a monoid itself, and this property lets you have functions that can take a variable number of arguments, like expressed here [1]. [1] https://wiki.haskell.org/Varargs Now I am not sure if it does apply to your case (especially: if it typechecks or explodes).

On Mon, 6 Mar 2017, Alex McLean wrote: > Hmm, the only snag I've found is that this works: > 1/3 :: Pattern Rational > > but this doesn't: > (1%3) :: Pattern Rational Sure, but this is true for all Fractional types except Ratio, e.g. (1%3) :: Double is forbidden, as well.

Ah right, a little bit of a pity that Patterns of Rationals can't be expressed like this, but as (1/3) seems to be interpreted the same as (1%3) without floating point error, no biggy. I wrote a blog post about this including a quick video demo: https://slab.org/patterns-are-the-time-of-numbers/ It feels really nice to have Patterns in the Num class, a huge, unexpected improvement. cheers On 6 March 2017 at 12:15, Henning Thielemann

<email obscured>> wrote: > > On Mon, 6 Mar 2017, Alex McLean wrote: > >> Hmm, the only snag I've found is that this works: >> 1/3 :: Pattern Rational >> >> but this doesn't: >> (1%3) :: Pattern Rational > > Sure, but this is true for all Fractional types except Ratio, e.g. > > (1%3) :: Double > > is forbidden, as well. > > -- > > Read the whole topic here: Haskell Art: > http://lurk.org/r/topic/6kyTKyeDzZF0lWDK5mk8U0 > > To leave Haskell Art, email <email obscured> with the following email subject: unsubscribe -- blog: http://slab.org/ music: http://yaxu.org/ crowdfund: http://www.pledgemusic.com/projects/spicule/