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

Reply via email to