Eric Grange (resurrector of the increasingly interesting looking DWS project) recently posted about a new idea he has had for the DWS engine, which in turn gave me an idea, or rather, prompted me to come up with what I think may be a new spin on an old one.

I briefly posted my idea to the comments on his post, and for more details of his suggestion please visit his pages. Here I shall spend more time outlining my idea, inspired by, but not necessarily directly connected to his.

The old problem at the heart of the idea, is the lack of “iif()” or so called “ternary operator” in Delphi.

We do have IfThen() in limited forms in various places in the RTL:

   s := IfThen(SomeCondition, stringA, stringB);

Which provides a neat (i.e. condensed) alternative to:

  if SomeCondition then
    s := stringA
  else
    s := stringB;

Except it doesn’t quite do that, as anyone who has encountered this function will testify.

Problems from the fact that IfThen() is just a function, like every other function in Delphi, and to pass expressions as parameters to that function, those parameter expression must be first evaluated. A simple way to demonstrate the problem is to (try to) use IfThen() to retrieve a property of an object or yield a default if the object is not assigned:

   s := IfThen(Assigned(obj), obj.Name, '(no object assigned)');

Because the parameters to the IfThen() function must be evaluated before IfThen() can itself be called, the obj.Name reference will cause an access violation if obj is not assigned, precisely what we were trying to avoid!

Eric’s idea was to support a way of declaring parameters that indicate that if those parameters represent an expression, that the expression itself in effect be passed and only evaluated within the function if required.

I can see how this is easily possible in a scripting engine such as DWS, and I can even see how it might be achieved with anonymous methods in Delphi, except that I really wouldn’t want to see that any more than I would be happy to see a generalised IfThen() contrived using generics, which would look something like:

   TTernary<T> = class
      class function IfThen(aCondition: Boolean; aTrueResult, aFalseResult: T): T;
   end;

   // In usage:
   s := TTernary<String>.IfThen( condition, stringA, stringB );

Some might call me “old fashioned” for not welcoming such new syntax.

I don’t think it has anything to do with being “old fashioned”, unless it is old fashioned to find such syntax ugly, cumbersome and just plain hard to read (not hard to understand, but physically hard read – it being full of syntax and punctuation, far more than should be required).

Not to mention the flat out ridiculous need to place the function in an extraneous class, just to gain access to generics support.

But I digress.

My idea came from thinking about what the code that the operator itself represents (and possibly even generates). To recap:

   s := IfThen(SomeCondition, stringA, stringB);

Is equivalent to:

  if SomeCondition then
    s := stringA
  else
    s := stringB;

Which in turn is equivalent to:

  case SomeCondition of
    TRUE  : s := stringA
    FALSE : s := stringB;
  end;

Now it seems quite clear and obvious to me that this form of the code quickly identifies how much of the code is not just boilerplate, but merely syntactic scaffolding, and could be replaced by… (drum roll please…)

  s := case[SomeCondition, stringA, stringB];

Or some variation on this. I suggest re-using the keyword “case” – perfectly valid since it is being used in exactly the same way as it’s current use, merely employing a slightly different syntax.

Similarly, using [] (rather than ()) ensures that the new syntax itself is not confusable with that syntax for case that is already supported and also differentiates this syntax from a function call which it might otherwise appear to resemble.

TA-DA!

I don’t know about you, but I would love to see this added to the language, and when I get my hands on DWS (and some time) I hope to explore adding it to at least that scripting version of Pascal.

17 thoughts on “The case for case[]

    1. You mean it’s too similar to e.g.:

      const
      STRINGS: array[false..true] of String = (‘When True’, ‘Otherwise’);
      begin
      s := STRINGS[ Condition ];
      end;

      Which is another alternative and more general purpose alternative to Ifthen() (it can be used for any type for which literal constants may be expressed, not just strings, integers etc).

      🙂

      Similarity to a non-function call invoking syntax (array referencing) for a new syntax that is not a function call seems preferable to me than similarity with existing function call syntax. Plus, using regular braces would be more ambiguous as “(” can legally follow “case” in the current syntax, where “[” currently cannot.

  1. I would love to see this construct. I hate “if then else” one liners.

    Call me crazy but for True/False I already use the case statement. I somehow find it more readable in a code. I know most will dissagre.

    Anyhow this is one of the cases that compact variant would come in handy. The only drawback harder debugging. With “if then else” you can clearly see which statement was processed.

  2. @runner: Yes, debugging would be superficially harder but not insurmountably so, imho. And on that score, it is long overdue for the debugger to be able to resolve executable statements to a finer degree of granularity than single lines of code.

  3. @Jolyon

    Don’t get me wrong, I am all up for this systax (or even something similar). I dont use IfThen because they do not cover all cases and are cumbersome in my opinion (and functions as you pointed out).

    The harder debugging was just and observation. It does not matter for trivial statements.

  4. To add upon the idea, Prism allows expending a full blown “case of” as an expression, but in practice it does make the code look quite messy.

    However the Prism inline “if” expression looks more interesting for ternary

    value := ( if bool then exprIfTrue else exprIfFalse );

    The ‘if’ could probably be left out, but I’m not sure allowing to leave out the else as Prism does is such a good idea.

    value := ( Assigned(obj) then obj.Name else ‘not assigned’ );

    It’s quite close to C’s ?: then, but still a bit verbose, and doesn’t quite have the compacity of an iif or case[], but hey, it’s pascal.

    That said, I’m not too convinced by the case[] syntax, it doesn’t stand out much, and the convention would be the reverse of that of the boolean arrays mentionned by other commenters (where false case is the first element).

  5. This has probably been discussed before but what would be wrong with this in Delphi?
    s := SomeCondition ? stringA : stringB;

    Perhaps it breaks some fundamental pascal rule or..?

  6. I would rather prefer case to support non ordinal types (like strings!) than adding a similar but different (because you kind of reduce it to evaluate only boolean conditions) way to work. Also this pretty much seems like changing this “if then else” one liner into some “c like” (less to type but harder to read because less “speaking”) syntax.

  7. Actually I like the way they implemented it in Prism.

    s := if condition then ‘stringA’ else ‘stringB’;

    s := case myNum of
    1: ‘one’;
    2: ‘two’;
    3: ‘three’;
    else ‘greater than three’;
    end;

    These are both valid syntax in the latest version, and they work with any valid types, not just strings.

  8. Personally I think this is not worth the effort to implement.
    Everybody keeps saying that more time is spent reading (maintaining) code than writing it and I find a normal If then else statement much more readable then case[SomeCondition, TrueA, FalseB].
    But then again it a matter of preference.

    Best regards,
    Ajasja

  9. Interesting thoughts on all counts.
    I also have been using the array[Boolean] construct to be able to use a conditional string inside another expression.
    But now, thinking of your proposition, if we are to introduce new syntax and change the compiler, why not just change the compiler and create that yet-another-magic-function iif that would indeed only evaluate one of its arguments according to the boolean condition?
    I tend to prefer a new reserved word, esp. self explanatory, than a new syntax construct. But YMMV…

  10. Great comments guys – thanks!

    @Mikael: The ‘C’ like ternary operator just doesn’t smell like Pascal to me.

    @Francois: “magic” functions are not very friendly – when I see a function call I expect to be able to debug into it. Functions that aren’t really functions are frustrating. Plus, implementing it as a magic function means choosing a name for that magic function that someone else has not already used for their own, genuine function.

    Chances are someone, somewhere HAS already written their own IIF(), or – as in my case – a bunch of additional IfThen() overloads to support different types.

    Using an existing reserved word in a new syntax is the safest way to extend the syntax supported by the language.

    The problem, as I see it, with using () instead of [] is that this too is more easily confused with existing legal syntax:

    “case (a + b)” is legal in current syntax (though what follows will be different, of course)

    “case [a+ b]” is not legal in current syntax, regardless of what follows.

    @Mason: Something about IF returning a result makes me uncomfortable. I think the biggest issue I have with it is that it requires that I write expressions where statements are otherwise normally required. It re-uses an existing reserved word, but doesn’t change it significantly enough to make the new usage entirely distinct. imho. Ditto the similar alternate syntax for CASE (though I am less uncomfortable with that for some reason).

    @Stefan: It doesn’t reduce an if-then-else one-liner – I *never* use if-then one-liners, let alone if-then-else one-liners. It reduces a 4 liner to a valid one-liner. 🙂

    @Ajasja: FWIW I am one of those that goes on and on about reading/maintaining code, but in this case the if-then or fully expressed case versions of this logic are *less* readable – the syntactic scaffolding is extraneous noise when simply selecting one of two simple expressions based on some simple condition.

    When genuinely branching flow of execution that same scaffolding is useful and essential, but not in this case [sic].

    imho.

  11. You could generalize the problem domain, and introduce a new keyword “expr” in addition to “var”, “const”, “in”, “out” etc. in function/procedure parameter declarations.

    The effect of this would be that an anonymous function is sent to the function instead of the evaluated value, and it was up to the function to decide *when* to call that code (just as you already *can* do with AM, only you don’t have to type so much around the expression). Then you could write:

    s := IifString(SomeCondition, stringExpressionA, stringExpressionB);

    where IifString is defined as:

    function IifString(const Condition: Boolean; expr TrueExpr: string; expr FalseExpr: string): string;
    begin
    if Condition then
    Result := TrueExpr
    else
    Result := FalseExpr;
    end;

    1. @Anders: Even setting aside my dislike for anon-methods, I do not think this would be a good idea in this case.

      It raises all sorts of questions about whether expressions passed as parameters should be evaluated once and only once when referenced within the target function (as would be the situation with a ternary operator, for example).

      This might make sense in some cases, but not in others. The problem is, you cannot tell from the function signature alone how your passed parameter (expression) will behave…. you have to inspect the source of the function to know whether an “expr” (or “uses”) param will be evaluated once (the function would presumably have to explicitly evaluate and store the result, or will be evaluated multiple times.

      I also wonder if an anon-method approach wouldn’t introduce unnecessary overhead (am I right in saying that anon-methods invoke all sorts of scaffolding, with interfaces and what-not to control scoping etc), for what could and should be a simple inline selector/branching construct.

  12. @Jolyon: I’m sure anon-methods would introduce overhead. There isn’t much you can do about that, if you really need the delayed evaluation of the expression.

    Algol 68 had this possibility “call by name”, and LISP (of course) have it, and the problem with repeated evaluation of the parameter expression is real.

    As you say, one way to solve that is to cache the result the first time the expression is evaluated, and in later references use the cache. This can be done by explicit code ( LocalP1 := P1; ) or be implicit in the compiler.

    OTOH, there might be cases where you don’t want to cache it, you might really want the expression to be evaluated each time it is used (horror: think of global variables and function calls with side effects, all used by the expression), so maybe the explicit model is best.

    The problem is, you cannot tell from the function signature alone how your passed parameter (expression) will behave…. you have to inspect the source of the function

    IMO, that goes for a lot of things in the OO world (or any kind of programming), so I don’t see that as a new problem. And if I see the type “expr” in the signature, I know for sure that I must read on 🙂 It’s worse in LISP where you normally don’t have function signatures…

  13. Sorry about the (non-existing) quoting of “The problem is, … inspect the source of the function”. How do you produce a quote in the comments?

Comments are closed.