How Programming Languages Change How You Think
By: Matt Eland
Tags: Programming Languages, Dotnet, Software Engineering, Functional Programming

While I enjoyed the film and its concepts, it got me thinking about programming and how different programming language s might alter the way we think about application development.

This is a thought that stuck with me and resurfaced as I delved deeper into functional programming.

The Impact of Programming Language on Thought

Let’s take a quick tour of a few different programming languages. We’ll look at some sample code from each one and speculate at the ways of thinking that they might encourage or discourage.

Disclaimer: I am including basic information on several languages I have not extensively programmed in to offer a healthy variety of syntax. I will be relying on some speculation for these as well as my own personal experiences with the languages my knowledge and experience are deeper in.

C#

C# is an imperative programming language built to support object-oriented programming (OOP) with syntax inspired by C++ and Java. Like F# and Visual Basic, C# runs on .NET and can interact with other .NET programming languages.

Let’s look at some sample C# code from a game project of mine from years back:

Here we have a class from a game context that contains an ApplyDamage method. Code flows from top to bottom and may or may not enter if statements for conditional logic before it returns a number indicating the damage dealt.

This type of programming is very typical for object oriented languages despite the simplified example here and encourages the following types of behavior:

Thinking about the system in terms of individual objects

Focusing on if statements which may or may not need to execute

Adding properties, methods, and inheritance to manage complexity as things grow

I should note here that although C# was built as an object-oriented interpreted language, it is shifting to allow the language to support functional programming approaches like F#.

JavaScript

JavaScript is a function-oriented language built originally for the browser. JavaScript has since grown to support full-stack application and server development.

Here’s a snippet of JavaScript code from a different hobbyist game project of mine (seeing a pattern yet?):

This JavaScript code is similar to our C# example above in that it represents an imperative series of commands that the program will run.

There are some notable differences to C#, however.

Most notably, JavaScript is loosely typed. This means its variables don’t have a type defined in advance and can be reused for things of different types. Objects can have new properties dynamically added or even removed as the program runs. Functions can also be called with any number of parameters, including less than or more than the function expects.

As a result, JavaScript programs tend to feel a bit more loose than strongly-typed programming languages. This additional freedom creates some unique problems which additional syntax such as the rest and spread operators (…) among others have helped alleviate.

TypeScript

I hesitate to include TypeScript since TypeScript is just a different way to generate JavaScript, but I think it’s worth discussing briefly using code from — you guessed it — a different game project of mine.

TypeScript is really JavaScript plus some optional type declarations. The TypeScript compiler takes TypeScript code and compiles it down to working JavaScript code which is then understandable by the browser.

Because of this, TypeScript isn’t much different than JavaScript, but it does add type declaration statements.

It turns out that type declarations are actually a pretty big deal to how you think about your code. Instead of wondering what type something is or having to rely on accurate documentation, you can now see the type declaration next to the variable or parameter. This is a nice touch, however it does have drawbacks.

Because TypeScript relies on the programmer to accurately define the types, it pushes you towards types that are easy to represent via standard syntax. This in turn discourages you from passing functions around as parameters.

If you were to declare a parameter that is a function that takes in a Number and returns a Boolean, that syntax is possible but still difficult to remember, write, and read, so you naturally tend to do it less frequently.

Python

Python is another imperative language like C# or JavaScript, but it’s syntax is notably different:

Code example taken from The Algorithms

As someone who has only done bits of Python programming, I can’t speak too much to how Python changes your way of thought, except to point out how elegant the syntax feels compared to other languages.

It’s a very minimal style requiring indentation over brackets to define scopes. This reduces the level of noise in the code and helps you focus on the logic.

In my experience, this style of minimalism causes me to emulate it with very concise functions that call out to other functions, resulting in code that is nearly as compact as F#, but significantly more readable.

I also should point out a comparison to JavaScript and TypeScript with Python: Python allows you to add a type hint if you choose to do so (and is pictured in the code above). However, this feature was added since I last worked with Python and I cannot talk much about it as a result.

Go

Go (also called GoLang) is a more recent development and represents another statically typed imperative programming language.

Let’s look at its syntax because it’s a bit different in a few aspects beyond just where braces and brackets are placed:

Code example taken from The Algorithms

Go has a few syntax niceties I’ve not bumped into in other languages yet.

The for keyword is much more flexible, allowing for infinite loops if needed. Additionally, Go uses := as a shorthand for declare and assign, which keeps noise to a minimum. Finally, we see a quick way of swapping two values with the , and = operators.

I cannot write much how Go might impact how we think about writing applications, but I’d be curious about the experiences of those of you who have worked more with Go.

My initial reaction is that it takes a lot of the elegance from Python and applies it to syntax similar to C#, Java, and JavaScript’s, and I’m curious how far that additional “syntactic sugar” goes in terms of changing how you think about problem solving.

F#

Everything I’ve covered so far has primarily been an imperative programming language. F#, by contrast, is a functional programming language, which relies on entirely different patterns of code:

Code example taken from The Algorithms

F#, like C#, is strongly typed and those types are enforced by the compiler. However, more type information is inferred by the F# compiler and so actual type keywords are only needed sparingly. As a result, F# syntax is able to work more seamlessly with complex types, since the need to represent those types in syntax is significantly reduced.

F# syntax often deals with “piping” things from function to function to bring about an expected result.

We’ll take a more in-depth look at F# in the next section and examine a few cases involving piping before exploring a wider range of ways its different syntax may impact how you think about problem solving in code.

Note: F# is not the only modern functional programming language, but out of my familiarity with the F# language and the fact that this article is technically part of the community F# advent 2020 series, we’ll zero in F# for the remainder of the article.

Other Languages

If you’d like to look more into the syntax related to many of the currently used programming languages, I recommend you check out The Algorithms on GitHub for an open source set of examples including some other functional programming languages.

I’d also love to see your thoughts on how programming languages have shaped how you think about programming, so please leave a comment as well.

How F# Changed How I think about Programming

With this overview of languages aside, let’s dive deeper into the F# language and how its features change the way we approach code.

F# is not C#

When I first tried to learn F#, it was a difficult experience for me. It was my first functional programming language and terms such as monads, partial application of functions, and higher-order functions made my journey a difficult one.

Adding to that burden, as I learned F#, I was focused on “How do I write this C# code in F#?” so my first attempts at F# tried to create strong types with mutable state like you’d do in a C# program.

While F# can do that, it’s not what F# is about. F# isn’t about creating classes with mutable state and traditional members but about writing functions and composing those functions into solutions to larger problems.

In object-oriented languages we focus on encapsulating logic in cohesive objects and calling methods on those objects in a specific sequence. In other words, the classes are the stars of the show and their methods help things work.

F# encourages puts the focus on individual functions and pushes you towards immutable state and chaining functions together. The functions are the main attraction and the types that support them are left as minimal as possible (though F#’s typing system is very powerful and flexible).

Thinking in Terms of Pipes

When I think of F# applications, I picture a series of pipes connected to each other — each one meticulously designed to have exactly the correct output given a specific input and have no other side effects. This type of function is commonly called a pure function.

The individual pipes (or functions) are small and highly specialized, but together they make a very capable application.

F# as a language is very good at chaining things together thanks to its specialized operators such as the |> or “pipe” operator which takes the value on the left and makes it the last parameter to the function on the right.

Just looking at the presence of this one isolated operator in F# a multitude of things become easy in F# that would be harder in a language such as C#.