@strict

bearophile bearophileHUGS at lycos.com
Sun Feb 14 07:47:55 PST 2010


Pure functions are nice because a smart compiler can sometimes perform some optimizations with them that aren't possible with normal functions. But for me their main advantage is that they help avoid some bugs, because they can't modify global state nor change their behaviour on its base (we can ignore memory overflow, and maybe even exceptions). My Python/Delphi programs that are mostly composed of nearly-pure functions are often the ones with the lower bug count.

I have seen that's true in OOP too: methods that are static (and don't use static attributes, they are pure methods, essentially) are often the less buggy ones. So I use static methods where I can, every time I can write a method that uses just the input arguments.

But programming in D with just pure functions is not always handy, and in OOP I sometimes must alter instance attributes, so they can't be pure. And sometimes using a variable that's not an argument of the function produces a little faster code/function.

So in D I can think of something intermediate between a pure function and a normal function/method, something that keeps code flexible but with a lower bug count. For this I can think about an attribute @strict that can be used on a free function, a nested function, an object method, and even a class (it means all it methods are @strict). Even @strict modules are possible (it means all callables inside it are @strict). (If I will ever design a new imperative language this will be the default behaviour. Using outer names randomly as in C/Java/C#/D is bad. Python3 partially avoids this trap with its "outer" and "nonlocal" statements).

@strict denotes a normal function/method, where all the names (variable) it uses inside are written down in an explicit way. Some of such names are normal function arguments, the other names can be global variables, variables from an outer function, instance attributes, static class attributes, enums.

// inside here types are optional
@strict(in int y, in z, out k, inout w) int foo(int x) {
  // ... code
}

This function/method "foo" takes int "x" as argument. Foo can't use outer/instance/static names beside "y","z","w","k" ("y" and "z" can't be written inside foo, "k" can't be read inside foo, and "w" is read/write by foo).

Some alternative syntaxes, a better syntax can be invented:

@strict_in(int y, z)
@strict_out(k)
@strict_inout(w)
int foo(int x) {
  // ... code
}


@strict int foo(int x) {
  @in int y;
  @in z; // types are optional
  @out k;
  @inout w;
  // ... code
}


@strict [in int y, in z, out k, inout w]
int foo(int x) {
  // ... code
}


@in int y;
@in z;
@out k;
@inout w;
@strict int foo(int x) {
  // ... code
}


@strict int foo(int x) {
  @strict_in int y;
  @strictl_in z;
  @strict_out k;
  @strict_inout w;
  // ... code
}


@strict int foo(int x) {
  @nonlocal_in int y
  @nonlocal_in z
  @nonlocal_out k
  @nonlocal_inout w
  // ... code
}


Why it's useful: system theory says that improving the separation of subsystems you reduce unwanted side effects, improving the reliability of the whole system.

That's why D modules too must produce a better isolation: as in Python "import bar;" has to import only the "bar" name in the namespace, so for example there are no new global variables and no surprises. This is a small change to the D module system that will improve D code. A simple syntax like "import foo: *;" can be used for the current (to be discouraged) behaviour.

Bye,
bearophile



More information about the Digitalmars-d mailing list