Magical Interactions

I want to talk about this little function:

func interact(_ process: (String) -> String) {
    var input = ""
    while let line = readLine() {
        input += line
    }

    print(process(input))
}

Brief explanation: it reads all the input from stdin as a String, feeds it into a closure process, which it takes in as its only argument, and prints process's output.

Here's how one might use it:

// counts characters from stdin and prints result to stdout
interact { String($0.count) }

Got that? Well, now I'm going to rewrite it in a slightly less Swift-y way:

let count: (String) -> String = {
    return String($0.count)
}

interact(count)

The argument for interact got defined with a name and an explicit type signature.

So, what's so special about this interact function? Two words: side effects. More precisely, it took away the concern of side-effects from the user. count belongs in the realm of pure functions. It has no worries about file handles or operating systems. It's (String) -> String. I wanted to emphasize this with the rewrite. Look at that empty line. Now you see a boundary between 2 worlds.

This may all seem contrived. But when I learned about this function in Haskell, I was blown away.

It's like a great magic trick: you are presented a scenario, say, writing a little script. Maybe you need to process some CLI output and print out a CSV or JSON string (literally 90% of the script I write). A Haskell programmer would jump into the bottom level of the problem and start writing these little pure functions: one to split the string, one to convert some numbers, one to manipulate a list, one to match some patterns...gradually the broken-down absractions get built back up via function composition. You can see the light at the end of the tunnel, yes, yes! If you feed this list into that function that returns a string you'll have the right value to print out! Okay, now the problem is solved in the pure functional world! The only thing left to do is...

Now, the setup of the magic is complete. Now, you are onboard with the solution, you thought the problem through with the magician...you are distracted. The ending came so...quickly, but unexpected. What? You just feed your solution to this interact function and...that's it? I was expecting some readLines or prints (okay, at least 1 print statement)!

That's the thing: interact deals with two side effects, the input and the output. But its user deals with zero. It's as if the two effects "cancel" each other out! It's a neat trick, really. Small, low-key, easy to miss. But I'm glad I noticed it and come to appreciate its power and simplicity.