javascriptfunctional-programmingside-effectsio-monadpurity

How would a functional language actually define/translate primitives to hardware?


Let's say I have a few primitives defined, here using javascript:

const TRUE = x => y => x;
const FALSE = x => y => y;
const ZERO = f => a => a;
const ONE = f => a => f(a);
const TWO = f => a => f(f(a));

If a language is purely function, how would it translate these primitives to something physical? For example, usually I see something like a function that is not a pure function, such as:

const TWO = f => a => f(f(a));
const inc = x => x+1;
console.log(TWO(inc)(0));
// 2

But again this is sort of a 'trick' to print something, in this case a number. But how is the pure-functional stuff translated into something that can actually do something?


Solution

  • A function is pure if its result (the return value) only depends on the inputs you give to it.

    A language is purely functional if all its functions are pure¹.

    Therefore it's clear that "utilities" like getchar, which are fairly common functions in many ordinary, non-functional languages, pose a problem in functional languages, because they take no input², and still they give different outputs everytime.

    It looks like a functional language needs to give up on purity at least for doing I/O, doesn't it?

    Not quite. If a language wants to be purely functional, it can't ever break function purity, not even for doing I/O. Still it needs to be useful. You do need to get things done with it, or, as you say, you need

    something that can actually do something

    If that's the case, how can a purely functional language, like Haskell, stay pure and yet provide you with utilities to interact with keyboard, terminal, and so on? Or, in other words, how can purely functional languages provide you with entities that have the same "read the user input" role of those impure functions of ordinary languages?

    The trick is that those functions are secretly (and platonically) taking one more argument, in addition to the 0 or more arguments they'd have in other languages, and spitting out an additional return value: these two guys are the "real world" before and after the "action" that function performs. It's a bit like saying that the signatures of getchar and putchar are not

    char getchar()
    void putchar(char)
    

    but

    [char, newWorld] = getchar(oldWorld)
    [newWorld] = putchar(char, oldWorld)
    

    This way you can give to your program the "initial" world, and all those functions which are impure in ordinary languages will, in functional languages, pass the evolving world to each other, like Olympic torch.

    Now you could ask: what's the advantage of doing so?

    The point of a pure functional language like Haskell, is that it abstracts this mechanism away from you, hiding that *word stuff from you, so that you can't do silly things like the following

    [firstChar, newWorld] = getchar(oldWorld)
    [secondChar, newerWorld] = getchar(oldWorld) // oops, I'm mistakenly passing
                                                 // oldWorld instead of newWorld
    

    The language just doesn't give you tools to put you hands on the "real world". If it did, you'd have the same degrees of freedom you have in languages like C, and you'd end up with the type of bugs which are common in thos languages.

    A purely functional language, instead, hides that stuff from you. It basically constrains and limits your freedom inside a smaller set than non-functional languages allow, letting the runtime machinary that actually runs the program (and on which you have no control whatsoever), take care of the plumbing on your behalf.

    (A good reference)


    ¹ Haskell is such a language (and there isn't any other mainstream purely functional language around, as far as I know); JavaScript is not, even if it provides several tools for doing some functional programming (think of arrow functions, which are essentially lambdas, and the Lodash library).

    ² No, what you enter from the keyboard is not an input to the getchar function; you call getchar without arguments, and assign its return value to some variable, e.g. char c = getchar() or let c = getchar(), or whatever the language syntax is.