functional-programming

Is importing a third-party module pure?


Reading in from a file is not pure because of interaction with the outside environment, namely the file system. Variables such as versions of the libraries may give different results between runs. So is reading in a module not pure as well?

Is there a pure way of doing this agnostic of the language?


Solution

  • Interesting question.

    Is there a pure way of doing this agnostic of the language?

    This part of the question is fairly easy to answer: No.

    In Referential Transparency, Definiteness and Unfoldability, Søndergaard and Sestoft demonstrate a series of variations of languages that have quite different properties when it comes to referential transparency, definiteness, etc. including a language that is referentially transparent even though it's non-deterministic.

    Granted, they don't define what 'pure' is, but you often (e.g. Wikipedia) see referential transparency and purity used interchangeably. I've done so in the past, too, until I learned that they're not quite the same.

    If we do consider language, then, we may arrive at varying answers. In Haskell, for example, the typical way client code consumes a module is to compile or statically link a particular module. Thus, there's no standard 'hot-loading' in Haskell. If you want to use a new version of a module or library, you'll have to recompile your code.

    This raises another question: In which context does purity apply? We may, indeed, hypothesize that 2+2 is always 4, implying that purity (or referential transparency) is some sort of universal property. This seems quite Platonic to me, and I don't believe myself to be a Platonist.

    Consider, as counter-argument, that you encode a module's version into it, so that client code may query an API about its version. This would be a constant in the library source code, although we change that constant every time we modify the source code. In Haskell, we may give such an API the type

    version :: Version
    

    Haskell considers Version a pure value, even though such a hypothetical version value isn't universally deterministic. Conal Elliot points out some other, suspect APIs in the Haskell base library.

    (A parallel example is the notion of 'global variables' in imperative languages, which are neither global nor universal, since they don't persist across restarts, or are equal across multiple processes. Rather, a typical global variable is only a maximal element within a process.)

    What about other languages?

    In .NET, for example, you'll typically link libraries at compile-time, like Haskell, but you can also 'hot-load' dlls while an application is running. Is that, then, an impure action?

    I would tend to think that it is, but again it depends on context, and how you actually interpret what 'determinism' and 'side-effects' mean.

    For instance, in Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell, Simon Peyton Jones argues that he uses unsafePerformIO to load configuration values from disk. His underlying assumption is that configuration values aren't going to change while a process is running, and by that assumption, reading configuration values are 'pure' for the duration of the process. If you only dynamically load modules once during a process, you could apply a similar argument.

    On the other hand, if you have a long-running process, you may want to be able to 'hot-swap' parts of the code, including imported libraries, on the fly. If that's a requirement, the above line of reasoning no longer applies, and you may be well-advised to consider module-loading an impure action.

    As I understand it, one extreme case of that is Erlang, which is specifically designed to be 'hot-swappable'. (To be honest, I'm not that familiar with Erlang, so that's just my superficial understanding.) Erlang is often considered a 'functional' language, although I personally consider that label dubious.

    In the end, you probably need to ask yourself why you think this matters. Unless you're writing in Haskell, Idris, PureScript, or another language that explicit distinguishes pure functions from impure actions, you'll need to keep track of that distinction yourself.

    I'm not implying that this doesn't matter. It's an area of great interest to me, but in practice it turns out that these questions are context-dependent. The answer depends on the programming language, the scope of 'universality' you have in mind, as well as what kind of application you're developing.