Multiple dispatch

I’ve been using a sort of multiple dispatch pattern in some of the code I’ve been writing recently. Thought I’d share, its kind cool.

(
var store = (
	(Integer -> Integer).asSymbol : {|a, b| a + b },
	(Integer -> Float).asSymbol   : {|a, b| a.pow(b) },
	(Float -> Float).asSymbol     : {|a, b| a * b },
	(Float -> Float -> Char).asSymbol : {|a, b, c| format("% c: %", a * b , c)}
);

var func = { |...args|
	var assoc = args.collect(_.class).reduce('->');
	var key = assoc.asSymbol;
	if (store.includesKey(key).not, 
		{Error("could not find key %".format(key)).throw});
	store[key].(*args)
};

func.( 5, 10).postln;           // 15
func.( 5.0, 10.0).postln;       // 50
func.( 5.0, 10.0, $F).postln;   // 50.0 c: F
)
3 Likes

looks like a possible approach for bolting on a type system?

I’d have no idea how to do that. The language c++ implementation code is scary!

I’m currently making a little framework for static structures, where the synth owns buses or buffers or some resources… syntax looks a little like this…

s.waitForBoot {
	~noises = JXGroup(groupName, { |group|
		var a = JXImport("/home/jordan/Desktop/test.scd").(name: '/a');
		var b = JXImport("/home/jordan/Desktop/test.scd").(name: '/b');	
		JXConnect(a[\output] -> b[\input]);
		
        group.forwardResource(
            a[\input] -> \input, 
            b[\output] /*assumes name unless clash*/
         )
	})
}

~noise[\input].bus.set(2);
~noise[\output].bus.scope

where “/home/jordan/Desktop/test.scd” is…

{   
	|name|
	JXTypeCheck(name -> Symbol);

	JXGroup(name, {
		var divide = JXSynthDef('/divide', func: {
			var in = JXIn.kr(\input, 0); // this synth owns this bus
			JXOut.kr(\divout, in / 2); // and this one
		});

		var adder = JXSynthDef('/adder', func: {
			var fa = JXIn.kr(\in, 0); // sim.
			JXOut.kr(\output, fa + 1);
		});

		divide.connect(\divout -> adder[\in]); // connect the busses, also supports many2many connections, the JXIn can specify if it wants to take the sum or the mean (or a users provided reduction function)

		JXGroup.forwardResources( // lets these be accessible out side the group
			divide[\input] -> \input,
			adder[\output]
		)
	})
}

In forces the code to mirror the server structure and avoids all globals with the import. I don’t need the flexibility of live coding some managing all my own resources just irritates.

The purpose is to make managing larger projects with multiple files easier, and giving good errors when something goes wrong, and because it runs once at the beginning, I don’t have to worry about speed…

I’m writing these little type checks are the start of every function and it makes debugging significantly easier. When there have been bugs, they are much nicer to fix as in normal sc you can have a large call stack where the wrong object was passed in at the top somewhere. It throws with an error when you send it the wrong stuff. Worlds like this…

{  |name ,n|
   JXTypeCheck(name -> [Symbol, Integer], n -> Integer ...);
   ...
}

Where name is either a Symbol or an Integer, or something that inherits from them.

When you call connect between two resources there is a little multiple dispatch system that looks at the types of thing your trying to connect and finds the correct connecting function.

So to make JXIn and JXOut I had to implement a few very small classes:

JXOut : JXResourceUgen {...} // ar and kr
JXOutConstructor : JXResourceConstructor {...} // captures data
JXOutResource : JXResource {...} // owns bus

JXIn : JXResourceUgen {...}
JXInConstructor : JXResourceConstructor {...}
JXInResource : JXResource {...}

and this one which registers the multiple dispatch, its name isn’t important.

JXConnection_pr_Out2In : JXResourceConnectionFunctionBase {
	*initClass {
		JXResourceConnectionFunctionBase.register(
			JXOutResource, JXInResource,
			JXConnection_pr_Out2In
		);
	}
    *call { |someJXOutResource, someJXInResource|   ... }

Then when the user calles connect( a[\out] -> b[\out] ) it ultimately calls JXConnection_pr_Out2In.call(out, in), but if the in and out were different types, say a ownedBuffer, and a borrowedBuffer, it will call a different method.

I’d love an optional type system! Stronger than hints, because I’d always want them to run, but if they were omitted it would default to an any type.

1 Like

I’m not versed in computer science, but in reading the multiple dispatch wikipedia article, and then taking another look at your code it seems like this allows you to effectively have several different functions all with the same name? Or in this case different behavior out of the same function name. Is that correct?

Interesting!

func.( 5, 10).postln;           // 15   - both ints
func.( 5.0, 10.0).postln;       // 50  - both floats
func.( 5.0, 10.0, $F).postln;   // 50.0 c: F - float float character

// and here is which functions they call
(Integer -> Integer).asSymbol : {|a, b| a + b },
(Integer -> Float).asSymbol   : {|a, b| a.pow(b) },
(Float -> Float).asSymbol     : {|a, b| a * b },
(Float -> Float -> Char).asSymbol : {|a, b, c| format("% c: %", a * b , c)}

exactly, the function here is called func, when it gets different types as input, you get a different result.

In the. admittedly rather large block of code, I’m using this technique to allow for one function name to delegate the connecting of different resources. You could do the same thing with inheritance, but this get messy quickly, or with a very large case statement. The code does the latter here, but wraps it up quite nicely away from the user.

Awesome use of pattern matching, but what does → do? It seems to be an undocumented instance method.

It makes an Association