Thread overview
What's a good approach to DRY with the block code of a case-statement?
Apr 26, 2021
Jack
Apr 26, 2021
ag0aep6g
Apr 29, 2021
z
April 26, 2021

I have a block of code that the only thing that change is the type passed in one of the template functions called so I'd like to make a DRY for this. But I'm not just replacing by a function due to control-flow, for example, there are if-statements where one just break and the other return 0. I think I could do something with mixin() that would kinda mimic C's macro but I still find it messy. Any alternatives?

static int doSomething()
{
	switch(val)
	{
		case VAL_FOO:
			auto obj = getObject!MyType(someData); // this is the type
												   // that changes
			if(obj.shouldExit()) break;
		
			auto m = Message(...);
			if(obj.doSomethingElse(m)) return 0;
		break;

	   // ...
	
	   default:
	}

	return doSomethingY();
}

Maybe my least resort, if I went to replace by a function, I could do something like this:

enum OP { BREAK, RETURN }
pragma(inline, true):
OP foo(T)()
{		
	auto obj = getObject!T(someData); // this is the type
										   // that changes
	if(obj.shouldExit()) return OP.BREAK;

	auto m = Message(...);
	if(obj.doSomethingElse(m)) return OP.RETURN;

	return OP.BREAK;
}

then:

static int doSomething()
{
	switch(val)
	{
		case VAL_FOO:
			auto r = foo!MyType();
			if(r == OP.BREAK) break;
			if(r == OP.RETURN0) return 0;
		break;

	   // ...
	
	   default:
	}

	return doSomethingY();
}

I still find this not much elegant. If anyone knows a better way to do this, help are very welcome

April 26, 2021

On Monday, 26 April 2021 at 20:39:55 UTC, Jack wrote:

>

I have a block of code that the only thing that change is the type passed in one of the template functions called so I'd like to make a DRY for this. But I'm not just replacing by a function due to control-flow, for example, there are if-statements where one just break and the other return 0. I think I could do something with mixin() that would kinda mimic C's macro but I still find it messy. Any alternatives?

static int doSomething()
{
	switch(val)
	{
		case VAL_FOO:
			auto obj = getObject!MyType(someData); // this is the type
												   // that changes
			if(obj.shouldExit()) break;
		
			auto m = Message(...);
			if(obj.doSomethingElse(m)) return 0;
		break;

	   // ...
	
	   default:
	}

	return doSomethingY();
}

I think you're looking for this D idiom:

sw: switch (rt_value)
{
    static foreach (ct_value; ct_values)
    {
        case ct_value: some_template!ct_value; break sw;
    }
}

Applied to your code it might look like this:

import std.meta: AliasSeq;
alias stuff = AliasSeq!(VAL_FOO, MyType, VAL_BAR, MyOtherType, /* ... */);
sw: switch (val)
{
    static foreach (i; 0 .. stuff.length / 2)
    {
        case stuff[i]:
        auto obj = getObject!(stuff[i + 1])(someData);
        if(obj.shouldExit()) break sw;
        auto m = Message(...);
        if(obj.doSomethingElse(m)) return 0;
        break sw;
    }
}
April 29, 2021

I'd recommend you to use templates with alias parameters but you mentioned that you need to retain function context(for gotos, continue, break, etc...)
One thing you could do is mix the ugly mixin solution with the good one:

//inside the function, so that you can access locals
pragma(inline, true)
string getDRYstr(T, parameters...)() {
    static assert(__ctfe);
    string mixedinstr;
    mixedinstr ~= /*code*/;
    static if(/**/){mixedinstr ~= /*code*/;}
    //...
    mixedinstr ~= "break;";
    return mixedinstr
}

There is no doubt that it is an ugly solution but the time saved not copy-pasting code could be worth it.