2

I wonder if somebody tried creating a language where you can write mutations in a straightforward imperative style, and the compiler transforms the changes automatically to pure, Redux-style actions. For example (pseudo-js):

atomic function repaint_car(car) {
    car.color = "Blue"
    car.value += 1000
}

would be converted to:

{
    type: REPAINT_CAR,
    new_color: "Blue",
    delta_value: 1000
}

To clarify, I do not expect this to work for arbitrary code of course. You would apply it to a scope of code that mutates one or more plain-old-data (POD) objects. The language or tool would then create an equivalent "action" object.

I think this would help if you want to change something deeply nested in the state. It is often hard in functional programming to construct the new state, but in mutable code you just write state.garage[0].cars.cupholder.color = "blue" (as an exaggerated example).

You could also convert code to use the command pattern. While not functional, it does contain the mutation of state, and plays a similar role to Redux:

class RepaintCarCommand extends Command {
    constructor(color, value) {
        this.color = color;
        this.value = value;
    }
    function do(car) {
        this.previous_color = car.color;
        this.previous_value = car.value;
        car.color = this.color;
        car.value += this.delta_value;
    }
    function undo(car) {
        car.color = this.previous_color;
        car.value = this.previous_value;
    }
}

It would also make the transition from simple to complex app easier. If you start with a simple sequential app, and at some point decide to add undo capability, you have to rewrite everything to use the command pattern. This capability would allow you to isolate the mutations, without restructuring the app too much.

Does this idea have a name? Does any language implement this? Or are there reasons why this would be impossible or not a good idea?

(Note: I'm not looking for a language recommendation. This is a question about programming language design. I would like to know if this idea has merit, and hope to learn something about equivalences between mutable and immutable/functional constructs. Please don't close this as a "recommendation" question.)

10
  • 1
    Close voter, could you please tell me how to improve my question? I am not looking for a recommendation, a la "Should I use Haskell or C++", or "What is a good book on C++". I am hoping somebody could give a factual answer like "Yes, you can do that by transforming imperative code to SSA form, and then doing this and that. Read up on hyper-mutable-functional programming." Or: "This only works in very simple cases, a general transform is not possible due to Knuth's theorem and the halting problem."
    – jdm
    Jun 26, 2017 at 10:24
  • How should code like var car; repaint_car(car); alert(car.Color) be converted? What you are suggesting is EXTREMELY complex transformation. And simple code snippet is not enough to give full idea what is meant to happen.
    – Euphoric
    Jun 26, 2017 at 10:27
  • There are multiple options. Option 1: alert is defined as screen.windows.add(MessageBox(caption)). This can be reversed. Option 2: Compile time error, "function alert is not pure". I am suggesting it to be able to work on plain-old-data objects. I have no expectation to wrap format_drive() or fire_missiles() :-). But for something like "format this span of text bold" or "insert a line into this table", I have done the conversion myself, and it seemed so mechanical that a compiler could do it better.
    – jdm
    Jun 26, 2017 at 10:30
  • I was not asking about the side-effect of alert. I was asking how to make the code immutable, when car is changed inside repaint_car. And to make it much harder, see something like this : var car; car2 = car; repaint_car(car); alert(car2.Color)
    – Euphoric
    Jun 26, 2017 at 10:32
  • 2
    Also state.garage[0].cars.cupholder.color = "blue" is a massive code smell and shows misunderstanding of OOP design.
    – Euphoric
    Jun 26, 2017 at 10:34

2 Answers 2

5

The closest thing to what you are asking is probably Haskell's do notation, which lets you write code that resembles sequences of imperative steps, then translates them internally to a monadic expression. If you use a free monad, you can then create an interpreter for that expression that does what you want with it.

Another idea that comes very close to what you are asking, especially the deep nesting part, is lenses. Lenses are difficult to explain concisely, but have the same semantics of creating something that resembles an imperative mutation to the programmer, but that performs a pure operation under the hood.

1

I wonder if somebody tried creating a language where you can write mutations in a straightforward imperative style, and the compiler transforms the changes automatically to pure, Redux-style actions. For example (pseudo-js):

atomic function repaint_car(car) {
    car.color = "Blue"
    car.value += 1000
}

Well, this isn't quite that, but Haskell's state-transformer monad families, better known as the ST monad families (and not to be confused with the State monad family), comes pretty close.

With the ST monad families, you can write mutations in a straightforward imperative style, and the result turns out to be purely functional code. The thing about ST is that the compiler doesn't accomplish this by transforming your code into purely functional code; the underlying code is still imperative and stateful. The compiler merely provides a purely functional interface to this.

So one way of implementing your function above might be...

-- A car in a state thread "s" (a "Car s") consists of a string variable in
-- that thread (an "STRef s String") and an int variable in that thread
-- (an "STRef s Int").
data Car s = Car { carColor :: STRef s String, carValue :: STRef s Int }

-- Given a car in a state thread "s" (a "Car s"), we can perform
-- an action in that thread (an "ST s ()").
paintCarBlue :: Car s -> ST s ()
paintCarBlue car = do
    writeSTRef (carColor car) "Blue"
    modifySTRef (carValue car) (+ 1000)

Haskell treats all of the above code as purely functional: it can be called from other pure code without the need for that nasty IO marker or anything. The ST monad families use a bit of type system trickery in order to ensure that everything is deterministic and side-effect-free.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.