What would tidal look like if it was written in (e.g.) Python?

We've (@Raph, @artheist and others) have been discussing this on the discord innard channel, and as @ritchse suggested, we're at the point where we should probably open the discussion here.

Here's our work so far on a python 'port', very far from usable but interesting already, for implementing the basic tidal types for patterns and applicative (and monad) functionality for combining patterns:
vortex/vortex.py at main · tidalcycles/vortex · GitHub

8 Likes

is the main idea here that development in general will accelerate because there are more users fluent in the python language?

uh oh ..

It is my impression that Tidal heavily uses concepts that are only available in a statically typed language with algebraic data types, higher-order functions, partial application (currying), type classes (allowing for overloaded string and numeric literals), constructor classes (Applicative), and powerful type inference. Maybe the reliance on these is less visible in application code, but it certainly is in the implementation?

With enough determination, these concepts might be simulated in a language that lacks these features. But, to use such simulations, programmers still have to understand the underlying concept - and switching to a less expressive language only makes this harder?

In my regular teaching (computer science), I fight Python by ignoring it (it worked for Perl ... now practically dead) and Javascript, by mocking it, and referring to Typescript from time to time. But of course, that setting is quite different.

So, I will stand back, and watch this unfold. Of course, unfoldr ( Data.List ) is a higher order function ...

2 Likes

Lets have some humility and leave tribalism at the door, I think it holds back computer science by keeping it trapped in single disciplinary thinking.

Partial application and type inference are certainly nice, but there are always trade-offs involved. The motivation for me is mainly curiosity in finding out what those trade-offs are.

11 Likes

Obviously, Python is not Haskell and things will surely look and feel different. I think that the goal is not to have a perfect working replica of Tidal in Python. You can already use Tidal if you need Tidal. However, I think that exploring how the Tidal pattern computation system can be implemented and layed down in different languages and paradigms is something that is already really interesting and valuable in itself.

From my point view, as someone interested in computation, abstraction and programming languages but not that proficient as a coder / developer, Vortex is something that I have long waited to see happen. Everybody can daydream about some of their favorite tools being ported to another language they gueninely like (like Python). I am still waiting for the big comeback of Common Lisp as the default language for creative coding and conversation-oriented programming.

Oh, and you also learn a lot about what makes certain types of languages more suitable for writing down some things and how to deal around that, etc..

3 Likes

because i see tidal and live coding in general as something free and democratic, with the whole idea of sharing code and tools being almost socialist, i'd like tidal to be ported to as many languages as possible haha. i wish every language was like haskell, but it doesn't mean it should be the only tidal's language

2 Likes

I love the idea of porting Tidal to other languages and programming paradigms. I've experimented with this over Sclang and Ruby a few years ago, never finished them but it was a great way for me to better understand the innards of Tidal :slight_smile:

I'd love to contribute! Do you have a roadmap in mind or just playing around for now?

2 Likes

Given that there is always a lack of man power for these projects, I doubt whether it is a good idea to adopt a complete different language, if you not have a clear idea why that would be a good thing. Two different languages to support is all issues * 4 or so? All though I use Python myself and I think it suits me very well for certain tasks, I doubt this move. Maybe KISS is also, focus on one language.

Just playing around for now really.. I intend to try it in Javascript as well, although a bit unfamiliar with javascript tooling these days.

It is really interesting exploring the different affordances of Python's dynamic types. It was surprisingly easy to implement the Tidal mininotation [ ] subsequences in plain python for example:
vortex/vortex.py at main · tidalcycles/vortex · GitHub

Working example here: vortex/vortex.py at main · tidalcycles/vortex · GitHub

3 Likes

This project is now at gabber zero - there's a script that plays a pattern including distorted kick drums:

vortex/play.py at main · tidalcycles/vortex · GitHub

Here's the pattern it plays so far:

pattern = (s(stack([pure("gabba").fast(pure(4)), pure("cp").fast(pure(3))]))
           >> speed(sequence([pure(2), pure(3)]))
           >> room(pure(0.5))
           >> size(pure(0.8))
)

It supports type inference, kind of.

I guess things to explore now include:

  • Making an experimental editor, maybe with pyqt
  • Reducing verbosity - e.g. so you don't have to put pure() everywhere
  • Tidying up the code, using decorators to 'patternify' parameters
  • Exploring possibilities for partial application, to make .jux(rev) possible (although so far neither jux or rev is implemented
  • Implementing a mininotation
  • Implementing all the pattern transformation functions etc
  • Trying this out in another language like javascript, and backporting ideas back to haskell mainline tidal
3 Likes

Exploring possibilities for partial application, to make .jux(rev) possible (although so far neither jux or rev is implemented

You can get partial application with something like

from functools import partial

def f(*args):
    try:
        x, y, z = args
    except ValueError:
        # if not enough arguments are given partially apply arguments
        return partial(f, *args)
    # do the actual calculation here
    return x + y + z

This works for all combinations:

In [6]: f(1, 2, 3)
Out[6]: 6

In [7]: f(1, 2)(3)
Out[7]: 6

In [8]: f(1)(2)(3)
Out[8]: 6

You can also put this in a wrapper and decorate all functions that you expect to be partially applied.

1 Like

Ah that's great, thanks!

As an experiment we tried moving the vortex repo to codeberg: TidalCycles/vortex: Experiments in porting tidal to python - vortex - Codeberg.org

Hi, we're going to talk through the vortex code and maybe brainstorm next steps shortly, at 2021-12-19T21:00:00Z

https://meet.google.com/tqm-haak-ipz

Feel free to drop by!

We had a nice chat last night, and came up with a TODO list for release zero:

Reading your paper @yaxu and this move / experimenting makes totally sense to me.

About the code. From what I read in Python educational material, is that Python works more with functions and modules and less so with classes. Authors of that material, advises more or less against (heavily) use of classes, but use namedtuples, dictionaries and the itertools instead. Classes does make the code also a bit slower in Python, I've read.

Given that Tidal has it's base in Haskell, I guess I didn't expect that the vortex code would use classes.

From what I've seen in the source code, the usage of classes is very limited and usually restricted to the margins. There are two classes used by the scheduler, the clock itself and the pattern streams. The GUI is using classes too because that's how things are done on the Python side but it's a separate piece of software distinct from the pattern manipulations.

The heart of Vortex is almost entirely functional, much more than what you would expect from a Python program. I found it really hard to read and digest because I am not that well versed in functional programming but it features fancy techniques and patterns you would expect from Haskell code: applicatives, pure, currying, etc... Some things look like classes but really aren't classes (Pattern).

1 Like

This is interesting, as I don't know too much about python programming paradigms.

As @Raph said there's not heavy use of OOP here. I guess I see classes just as a way of associating some functions with a class of values.

Thanks to the Pattern class, currently both of these work in vortex (and do the same):

  • sound("bd", "sn").every(3, fast(2))
  • every(3,fast(2),sound("bd", "sn")).

In this case, I prefer the former, as it's easier to chain transformations like we do in haskell with $.

But maybe using classes to represent Event and TimeSpan isn't needed? I'd be very happy for python experts to have a look and make suggestions.

+1 for the dot notation. It is much easier to read.

Also, FWIW I write a ton of Python professionally and use classes all the time. They make it much easier to deal with larger code bases. Sure everything could be functions, but the parameters can get out of control. Of course I have run into slow classes while profiling which I replaced with fast-af-functions. But that was in a web scale application handling millions of requests per hour. And it was simply overhead from the cruft in the class. The class likely could have been rewritten to deal.

2 Likes

This Python implementation inspired me to try to implement a Kotlin one, I called it Kidal, because I don't have much fantasy for names :smiley:

It's able to sound, there are some functions implemented and others on the way. There's a little README that explains how to use it (in a REPL environment).
If someone's interested to partecipate, please let me know!

2 Likes