Doing Functional Things in Procedural Languages

Table of Contents

1. Preamble

This is not a claim that every language can be functional. It is purely about using functional technqiues in procedural languages. I'm putting this first so that I can direct all criticism towards it when functional programming fans jump up my ass for daring to compare functional programming with procedural programming in a manner that is not just shitting on procedural programming1.

I take inspiration from Higher Order Perl, a favored book of mine, for demoing this exact idea in Perl. I enjoy functional programming, for two reasons: first, it makes my code shorter, and second, it makes it less likely for my code to have bugs, so I don't have to worry about testing as much and can move on with my life2.

In this case, functional programming is essentially being used as a macro language for procedural programming, to reduce code size and make everything generally simpler3. But first, I want to go over a little language pattern I've noticed whenever this sort of discussion crops up online.

1.1. Language Issues

In general, functional programmers like to use "you can't do this" as a stand-in for "the compiler does not guarantee that this is done".

E.g.,

  • "You can't make a pure function" → "From a function signature alone, you cannot determine whether or not it affects global state or mutates variables"
  • "You don't have strong types" → "You don't have GADTS, and the type manipulations that you can do provide escape hatches"4

I find this unhelpful, because something can still be useful even if it is not enforced by the compiler. It will require discipline from the programmer, but it's still there, so it all comes down to what you, the user, really care about the most5

I also want to note that the gap between functional programmers and procedural programmers appears to have ben influenced largely by object orientation, paired with bad compilers for early functional languages6. OO appears to have convinced most working programmers7 that typing was about creating a hierarchy. A particularly salient example is Rob Pike's reasoning as to why Go doesn't really "do" types (emphasis mine):

One thing that is conspicuously absent is of course a type hierarchy. Allow me to be rude about that for a minute.

But more important, what it says is that types are the way to lift that burden. Types. Not polymorphic functions or language primitives or helpers of other kinds, but types.

That's the detail that sticks with me.

Programmers who come to Go from C++ and Java miss the idea of programming with types, particularly inheritance and subclassing and all that. Perhaps I'm a philistine about types but I've never found that model particularly expressive.

My late friend Alain Fournier once told me that he considered the lowest form of academic work to be taxonomy. And you know what? Type hierarchies are just taxonomy. You need to decide what piece goes in what box, every type's parent, whether A inherits from B or B from A.

He never discusses functional-style types (i.e., sum & product types)8. He views types as not "what is this thing and what makes sense to do with it" but "where does this thing fit in my hierarchy." No wonder then that EE-type9 programmers refuse to listen to functional expositions on the utility of type-based thinking, all they expect to hear is another rendition of shuffle your papers around on your desk for a week before you can actually do anything10.

1.2. Utility

So, if the compiler isn't enforcing correctness, what's the point of these mechanisms? Well, just because a compiler doesn't do something doesn't mean you can't. So this allows you to apply functional thinking, use functional algorithms, golf12, and avoid step-debugging where possible.

2. Functional Techniques That I Though Of

This is not a comprehensive list, just what I use the most. Currying & point-free programming are notable by their absence, but they're inherent to the language, so you can't really use them unless the designers added them in.

2.1. Recursion

This is pretty straightforward. Most procedural languages allow you to write recursive functions13, and most of their compilers support tail call optimization.

2.2. Map/Filter/Reduce

Easily done if you have access to function pointers or an equivalent. The Commander wrote a library(?) for map & reduce, though he doesn't use it.

2.3. Immutability

This is more of a discipline thing. Just don't mutate variables, and if you have to, mark them with a lil Hungarian thing14.

2.4. Higher-order Functions

Again, if you have function pointers or similar, you have higher-order functions15.

2.5. Pure Functions

Like immutability, it's a discipline thing, but it's not super difficult to pull off.

2.6. State Modeling Through Types

This is a bit trickier. You will usually have enums & discriminated unions for sum types, but enums tend to be represented by integers or booleans, and discriminated unions may or may not have escape hatches, so once more it falls on the programmer to verify behavior and not fat-finger themselves into illegal states.

3. Conclusion

Can you do functional things in procedural languages? Yes.

Do you want to? Maybe, it depends on your goals and what you care about. You tend to go down the language's happy path unless you put in effort not to, which in procedural languages really isn't a functional style.

Has this been a good use of an hour and a half of my time? Dunno, but it's not like I was gonna do anything except watch anime and play Factorio anyway.

Footnotes:

1

You know who you are

2

As opposed to looking towards code correctness as the end-all be-all. I only really care that my code is correct for the subset of inputs that I give a shit about, and everything else can be considered user error. I can get away with this because I primarily build tools for myself to use, so the damage is limited

3

Very Gopherian

4

Such that things like pattern-matching can't really be applied in the same way that they can be in a strongly-typed functional language

5

For example, if you prefer step-by-step thinking rather than equational thinking, then you won't give a shit about algebraic datatypes. If you think in "bottom-up" terms, then you generally won't care about things that make "top-down" thinking easier, and so on

7

I.e., not PLT enthusiasts

8

Interestingly, he does focus on composition (a functional priority), just from a different angle

Doug McIlroy, the eventual inventor of Unix pipes, wrote in 1964 (!):

We should have some ways of coupling programs like garden hose–screw in another segment when it becomes necessary to massage data in another way. This is the way of IO also.

That is the way of Go also. Go takes that idea and pushes it very far. It is a language of composition and coupling.

The obvious example is the way interfaces give us the composition of components. It doesn't matter what that thing is, if it implements method M I can just drop it in here.

9

The ongoing and eternal war between electrical engineers and mathematicians is the root for 90% of PL boxing matches. von Neumann was the Kwisatz Haderach who synthesized the two, but he's dead and no one is really willing to take a page out of his book

10

If they're any good, they already are thinking about how they want the thing to execute using representations they're used to manipulating; OO just adds an extra layer onto this that doesn't help because you end up doing procedural programming under the hood. Functional melds the representation & solution parts together with stuff like pattern-matching, but as I already stated, they've tuned out because they have other shit to do11.

11

"Oh but they should stop and listen anyway because you always want to be learning" They view proselytizers the same way they view schizo street preachers. You gotta SELL!

12

My favorite part. I've the APL brainworms

13

At least, I can't find one that doesn't

14

Like putting a ! on the end

15

Using Wikipedia's definition, which may or may not be the same as the reader's

Created: 2025-04-13 Sun 16:40

Validate