Hacker News new | ask | show | jobs
by delegate 1546 days ago
I don't speak Rust well, but I can share my perspective from Clojure, where I tend to think in data. A program is a sequence of immutable functions taking input from the world and reducing that to instructions to mutate the world (aka side effects). The world here can also be some place that stores state. Ideally mutation is the last step of the pipeline, which takes the instructions and produces the side effects.

Or simply

input -> fn1 -> fn2 -> fnn -> mutate!

Where 'fn' are all immutable.

2 comments

That model fits functional reasoning. It doesn’t fit all programs, though, as it doesn’t allow the input to depend on the _execution_ of the mutation instructions.

Example: input “I step forward”, output “monster appears”, next input “I try to shoot it”.

That second input wouldn’t be there if the output weren’t executed.

You missed the point where a program can take inputs over time. In your example, the program takes “step forward” and goes through the flows, eventually mutating state at the end, including redrawing the screen or prompting the user for their next input. The next input is based on the current state of the machine, and there can be as many or few valid state transitions as you, the programmer, would like to manage.
What you propose requires some arrow leading (directly or indirectly) from mutate! to input. The model I commented on:

  input -> fn1 -> fn2 -> fnn -> mutate!
doesn’t have such a loop.
Your example isn't entirely clear to me - do you mean that “I step forward” and “I try to shoot it” are instructions in sequence? What exactly do you mean with allowing the input to depend on the execution of the mutation instructions?

In any case, what I'm going for is that in both imperative and functional programming languages I would have a function/method like "handleInput" that takes an input and decides what to do with it. The difference would be that in a classical OOP setting handleInput would be a method of your GameState class [1] while in FP handleInput would look something like "Input -> (GameState -> GameState)", i.e. a function that takes an input value and returns a function that transforms the game state in some way (alternatively and equivalently, thanks to associativity/currying: a function that takes an input value and a current game state and returns a new game state).

[1] I know, this obviously is a very contrived example, it'd only work for very simple games. Game programming patterns are interesting but not the focus here.

Outputting and manipulating side-effects can be useful even in imperative code.

I had real performance problems a while back attempting to build a very large file, my code could only produce the write instructions out of order and the file was too big to hold in memory, so what I ended up doing was writing writing instructions in radix-grouped batches to a bunch of temporary files, and then reading and evaluating them to build the large file.

This seems counter-intuitive, as it more than doubles both the amount of data written to disk as well as adds a reading-step, but doing it this way means the data is written in a way the hardware can deal with a lot more efficiently. Sequential access to and from the instruction files (off a mechanical drive), and densely clustered writes to the big output file. (on an SSD, strictly sequential writes matters less than being in the same block)

This reduced the runtime from several hours to like 5 minutes.

I think it's always fascinating to find situations where counterintuitively it is faster to do more work.

For example, it took me a while to realize that most of the it's actually faster to read/write compressed data overall - you'd think that reading from a disk and decompressing the data would be slower than just reading uncompressed data from a disk directly, but due to the vast difference in disk IO performance and CPU decompression performance it's almost always faster to perform disk IO compressed. I'm writing almost always since I'm not sure how the tradeoff looks for current high performance PCIe SSDs (or other storage devices with very fast IO).

> immutable functions

That doesn't sound right.

Oh you're right, having coffee at the terrace while typing on the phone, I meant 'pure' functions which take and return immutable data structures.