[Estimated Reading Time: 2 minutes]

A quick post on a small but hugely useful little language feature in Oxygene… if expressions.

Many people will be familiar with the so-called ternary operator. Delphi developers will also be aware that there is no direct equivalent in Delphi.

In ‘C’ and other languages we can write a statement such as this:

    x = a ? b : c;

The result is that x is assigned the value of b when a is true, otherwise x is assigned the value of c.

The ternary operator evaluates only either b or c as determined by the condition a.

In Delphi you can use the IfThen() function (for supported types of values of b and c) but this is notorious for the short-coming that regardless of the value of condition a both b and c will be evaluated.

At best this makes IfThen() potentially less efficient and at worst it can render it entirely useless if the evaluation of b or c has undesirable consequences or side-effects.

For example if you tried to use IfThen() to avoid a division by zero:

     x := IfThen( divisor <> 0, numerator div divisor, 0);

This code will still throw a division by zero exception when divisor = zero. The equivalent ternary expression would not:

     x = divisor != 0 ? numerator / divisor : 0;

In Delphi therefore you have no choice in these situations but to long-hand an if-else statement:

   if divisor <> 0 then
     x := numerator div divisor
     x := 0;

In Delphi therefore it is not uncommon to find this one, simple problem solved by at least three different techniques:

    // 1. Where the values of b and c are supported by IfThen
    //     and have no side effects:

    x := IfThen( a, b, c );

    // 2. Where b and/or c are variable expressions that 
    //     do or may have side-effects:

    if a then
      x := b
      x := c;

    // 3. Where the values of b and c are constant expressions:

      AVALUE: array[false..true] of Integer = (0, 42);

    x := AVALUE[a];

Oxygene addresses this gap by simply re-purposing the if statement as an expression:

  x := if divisor <> 0 then numerator div divisor else 0;

But Oxygene also goes further, extending the same behaviour to case statements, for example:

   x := case selector of
     CONST_A : valueA;
     CONST_B : valueB;
     CONST_C : valueC;

Applying case and if in this way eliminates the duplication of the assignment expression in the overall statement and can be applied consistently in other scenarios such as choosing values to pass as parameters to functions:

   Foo( a, if a > 42 then b else c );

The result is code that is consistent as well as both (imho) more concise and also clearer in intent than an if-else or (for case) a series of if-else if-else if statements.

Although a seemingly very small thing it is one of my favorite features of the Oxygene implementation of Object Pascal.

14 thoughts on “Expressive If and Case …”

  1. I’m glad you like these. I proposed them to Carlo based on SQL which also has IF and CASE as both statement and expression.

  2. Maybe I am in minority, but I always try to enhance the SRP (Single responsibility principle) concept also to a single code line. “? :” statement is against it, what you write here, even more. For me, the clean and tidy code is when I have just a single operation in one single line.

    This is of course idyllic coding standard, but I used to strive into it as well as I can.

    1. If I understand you correctly, it depends what you mean by “a single operation”.

      If the operation is to “assign a value to some variable” (or pass a value in some function parameter) where the value varies according to some condition, then the ability to express that condition directly within the operation is precisely what results in a single operation.

      If you consider the evaluation of the condition itself to be “a single operation” then you have to create two possible execution paths, dependent on that operation, to conditionally perform one of two further operations.

      So on the one hand you have one operation with a conditional component within, or three operations where one represents a conditional branch and two represent the variations of the operation that give rise to two discrete versions of that same operation.

      If the concern is the multiple responsibilities then within the single operation, bear in mind that it is not unusual in structured programming to have single operations with multiple but closely related responsibilities, particularly (and perhaps especially) when it comes to composing execution paths into “a single operation”. Even the humble for loop combines an iteration with initialisation and termination conditions, for example.

      1. Because of differences in the for loop I find Delphi more logical language than C++ for example. πŸ™‚

        *for .. in* loop looks fantastic for me. πŸ˜‰

        I understand your arguments about sense of single operation. My point of view is: *IfThen* is a command combined of one logical expression and two arithmetical expressions.

        It may look elegant in C++ where begin/end statement is just one character, but in Delphi as such verbal language it requires few seconds more to disassemple the ternary operator into less complex operations (at least for me).

        1. Except that IfThen() is not one operation but five:

          1. Evaluate the condition expression to be passed as 1st param
          2. Evaluate the expression to be passed as 2nd param
          3. Evaluate the expression to be passed as 3rd param
          4. Call the IfThen() function
          5. Assign return value to a designated variable

          (The above assumes that we are conditionally assigning one of two expression results to some variable)

          The disguised “simplicity” hides the fact that far from reducing code to the minimum number of required operations the use of IfThen() necessarily and wastefully performs more operations than are required in any given instance of execution. Which is also what makes it utterly useless in particular circumstances (as described).

          By contrast an if expression consists of just three distinct operations:

          1. Evaluate the condition expression
          2. EITHER: Evaluate the TRUE expression OR: evaluate the FALSE expression
          3. Assign evaluated expression result to a designated variable

          (Again, assuming conditional assignment of one of two expression results to some variable)

  3. Oxygene is really a great language and I wish someday Delphi will adopt such language sugars. Unfortunately it’ just not viable to replace Delphi with it for development really world software, because for a development tool, the language is only part of it, the more important parts are the richness of available libraries and components, and the productivity of the IDE, and also backward compatibility if one has existing projects.

    1. Backward compatibility is the only thing on your list that could actually be regarded as a “problem” and is the only thing that has any bearing on the other items in that list.

      Available libraries and components: This makes no sense. With Oxygene you have full, unfettered access to the huge range of libraries and components available for each platform, the range and diversity of which makes Delphi components pale by comparison. Again, backward compatibility is the only real issue. e. g if you have a lot of Indy code then the fact that you can use great libraries such as retrofit for Android apps to access RESTful services and websockets isn’t much help. but if you aren’t invested in a ton of legacy Indy code then you have many, many more options and some of them far more advanced and with greater productivity than those available for Delphi (retrofit being just one great example).

      Productivity of the IDE: You have the choice of Visual Studio or Fire (and other options coming soon ;)). Visual Studio needs no introduction and to argue that this does not compare favourably with Delphi in the productivity stakes is frankly ridiculous. Fire may be lacking some of the finesse of Visual Studio, but being a Mac native IDE in itself offers productivity benefits compared to development in Windows with the various bridging solutions to “connect” the IDE to the mac OS for mac OS or iOS development.

      For UI productivity: You can use Android Studio designer to layout Android UI files (though the nature of Android UI’s means that actually hand-crafting the XML is often more straightforward and accurate, not to mention productive). And you can use Visual Studio designers for .NET UI’s. And Xcode interface builder for mac OS/iOS UI’s.
      Again, the argument comes back solely to whether you have a ton of legacy DFM files that you cannot let go of (but there not even FireMonkey helps you, if the aim is to get those legacy UI’s onto anything other than Windows).

      And meanwhile, how does Delphi help you when it comes to building Azure .NET web services ? Or any .NET software component ? The answer of course is it can’t so the productivity score if you have such a project is exactly 0. πŸ˜‰

      Is .NET not part of the “real world” of software development ? I think you’ll find it is (much as we might wish t’were otherwise). πŸ™‚

      1. meh. If you’re going to move away from Delphi, you might as well just jump to C# in Visual Studio, no point in risking your future investment in something even less used than Delphi. Seems pointless to me.

        1. This is a very old skool way of thinking and is exactly what .NET set out to change.

          Sure, if you’re building an enterprise or commercial system then there are factors to consider other than individual language preference. But for other projects, it matters not one jot whether I use C# or Pascal or any other language that supports .NET. As long as it is a first-class .NET citizen and provides unfettered access to the richness of the .NET framework then it literally becomes a question of language preference.

          Using Oxygene I can consume .NET assemblies and I can provide .NET assemblies for others to consume using their own preferred language. I do not have to choose a language based on the choices of others.

          And I can (and should) be conversant in as many languages as are relevant.

          To paraphrase Neo, “I know C#”. I also know Pascal/Oxygene and have to also admit to knowing COBOL and FORTRAN and Modula-II and C/C++ and Java and SqlWindows and PowerBuilder (starting to show my age I think) and JavaScript (spit).

          Being familiar and proficient in a number of languages makes me well placed to decide which one I prefer to use when I have that freedom of choice.

          I simply do not understand people who confine themselves to one particular language simply because that’s what others have chosen.

    2. The Delphi ecosystem of libraries and components is barren. Oxygene gives you all the libraries of .NET. The NuGet package manager has over 65K free and open source libraries available! Torry.net has a little over 10K Delphi components and most of those are either hopelessly outdated or commercial demos.

      The Delphi IDE is still broken, and may be the only IDE on Earth that doesn’t let users create their own key bindings. πŸ™ Visual Studio is an award-winning, world-class IDE.

      Backward compatibility is a legitimate issue, but that doesn’t mean you can’t use Oxygene going forward for new projects. In fact, RemObjects just introduced a product that lets you call legacy Delphi code from Oxygene, which goes a long way to eliminating even this hurdle.

  4. I will never understand why someone would have chosen Pascal over C# to work in .NET. In fact, I would like to have the opportunity to use C# for Win32/VCL.

    1. I presume this is based on “Pascal” in the form of the Delphi.NET compiler ?

      If you looked properly at Oxygene I think you would instead ask yourself why anyone would choose to use C# ? πŸ˜‰

    2. Oh and also, the RemObjects compiler stack does allow you to use C# for Win32.

      Oxygene is only one compiler. RemObjects also have Hydrogene (C#) and Silver (Swift). They are also working on a Java compiler. All of these compilers sit atop their compiler technology stack, so all of those “front end” languages (front end w.r.t the compiler, i.e. source code) can produce code for any supported back-end (again, w.r.t the compiler, i.e. object code).

      Currently supported backends (it’s a growing list and more are planned) are: .NET, JVM, iOS, macOS, Win32/Win64, Linux (64-bit)

      So if you want to code C# for Win32, go right ahead – you can! πŸ™‚

      Of course, this doesn’t include being able to use the VCL (duh!) but it does mean being able to create a modern VCL equivalent taking advantage of modern architectures, patterns and language features that the authors of the VCL didn’t have available to them. You can then share your C# Win32 code with other Elements developers who might prefer to use other Elements language front-ends.

      If that’s what you want to do. πŸ™‚

    3. As Deltics already said, you can!

      What’s more, the “elements” compiler isn’t actually really split into Oxygene, Hydrogene (C#), and Silver (Swift). It’s one compiler that can handle all 3 (and in the future Java too).

      That means, you can take an existing C# project, open up the (msbuild xml) project file in a text editor, and replace the reference to microsoft’s C# compiler with the reference to the elements compiler instead.

      Open it in VS and you can still compile your project, just that it’s now being compiled by elements instead of MSCS.

      You can now simply add .pas and/or .swift files to that project (even things like a partial class that’s implemented 3 different languages for different parts).

Comments are closed.