DEV Community

Jonathan Higger
Jonathan Higger

Posted on

Programming a Cake

As a beginner in the coding world, one of the hardest things to wrap your brain around is coding paradigms. When I first learned to code, I remember thinking "Oh Ruby is an OOP language so when I write ruby it's OOP" or "JS is an imperative language".

Welp, it turns out that in most languages you can code with completely different paradigms. In fact Javascript has no shortage of functional code, object oriented code, imperative code, and declarative code. That means that as a beginner, understanding what these different paradigms mean can be REALLY confusing. Hopefully this cake analogy helps a bit.

Imperative Cake

Imperative is like giving a set of instructions. To bake a cake imperatively, we have to break things down into very explicit steps.

get flour
get sugar
get whipped_cream
get cherries
get candle
get pan

mix = flour + sugar

put flour and sugar in pan

set oven to 400

wait for 5 minutes

put pan in oven

wait for 20 minutes

put on gloves

take out pan

add whipped cream to pan

add cherries to pan

add candle to pan

light candles
Enter fullscreen mode Exit fullscreen mode

Procedural Cake

Procedural is a type of imperative programming, but specifically you are now allowed to use procedures. A procedure is just a way of saying "Do these steps but call it something else"
Lets make a procedure called "Bake". It will allow us to dump in a pan, and a time, but will contain some of the minor details.

procedure Bake(thing_to_bake, time, temperature){
    set oven to temperature
    wait for 5 minutes
    put thing_to_bake in oven
    wait time
    take out thing_to_bake 
}
Enter fullscreen mode Exit fullscreen mode

Now we can simplify our imperative code

get flour
get sugar
get whipped_cream
get cherries
get candle
get pan

mix = flour + sugar

put flour and sugar in pan

bake(pan, 20, 400)

add whipped cream to pan

add cherries to pan

add candle to pan

light candles
Enter fullscreen mode Exit fullscreen mode

We're about to talk about the opposite of imperative code, but before we do, just know: ALL CODE IS TECHNICALLY IMPERATIVE, but just like we wrote a procedure that represents imperative code, we can make abstractions that allow us to not think about the steps, but instead think about what the steps create. That brings us to...

Declarative Cake

This one might look really stupidly simple, but that's kind of the point. A declarative system abstracts away the steps needed to make something, and allows you to represent the entire system as a transformation of the data that goes into it

<Cake
   toppings: [cherries, whipped_cream, chocolate_icing]
   candle_count: 1
/>
Enter fullscreen mode Exit fullscreen mode

And that's it, that's our declarative cake. One thing that confused me at first about declarative programming is how it tied in with "Functional vs OOP". A declarative system can be built with functions, objects, or even boxes in excel. Here are some other ways of representing a declarative cake.
An OOP declarative cake

new Cake({
   toppings: [cherries, whipped_cream, chocolate_icing],
   candle_count: 1
})
Enter fullscreen mode Exit fullscreen mode

A Functional declarative cake

createCake({
   toppings: [cherries, whipped_cream, chocolate_icing],
   candle_count: 1
})
Enter fullscreen mode Exit fullscreen mode

The reason why, as Web Developers, we tend to like declarative systems, is because it can greatly simplify the way we look at things. Under the hood, in order to make a cake you have to follow all the steps. Sometimes you don't care about how a cake is made, you just care if it's there.

For example, maybe you're the accountant for a bakery. Your job isn't to make cakes, it's only to count cakes and figure out how much the bakery has made. Your job isn't to concern yourself with how the cakes are made, you just want to make sure that the company is profitable. So instead of thinking about Cakes as all the steps to make a cake, just call it a cake and count em up!

As a web developer, declarative code is used in both the frontend and the backend.
On the backend we formulate abstractions like "Controllers", "Models", and "Views". We often don't know or care about how those things are interacting with each other, but we can change the shape of them to turn our backend into a system that processes signals in the way we want.

On the frontend, we use libraries like React, Angular, Ember, Elm, or Vue, so that way instead of writing document.querySelector for everything, our code looks more like the html that it eventually creates.

Functional Cake

So now hopefully you're starting to see the difference between imperative and declarative. Now we're going to talk about A Functional Cake. In functional programming, we make use of FUNCTIONS (Shocking). Note: Here we about to talk about functions in their intellectual purest sense, but many languages (like javascript, ruby, python for example) actually use functions more like procedures. For most languages, a function is NOT actually a function in the classical sense. WHY?
Technically, a pure function takes in data, and returns a transformed version of that data.

For example, think of algebra. Y = X + 5. We are saying here that if you plug in a 2, Y is 2 + 5 or 7. The "return" part of what I said before is basically that in PURE functinal programming, the function ALWAYS will equal some mathematical calculation of what you put in.

So in the case of our functional cake everything is just a function of our data. So here our data is our ingredients, and our toppings.

Our mixture is a direct function of what we're mixing
Our plain cake is a function of our mixture being baked
and our final cake is a function of adding the toppings to our plane cake

mixture = mix([flour, water, sugar, eggs])
plain_cake = bake(mixture)
final_cake = top(plain_cake, icing, cherries, candle)
Enter fullscreen mode Exit fullscreen mode

You can simplify this all into one big function

function getBakedCake(ingredients, toppings){
   top(
      bake(ingredients),
      toppings
   )
}
Enter fullscreen mode Exit fullscreen mode

Bonus Material Clean functional programming

If you think it looks weird wrapping a bunch of functions in this way you're not alone. As programmers our brains like to read from top to bottom as "Do this thing" then "do this other thing" then "do this other thing". But with functional programming it becomes a bit difficult to track the order because things have to keep nesting deeper to the right.

const prossessedByFiveFunctions = function5(
    function4(
        function3(
            function2(
                function1(
                    thing
                )
                function2SecondParam
            )
            function3SecondParam
        )
        function4SecondParam
    ),
    function5SecondParam    
)
Enter fullscreen mode Exit fullscreen mode

This sure f**king sucks to read!

We could clean it up by making a bunch of intermediate variables like

const processedByFirstFunction = function1(thing, function1SecondParam)
const processedBySecondFunction = function2(
    processedByFirstFunction, function2SecondParam
)
const processedByThirdFunction = function3(
    processedByFirstFunction, function3SecondParam
)
const processedByFourthFunction = function4(
    processedByFirstFunction, function3SecondParam
)
const processedByFiveFunctions = function5(
    processedByFourthFunction,
    function5SecondParam
)
Enter fullscreen mode Exit fullscreen mode

But functional programmers figured out a smart hack to clean this up. What if we made a new operator called the pipe operator, that allowed us to plug functions in backwards. Many functional languages use |>, javascript doesn't have one but if it did we could refactor our code to look like this (no stupidly named intermediate variables)

const processedByFiveFunctions = thing
    // leave () blank if you don't want to pass anything in as a 2nd param
    |> function1(function1SecondParam) 
    |> function2(function2SecondParam)
    |> function3(function3SecondParam)
    |> function4(function4SecondParam)
    |> function5(function5SecondParam)

Enter fullscreen mode Exit fullscreen mode

Now that's some sexy, readable looking function code (Although it does take some getting used to). Since JS doesn't have a pipe operator currently you can try third party libraries to do something more like this.

const processedByFiveFunctions = pipe(
    thing,
    (x) => function1(x, function1SecondParam),
    (x) => function2(x, function2SecondParam),
    (x) => function3(x, function3SecondParam),
    (x) => function4(x, function4SecondParam),
    (x) => function5(x, function5SecondParam)
)
Enter fullscreen mode Exit fullscreen mode

That brings us to our ultra pretty functional cake

const getBakedCake = 
   ingredients => 
   toppings =>
      ingredients
      |> bake
      |> top(toppings) 
// can be called like `getBakedCake(["flour", "water"])(["candles", "icing"])`
Enter fullscreen mode Exit fullscreen mode

Some important takeaways are that:

  • We never modified any variables (no mutations)
  • The whole system turns into one value (referential transparency)
  • No other parts of code were affected or called on (no side effects)

Without going too far down the rabbit hole, developers like functional programming because it's restrictions can yeild less chaotic, more predictable systems. There is an evergoing war between OOP and FP programmers. I've clearly chosen my side but lets talk about a the main caviat to functional programming.

If everything was a pure function, then you could not write good applications. That means that every good app breaks the functional paradigm at some point in order to actually do something. Think about it, anytime you actually perform any action it's not fully Functional Programming any more:

  • Log something to a screen? Side Effect
  • Change the state of a counter? Side Effect, and Mutation
  • Alter a database entry? Side Effect
  • Generate a random number? No longer a pure function

But just because you can't go 100% functional all the time doesn't mean that you don't see MASSIVE benefits by trying to minimize the chaos when you can. Functional advocates think of application state as Cyclops(X-men) see's his powers. CONTAIN THAT SHIT! Having eyeballs that can shoot a massive laser beam through anything is only useful if you know exactly when you are turning it on and what you are aiming it at. We want our apps to be superheros, not blasting holes in buildings on accident.

OOP Cake

Lastly we're going to talk about my least favorite, but still important type of cake... The OOP Cake. This may actually be one of the most important types of cake for web developers, because OOP reigned supreme in the industry as the defacto way of doing things for a long time.

In OOP, or Object Oriented Programming, we tend to simplify our code not as mathematical equations, but instead as objects that not only can store procedures on how to do things, but also each mantain their own state. Lakes make a cake object really quick.

class Cake {
    initialIngredients = [];
    toppings = [];
    isDone = false;

    consructor(ingredients, toppings){
        this.initialIngredients = ingredients
    }

    async bake(){
        await this.putInOven();
        await wait();
    }

    async top(toppings){
        // do something in herek
    }
}
Enter fullscreen mode Exit fullscreen mode

To be honest, this code looks kind of nice. In the Cake class, I have all my state neatly tucked into the class, and I have all my relevant methods right inside of the class itsself. Now if I want to create a cake and use it somewhere in my software I can just do.

const chocolateCake = new Cake(["brownie mix", "water"],["icing", "cherries"])
console.log(chocolateCake.isDone) // false
Enter fullscreen mode Exit fullscreen mode

looks pretty sleek and sexy! But actually I made a mistake, my cake isn't done I forgot to bake it. NBD lets fix that

const chocolateCake = new Cake(["brownie mix", "water"],["icing", "cherries"])
chocolateCake.bake()
console.log(chocolateCake.isDone) // true
Enter fullscreen mode Exit fullscreen mode

So it works, and isn't super ugly. In this small example, it doesn't seem super hard to look at this and say "Oh shit we just forgot to bake it."

The reason I don't like OOP very much is because, especially when you are working on someone else's 500+ line class, it becomes very difficult to reason about what state is there, why it's there, and what the sequence that things are being processed is.

Don't want to go too far down the "shitting on OOP rabbithole". Alot of REALLY GREAT software has been written in the style of OOP and a lot of REALLY SHITTY code has been written in FP and visa versa.

Summary

As a newb, or maybe even an experienced developer, it can be extremely hard to navigate through all the styles of coding out there. Truth be told, if you're like me, you won't really understand these styles until you do it for a while. For me, I didn't understand what declaritive code was until about a year into writing react code. After hearing it being explained 1000 times, and seeing examples of it, I started to make a mental picture of what it really means. The same goes for OOP, FP, and more.

The best advice I can give is to read a little and code a lot. I think spending 20% of your tech time reading / podcasts is a pretty healthy balance. Hopefully this little cake analogy can simplify this stuff in your head. In the long run understanding what these styles mean will not only help you write code, but also communicate better as a developer. I encourage you to try solving the same problem in different paradigms if you ever get the chance. I clearly have my opinions, go form your own! You can completely hate OOP cakes or FP cakes and still make a shit ton of money as a software developer.

GO MAKE SOME CAKES!

Discussion (0)