Profile picture
your friend myk @mykola
, 40 tweets, 8 min read Read on Twitter
ahem, I am getting my thoughts together to start recording my course on thinking like a function, and because I like to think out loud y'all are about to get a twitter thread about it. Feel free to mute, but I'll be rambling about this for a bit to get my head on straight.
So! Thinking like a function! Step one, what's a function?

I'm so glad you asked! A function is a map between a domain and a codomain!

Not super helpful, right? Is there a better way we can reason about these super important building blocks of computation? Yes!
What we really mean when we say that is this: a function is a rule that associates some sort of output with some sort of input.

Crucially, the same input return the same output every time! Sometimes, this is obvious! sum(1, 3) will always return 4, right?

But is this general?
What about a function like:

function logtime() { console.log(Date.now()) }

Is that mapping input to output?

As a matter of fact it is, but to understand why it turns out you have to completely change how you think about functions. It's worth it!
"But myk! There is no input to that function! And it doesn't return anything!"

My dude I once thought as you did. But here's the trick: input isn't exactly equal to arguments, and output isn't exactly equal to return value. To grok this we must briefly transcend language!
We've said that a function must be an unambiguous and idempotent map between input and output, and then we showed a function that takes nothing, returns nothing, and behaves differently every time it's called.

So we have a choice. We can accept that our definition is wrong, or
we can understand that when a programming language implements the `function` keyword it's giving us a mechanism with which we can express a function - it's not giving us the function itself.

Think about it this way:
`function sum(a, b) { return a + b }` seems like a reasonable way to express the sum function, right? This is javascript, so it even handles floating points for us. Just as long as a, b and a+c are all numbers which are less than Number.MAX, right?

Gosh what a sudden constraint!
Intuitively we understand that actually the concept of adding two numbers together is not intrinsically constrained by 32bit floating point memory limits, right? We get that our `sum` function in javascript is actually a subset of the one true universal idea of adding numbers?
So this is a really important idea, because it starts to break down for us the notion that programming languages are capable of 'containing' functions. They're not. At best they can express some small subset of possible functions, and must comply with physical constraints.
So this means that we're liberated from having e.g. JavaScript define what a function is, right? Whenever our formal definitions come into conflict with what a language lets us express we now understand that the language can be (and often is) wrong.
So how does that help us understand what's going on with that `logtime` function above? We said either our definition of function is wrong or our language is not accurately expressing the full mapping, right?

So let's think a bit harder. What's breaking?
We expect the value of Date.now() to change over time, because intuitively we understand that that's what that function does.

What we don't see is that the current value at execution time _is an input_. All inherited scope is an input to your function.
Similarly, outputs have nuance - this function returns nothing, but it _does_ cause text to be written to your console. To understand why this is still a function, you have to understand that all changes to the environment made by a function _are its output_.
"Oh gosh, you're right!" I can hear you saying. "Does this mean that all of the painful mocking I do in my test suites is just there because I need to control the input to my functions but have no easy way to express that idea?"

Yes, my friend, that's exactly what that means.
So it turns out that functions are relations. If that word sounds familiar it's because SQL databases turned an obscure mathematical field called relational algebra into the way we efficiently store information, and there's a lot of value there.
A relation is a set of tuples - this is a fancy way of saying it's a table, with rows and columns. Relations can express all sorts of really complex things, and thanks to relational algebra we know how to do stuff like joins between multiple relations to fully capture complexity.
The core idea is this: each row in your table represents some truth. Let's say you have a table that looks like this:

1,1,2
1,2,3
1,3,4
1,4,5
2,1,3
2,2,4

etc. all of these numbers exist in an arrangement which provides meaning. Can you see what this table expresses?
If you said 'Gosh Myk that looks like the beginnings of the sum function!" you'd be right. Do you see it? Each row specifies two inputs and one output. Each tuple's third value seems to be the sum of the first two.

Can we imagine this table as infinite?
Compare this to our earlier JavaScript implementation of the sum function, which had artificial limits imposed by runtime constraints. Which of these two models is a better expression of the sum function?

Trick question: better for what? The second doesn't run.
But! Understanding that you _can_ think of functions as tables is a huge huge huge insight, and underlies the rest of what I have to say. I'm going to be continuing this thread throughout the week.

The next important thing to talk about is data types, and it's cool. :)
Okay, time to talk about data types! To recap, please read the thread above. Crucial concept here is that you can _think about_ functions as infinite tables where some columns represent input and some columns represent output, right?
So to bring back our tabular representation of the sum function, it looks something like this:

1, 1, 2
1, 2, 3
1, 3, 4
2, 1, 3
2, 2, 4
2, 3, 5

etc. So far so good. But like, there's something really convenient about this example - all of the values are integers.
If you're trying to model JavaScript behavior, you could totally have rows in this relation that look like:

1, 'hi', '1hi'
'foo', 'bar', 'foobar'

etc. Intuitively we understand that once we do this we're no longer a sum function, we're something else, right?
The reason we can think about the sum function as a sum function is because we intuitively and automatically data-type our columns. In our clean example, we've got an implicit rule: nothing can show up in any column that isn't a number.

This is called a data type.
There are a number of ways to define data types, and if you frame it just right you can make a computer scientist's head explode by handwaving over it the way I'm sort of about to do. But that's okay! We're learning a mental model, not a gospel truth!
You can think of a datatype as the set of all values that are allowed to be present within a given column. We can say they must be strings, or integers - but we could also say that they're your favorite numbers, or the memory of the taste of the best strawberry you've ever had.
The point I want to hammer home here is that our programming languages are _severely limited_ when expressing types. If we are thinking about functions strictly as programming mechanisms we mistake the set of expressible types for the set of all types, and it's not.
So! We can in our mental models define a heavily, arbitrarily restrictive set of types. In fact, look at my sum function examples - they're not exhaustive, each column contains one of a finite set of digits, right?
So let's bring it all together. A function is a table where some columns are inputs and some columns are outputs. The valid values of each column are determined by the data type we want to apply. Rows which violate one or more column data types are invalid, they fall out.
So this is how you reason about functions as tables, how you can enforce datatype validation (at the conceptual level) to constrain the size of your tables vertically.

But what about horizontally? Can we add and remove columns? What would that mean?

I'm so glad you asked!
Let's add a column to our `sum` function:

1,1,2,+
1,2,3,+
1,3,4,+
2,1,3,+
2,2,4,+
2,3,5,+

A column represents something that can change. If we make the + explicit, we can change it and add the following rows _to the same table_:

1,1,1,x
1,2,2,x
1,3,3,x
2,1,2,x
2,2,4,x
2,3,6,x
Do you see what we just did? By adding a column representing the arithmetic operation, we've just accidentally discovered _abstraction_.

I'm here to tell you that abstraction is nothing more than adding a column to your mental table. And it has a dual - concretion!
What would it look like to remove a column from our mental model? We do that by restricting the data type to a single value. Let's say our first column can now only be the number 2 - we no longer need to include it. We can just do the following:
1,3,+
2,4,+
3,5,+
1,2,x
2,4,x
3,6,x

Does this remind you of something? How do you perform concretion in JavaScript? Let's redo that same operation, but in JavaScript.
function math(input1, input2, operator) {
if (operation === '+') return input1 + input2
if (operation === 'x') return input1 * input2
}

const concretized = math.bind(null, 2)
In other words: when we partially apply an argument to a function in JavaScript, we can think about that in terms of constraining the datatype of the first argument to a single value, which can be left out.

It's still an input! We just don't have to think about it.
So _if you want to think about it this way_, you can model the concept of scope inheritance as a form of partial application.

const sum = (a, b) => a+b
const add2 = sum.bind(null, 2)

OR

value = 2
const addValue = a => a+value

Do you see why these are the same operation?
(It's perhaps not correct to say that scope inheritance is a form of partial application - rather, they're different ways to express the concept of concretion. All programming requires us to get really good at this, but we never really think about the unifying abstractions!)
Anyway, I'll leave that here for now. Tomorrow we'll talk about higher order functions, and what it looks like when the data type for one of your columns is a function, rather than just a normal value.

Hint: tables compose!
Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to your friend myk
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member and get exclusive features!

Premium member ($30.00/year)

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!