4
$\begingroup$

By options of purity, I specifically am talking about global properties across the entire language such as all variables being immutable, all functions being pure, BQN's having all primitives be pure and terminate, or to some extent APL's "everything is an array".

What these properties do is effectively limit the programmer/implementer, and I was wondering if this hurts or helps, and in what ways. I personally prefer functional style, with pure functions and immutable data, but it is limiting and most popular mainstream languages seem to allow as much as possible such as Javascript, PHP, and Python.

$\endgroup$
1

1 Answer 1

6
$\begingroup$

Another option, of course, is to allow controlled impurity, such as unsafePerformIO in Haskell.

Here's the approach we took in Mercury.

Mercury is a logic language, like Prolog, but with strong types, strong modes, and strong determinism. When it came to implementing a foreign function interface, we decided that this should have strong compile-time guarantees, too.

Mercury defines three levels of purity:

  • A procedure is pure when the set of solutions (i.e. its "outputs") depends only on the input arguments. Note that this doesn't mean that it can't do I/O! Mercury has linear modes which allow a pure way to perform I/O, similar to linear types in Clean or monads in Haskell, and strong determinism ensures that you can't backtrack over I/O.
  • A procedure is semipure if it is pure, but its semantics may be affected by impure predicates.
  • A procedure is impure if it may perform I/O or modify hidden state without declaring it in its interface.

You can think of semipure as meaning that it can "read" hidden state, but not "write" it.

No procedure, even an impure one, may change the declarative semantics of pure code. That is, it must honour its type/mode/determinism declaration.

Every place where an impure or semipure procedure is called must be annotated:

impure_predicate(X,Y,Z) :-
    this_is_a_pure_call(X,Y),
    impure this_is_not(Y,Z),
    % etc etc

You can also make a promise to the compiler that this impure code is actually used in a pure way:

pure_predicate(X,Y,Z) :-
    this_is_a_pure_call(X,Y),
    % The inside of this goal is impure, but the outside is pure.
    promise_pure (
        impure this_is_not(Y,Z)
    ),
    % etc etc

You can also promise purity at the procedure level via a pragma.

Promises of course can't be checked, but as Henry Baker rightly said, if you lie to your compiler, it will get its revenge.

$\endgroup$
1
  • $\begingroup$ I love this. Makes it obvious to a reader where code is pure and where it isn't. I have had issues with python when modifying code and assuming functions are pure, and then get greeted with awful errors. Thanks for the answer :) $\endgroup$
    – Brian BED
    Commented May 17, 2023 at 0:35

Not the answer you're looking for? Browse other questions tagged .