SwitchExpression models a C-style switch like this:
switch (<match>) {
case <ifs[0].if>: return <ifs[0].then>;
case <ifs[1].if>: return <ifs[1].then>;
...
case <ifs[n].if>: return <ifs[n].then>;
default: return <else>;
}
C also only allows the <ifs[*].if>s to be literals in this case. That being said, I see no reason why we couldn't just expand the logic to arbitrary expressions, and would be open to changing this. IfValue.if could be deprecated in favor of a new Expression field to retain backward compatibility. Semantically, you would just pick the first one that matches, so there's no need for uniqueness, and therefore no need for them to be literals for a static check.
For today's version of Substrait, you'd have to use IfThen if you need the switch cases to be non-literals. Each IfThen.ifs[i].if would then be set to an equality function binding [1] that compares the switch expression against the case expression. Borrowing C syntax again, you'd get something that looks like
if (<match> == <ifs[0].if>) return <ifs[0].then>;
else if (<match> == <ifs[1].if>) return <ifs[1].then>;
...
else if (<match> == <ifs[n].if>) return <ifs[n].then>;
else return <else>;
However, you would have to repeat the match expression for each branch, which could be a problem if the expressions have side effects, and could also be a performance issue. You can usually avoid that by inserting a project relation for the switch expression if it's nontrivial, so it just becomes a cheap FieldRef. Dealing with common subexpressions like that more elegantly is IMO an open problem. Inserting a new projection to eliminate a common subexpression is a very non-local operation, so if that's the only way I foresee a lot of spaghetti code for producers and optimizers.
[1]
https://github.com/substrait-io/substrait/blob/de6bc9fad440880b6b5333cb0ee129d2c19e471c/extensions/functions_comparison.yaml#L20-L33