9d80511470b05582fdb13329de734bda_400x400
June 11th, 2015 by john.warden@gmail.com

In my post on Procedures in a Pure Language – Part 1, I discuss how even pure functional languages can be used to create procedures that have effects, and how that is how things should be.

In Procedures in a Pure Language Part 2, I propose a little language where these impure procedures can coexist with pure functions in a way that makes the line between pure and impure very clear.

In this post, I propose adding a stricture to this language that ensures that, while procedures can have effects, they cannot have side-effects.

Here’s a quick review of a simple program in this language.

main = (console) -> procedure
  let greet = console.println("Hello, Bill")
  greet!

println and main are functions which, given a console argument, return procedures, which can be executed with the ! operator. Let’s call such procedures, which are bound to the object on which they operate, methods.

Containing Effects

Now let’s say add this rule to our language: procedures cannot execute other procedures other than methods on objects passed to them as arguments.

So procedures have no ability to directly reference or execute any other procedure: there are no built-in procedures like println, no global objects like Javascript’s window, and no ability to directly import services or objects like Java’s System.out.

Whoever runs the main procedure above can be certain it will have no effects outside of those that can be achieved by invoking methods on console. Since the caller has complete control over these methods, any effects of main are completely contained.

So these procedures can have effects, but since those affects are contained, they cannot have side-effects.

Contained Inconsistency

Along with not having effects, pure functions must be referentially transparent. In other words, they can’t be inconsistent. Let’s say procedures in our language can only be inconsistent as a result of invoking inconsistent operations on the objects that were passed to them as arguments. Their inconsistency is also contained.

“Impure” Inversion of Control

So a program can’t actually do anything unless it is provided with an object on which it can invoke methods. To OO programmers this sounds like dependency injection or inversion of control.

We can use given/inject preprocessing to achieve inversion of control in this language without the syntactic overhead of manual dependency injection.

given console
main = procedure  
  console.println!("Hello, Bill")

Procedures Inside Functions

There is no reason that a function cannot be pure, and still use procedural code in its implementation. For example:


greet = (name) ->
  mutable output = []
  output.push! "Hello, "
  output.push! name
  output.join("")

greet creates a locally-scoped mutable object, output, and manipulates it — thereby producing effects. But those effects are contained to output, which is not returned and therefore disappears when the function returns.

A functions may be implemented using temporary internal stateful computations like this and still be pure if these states cannot affect the caller or the outside world.

Sandboxing

Since any effects of executing procedures are contained to objects passed to those procedures, we can sandbox their effects.

Presumably, when we run the above program in our hypothetical language, the interpreter will by default pass the a real console object that will actually print to standard output. But let’s say we have the ability to create simple mutable objects that look someting like this:

    mutable mockConsole = object
      output: [] # empty list
      println: (message) -> procedure
        output.push!(message)
    

Now we can pass a mock console to main:

    main = (console) -> procedure
      console.println!("Hello, Bob")
    main!(mockConsole)
    mockConsole.output // ["Hello, Bob"]

Since all services that the main function might use to have effects (Network, Filesystem, etc.) must be passed to it, it makes commandline scripts written our language easy to test.

Conclusion

Requiring inversion of control for all dependencies on services that can be used to have effects, gives the caller of the procedure complete control over its effects: effects without side-effects.

Posted in Programming Language Design Tagged with: , ,

Pure
May 31st, 2015 by john.warden@gmail.com

In my last post on Procedures in a Pure Language, I discussed how even a “purely functional” programming language such as Haskell actually allows you to create procedures that have side effects, and how that is the way things should be.

I also defined the following wish list for a programming language where:

  • I can write pure functions.
  • I can also write procedures.
  • I can treat procedures as values.
  • I clearly see in my code when I am defining a procedure and when I am defining a pure function.
  • I clearly see in my code when a procedure is executed.
  • I can write procedures without the syntactic overhead of the IO Monad required in Haskell.
  • I can contain procedures to only operate on specific objects, so that I can limit their effects to a sandbox.

Proposed Solution

Suppose we start with a simple untyped language with an -> operator for defining anonymous functions, and a ++ operator for concatenating strings.

    f = (name) -> "Hello, " ++ name
    f("Bill")

Since this program has no procedures, it doesn’t do anything other than produce the value “Hello, Bill” when evaluated.

Now let’s add procedures:

    main = (console) -> procedure
      console.println!("Hello, Bill")

I have defined a function, main, which takes an argument named console, and returns a procedure.

The body of a procedure is a sequence of imperatives. In this example there is a single imperative, console.println!("Hello, Bill"). A imperative is to an expression what a function is to a procedure: both return values, but imperatives don’t have to be pure functions.

console.println, like main, is a function that returns a procedure. The ! operator says that this procedure should actually be executed, on not just returned, at this point in the code. Otherwise, the result of evaluating main would be a procedure that, when executed, just returns another procedure.

Methods

console.println looks like what you’d call a method in OO. I’m not thinking we’re defining an OO language here, mind you. We could easily have written this as println console, but I like the . syntax here. Either way, println is a function that is somehow attached to the console value — or more specifically console is polymorphic: console itself supplies the definition of println. We don’t need to go into detail of exactly how this works (types? classes? typeclasses?). I’ll just say that functions like println that are attached to objects are called methods.

The “Apply and Execute” Operator

The ! binary operator could be thought of as “apply and execute”, because it applies a function to its arguments, and then execute the procedure that is returned.

You can also apply a function to it’s arguments without executing it:

    let greet = console.println("Hello, Bill")

The ! operator can also be used as a unary, postfix operator, which simply executes a procedure (instead of calling a function and executing the resulting procedure).

    greet!

Operations

Methods like println, that return procedures are called operations.

The ! binary operator is used to invoke an operation by applying arguments to a function and then executing the procedure.

Summary

main = (console) -> procedure
  let greet = console.println("Hello, Bill")
  greet!
  console.println!("Hello, Bill") // another way of doing the above.

So main is a pure function that returns a procedure. println is an operation — a method that returns a procedure. println, like all methods, is also a pure function, because simply applying it has no side effects.

greet is a procedure, the result of applying println to its arguments in the expression console.println("Hello, Bill").

greet!, because of the presence of the ! operator, is an imperative.

console.println!("Hello, Bill") is likewise an imperative.

Summary of Definitions

  • Function: takes arguments, never has effects.
  • Procedure: takes no arguments, has effects when executed.
  • Method: functions attached to objects (enabling polymorphism).
  • Operation: method that produces a procedure.
  • Expression: has no effects, consistent.
  • Imperative: may have effects or be inconsistent.

Conclusion

We have defined a language that, like Haskell, allows us to define pure functions, which themselves can produce procedures that can be executed. The body of any function containing imperatives that execute procedures must be a procedure, just as in Haskell any function that uses a bind operation or do on an IO something must itself return an IO something. But our language has first-class procedures instead of the IO monad, and the ! operator instead of do or any of the bind operators.

Also just as in Haskell, “evaluating” the program has no side-effects. It just produces a procedure which you can then execute.

Our language doesn’t treat procedures as Monadic value as does Haskell. After a program is evaluated there is no need for something that can be bound, fmaped over, or stuck in a do block, since all that you will ever do with this procedure is execute it.

Also by treating procedures differently from monadic values, it is even easier to see exactly when you are invoking procedures. This will be helpful to a programmer striving to minimize unnecessary use of impure code.

Posted in Programming Language Design Tagged with: , , , ,