Has anyone defined an `ap`-like function `:: f (f a -> f b) -> f a -> f b` where `f = Pattern`?

The Applicative class exists:

class Functor f => Applicative f where
  ...
  (<*>) :: f (a -> b) -> f a -> f b
  ...

If this were a class too, it would look something like:

class Applicative f => Metapplicative f where
  ...
  (<**>) :: f (f a -> f b) -> f a -> f b
  ...

I defined such a function in Montevideo. I'll probably do the same for Tidal if it hasn't already been done, but it'd be cool if it had.

I just defined maybe the gnarliest regex of my life to look for anything resemblilng f (f a -> f b) -> f a -> f b in the source code, and no dice:

-- Haskell:
word = "[0-9a-zA-Z_]\\+ *"
lp = "( *"
rp = ") *"
arrow = "\\-> *"
w2 = word ++ word
f2 = word ++ word ++ arrow ++ word ++ word -- func from 2 words to 2 words
re = word ++ lp ++ f2 ++ rp
     ++ arrow ++ w2
     ++ arrow ++ w2

-- Emacs: In `re`, replace each \\ with \, because I apparanetly still don't understand strings in Haskell well enough.

-- Shell:
grep -i "[0-9a-zA-Z_]\+ *( *[0-9a-zA-Z_]\+ *[0-9a-zA-Z_]\+ *\-> *[0-9a-zA-Z_]\+ *[0-9a-zA-Z_]\+ *) *\-> *[0-9a-zA-Z_]\+ *[0-9a-zA-Z_]\+ *\-> *[0-9a-zA-Z_]\+ *[0-9a-zA-Z_]\+ *" -r . --color

There's actually one match, in "tidal-parse/src/Sound/Tidal/Parse.hs", but that's not relevant.

1 Like

Nice. I love me a typed programming challenge ...

  1. regexp .. well. For searching by type, I use f (f a -> f b) -> f a -> f b - Hoogle But, there is no such thing (in the APIs of packages on stackage)

  2. I was trying to use GitHub - augustss/djinn: Generate Haskell code from a type but it's not polymorphic enough (?).

  3. The type alone does not tell me what you expect the function to do. What laws do you expect to hold? Would this work for other Applicatives? I guess you need Monad as well?

  4. I built this, which has the type you want:

\ g x -> join (fmap (\h  -> h . pure )  g <*>  x )
  :: (Monad m, Applicative f) => m (f a1 -> m a2) -> m a1 -> m a2

if m = f = Pattern, then for join, there are three choices (inner, outer, squeeze), and for <*> there are as well (structure from left, right, both)

This sounds to me like a patternized version of spread with the arguments rearranged a bit.

Specifically, something like:

spread ($) [id, rev, fast 2, echo 0.5] pat

could instead be represented as

ap (listToPat [id, rev, fast 2, echo 0.5]) pat

I don't think there's a short mininotation for a pattern of functions, though, so I'm not sure how useful ap is here...

@jwaldmann What are inner, outer and squeeze?

I agree that it's not clearly a great candidate for a typeclass. I don't know what rules it should satisfy for other types.

For the Pattern type, the law it should satisfy is, I believe, this:

If

f :: Pattern ( Pattern a -> Pattern b )
a :: Pattern a
b :: Pattern b
s :: State

and query f s yields a single event e covering the entire timespan of s with the payload g :: a -> b, then query (f <**> a) s should be equal to query (g a) s.

patternized version of spread

@bgold Sweet! Yes, that's exactly what it is.

don't think there's a short mininotation for a pattern of functions, though

I thought about that and it seems hard. But it's easy to make a pattern of words (or numbers) and then use a lookup function to replace the words (or numbers) with functions. The awkward part is that since some functions take arguments, you'll need to apply the arguments in the lookup table rather than in the pattern -- but it's nice and simple:

funcPat :: forall a b label. Show label
        => [(label, Pattern a -> Pattern b)]
        -> Pattern String
        -> Pattern (Pattern a -> Pattern b)
funcPat funcs labelPat = let
  m :: Map String (Pattern a -> Pattern b)
  m = M.fromList $ map (_1 %~ show) funcs
  in filterJust $ fmap (flip M.lookup m) labelPat

(That function could be more general, too -- the Pattern a -> Pattern b could be swapped out for a type variable.)

I meant innerJoin, etc. ( Tidal/src/Sound/Tidal/Pattern.hs at 2.0-dev · tidalcycles/Tidal · GitHub )

In this pull request I submitted two functions, one of which corresponds to the title of this thread.

The two functions are these:

+meta :: forall a b.
+        Pattern (Pattern a -> Pattern b) -> Pattern a -> Pattern b

+replace :: forall a b. Ord a =>
+           [(a,b)] -> Pattern a -> Pattern b

Here's a minimal example using both. This plays the pattern "ho*8" at normal speed for the first half, and at double speed for the second half.

let slang = [ (0, id)
             , (1, fast 2) ]
   in p 1 $ meta (replace slang "0 1") "ho*8"

I tried to put them in reasonable places and give them reasonable names.

I found myself reminded of inhabit while reading your latest example, and came up with the below which seems to do as expected though with less new code and more usage of existing Tidal functions:

(import Data.Functor (bimap))

replace x = inhabit $ bimap id pure <$> x

or this, if you don't want to have to put strings in the table:

replace x = inhabit $ bimap show pure <$> x

(Alternatively, one could copy the code of inhabit itself and (either or both):

  • Replace its String dependency with an Eq constraint
  • Fall back on id instead of silence

Or, if you're okay with writing String keys and writing pure with the values, you can just use inhabit directly and with a potential (though likely overkill) benefit of being able to pattern directly inside the table values themselves. A whole 'nother layer of meta. :wink:
)

together with this:

meta pf p = pf >>= ($ p)

I'm curious, though, as to whether or not these implementations cover the same requirements.

While doing some reading and working on some other things after posting this I realized that my take on meta is just barely more than bind. I'll update my post to reflect that.

replace x = inhabit $ bimap show pure <$> x

Oh dang that's awesome.

Good grief that's impressive. Will play around with. Thanks, @mvdirty.

Your insights, @mvdirty, render my definition of meta unnecessary -- I'd rather write pf >>= ($ p). But maybe replace still deserves to exist. I've updated the pull request accordingly.