Functional Code Considered Beautiful
This is a modal window.
Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.
Formale Metadaten
Titel |
| |
Serientitel | ||
Anzahl der Teile | 170 | |
Autor | ||
Lizenz | CC-Namensnennung - keine kommerzielle Nutzung - Weitergabe unter gleichen Bedingungen 3.0 Unported: Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben | |
Identifikatoren | 10.5446/50542 (DOI) | |
Herausgeber | ||
Erscheinungsjahr | ||
Sprache |
Inhaltliche Metadaten
Fachgebiet | ||
Genre | ||
Abstract |
|
00:00
Funktion <Mathematik>Kontextbezogenes SystemSummengleichungLokales MinimumPASS <Programm>Erlang-VerteilungGrößter gemeinsamer TeilerDivergente ReiheCodeDatenparallelitätSoftwareQuick-SortFunktionale ProgrammierspracheObjekt <Kategorie>ZählenElement <Gruppentheorie>MusterspracheDivergente ReiheZeiger <Informatik>GeradeKontextbezogenes SystemImplementierungWurm <Informatik>Rechter WinkelResultanteHalbleiterspeicherMultiplikationsoperatorProzess <Informatik>ProgrammfehlerAppletStrategisches SpielEinfache GenauigkeitGanze ZahlCASE <Informatik>ParametersystemFitnessfunktionSoundverarbeitungAlgorithmische ProgrammierungErlang-VerteilungShape <Informatik>SummengleichungDatenstrukturKlasse <Mathematik>AggregatzustandVisualisierungHecke-OperatorCoxeter-GruppeFramework <Informatik>ProgrammierspracheAutorisierungZahlenbereichSystemaufrufBruchrechnungSchnittmengeSchlussregelDifferenteApp <Programm>TupelAlgorithmusDivisorThreadParallele SchnittstelleProgrammiergerätBitrateProgrammierungWort <Informatik>Abgeschlossene MengeLesen <Datenverarbeitung>Baum <Mathematik>p-BlockDokumentenserverCodierungNeuronales NetzMereologieUmsetzung <Informatik>GrundraumProtokoll <Datenverarbeitungssystem>MomentenproblemTreiber <Programm>MathematikGüte der AnpassungGraphfärbungOrdnungsreduktionSoftwaretestSichtenkonzeptProgrammbibliothekKomplex <Algebra>NeuroinformatikFlächeninhaltEndliche ModelltheorieGrößter gemeinsamer TeilerVererbungshierarchieLokales MinimumKontrollstrukturBefehlsprozessorRuhmasseMarketinginformationssystemPunktKonstruktor <Informatik>AssemblerVariableInstantiierungRekursive FunktionATMArithmetischer AusdruckNichtlinearer OperatorRichtungTypentheorieRepository <Informatik>KonfigurationsdatenbankPascal-ZahlendreieckSkeleton <Programmierung>HochdruckRippen <Informatik>POKEDreiecksfreier GraphLoopHardwareDivisionEchtzeitsystemTermFunktionale ProgrammierungAttributierte GrammatikKategorie <Mathematik>Ordnung <Mathematik>AussagenlogikQuellcodeBitVektorraumBlackboxSprachsyntheseObjektorientierte ProgrammierspracheRadikal <Mathematik>Arithmetisches MittelSchreib-Lese-KopfÄhnlichkeitsgeometrieRechenschieberGebäude <Mathematik>Produkt <Mathematik>Web-SeiteKalkülMathematische LogikSoftwareentwicklerVersionsverwaltungRauschenSystemzusammenbruchInformatikMatchingIdentitätsverwaltungStandardabweichungMailing-ListeNP-hartes ProblemGrundsätze ordnungsmäßiger DatenverarbeitungNegative ZahlMustervergleichZahlensystemBetrag <Mathematik>Differenz <Mathematik>DefaultGleichheitszeichenMinimumWeg <Topologie>AbzählenKonstanteIdentifizierbarkeitMultiplikationAusnahmebehandlungSystem FKonditionszahlDatenbankEreignisdatenanalysePoisson-KlammerHilfesystemErwartungswertDeklarative ProgrammierspracheMAPDifferenzkernPrädikat <Logik>Gewicht <Ausgleichsrechnung>SommerzeitMetrisches SystemExistenzsatzTopologieEinsComputerspielTotal <Mathematik>IterationForcingComputeranimation
Transkript: Englisch(automatisch erzeugt)
00:01
Good? Okay. Well, welcome. Thank you. My name is Garrett Smith. I'm a programmer at CloudBees, and I apologize for standing above you, but I had absolutely nothing to say about it. I like the other auditoriums, because they tower over the speaker. This is kind of weird for me. I should have a guitar or something up here.
00:21
Big speakers. We'll make the best of it. So I'm going to talk about this crazy topic of beautiful code. It's not like technical, that technical. It's not like we're talking about a database or how to make things go faster, the typical things that, you know, we as geeks really like. This is like humanities. It's like sociology or psychology,
00:42
aesthetics, something weird. But it's actually really, really important, and I'm super excited about this topic. As this talk came together, I'm starting to really believe what I'm saying. So I convinced myself that this is true, and we'll see at the end if you guys are convinced. All right.
01:00
So before we get started, I've been asked to mention this fptrack hashtag. So if you're going to tweet anything, basically the jeering and the throwing of tomatoes in real-time online globally, that sort of thing, use the hashtag fptrack, and I'm at Garrett, G-A-R-1-T. Some background. Okay, so in talking about this topic of beauty in code,
01:25
functional code is beautiful. I need to define first what I mean by beautiful. All right, so I need a few slides to take care of that. So there's this term in software called correctness. You'll hear it. If you think about code, if writing good quality code is important to you,
01:41
you'll run into this word at some point, correctness. Is my code correct? And can we reason about the code? Can we determine by looking at the code if this is correct? It's different from sort of brute force testing, which a lot of us are used to. We write some code, and we just test the living daylights out of it. We're not really sure if it's correct or not, but it passes the test.
02:01
So I like this term in one sense because it's good, it's positive, it helps us think about things, but I don't like it in another sense. It suggests that there's some sort of absolute standard of correctness. And I don't buy that. In software, I don't think that there's a god of software out there that says this is correct, and you must, therefore, meet the standard of correctness. To me, software is an experiment.
02:23
So we write some code, we get it to work, we put it into production, and we see what happens. There's no concept of correctness there. There's just what we do. So we got the code out there, we run it, we observe it. Okay, is it doing something useful? Does it seem to work? Do our users get some value out of it? Okay. Is there a problem with it?
02:40
Well, then we need to fix it. So this experiment goes back into the laboratory, and we make some changes to it. So the whole idea is you do this iteratively. So you write some code, you play around with it, you put it into production, you get some data, and then you recycle that back and forth. So it's iterative. There's no point at this idea of correctness manifest here.
03:01
But we need a term to talk about this. We need a term to rally our thought process. What do we want to do when we're building software? I like this word, intent. So I can't talk about what is correct, but I certainly can talk about what I mean to do. What am I trying to do here? Why are we here? Why are we in this room? Why are we listening? Why are we at our terminal?
03:20
Why are we typing? What is the meaning of what I'm doing? So we can talk about being very intentional when it comes to the software that we build. So this dovetails into this beauty. I'm kind of making this up. This is not an Aristotelian beauty here. This is my beauty. This is the Garrett beauty. It's expressing yourself in code
03:42
in a way that makes your intentions clear. That's my assertion. This is what beauty means in the context of this presentation. I'll repeat it. Beautiful code is code that is obviously, clearly, unambiguously clear in its intention. So when you look at the code,
04:00
you say, that's what this code is meant to do. It's deliberate. There's no, like, scratching your head going, I don't know what this code is doing. I don't know why it's written. That's not beautiful. So I'm going to be talking today about some methods or some principles or some ideas around creating beautiful code. That is to say, code that clearly reflects the intention of the author.
04:20
It's pretty high level, pretty philosophical. We'll get into some code. All right. Speaking of code. Okay, this is some Python. So I'm going to be using Python and Erlang in this presentation. So if you don't know those languages, it doesn't matter because they're simple, easy-to-understand languages. This is Python.
04:40
So I look at this code, and I ask myself, is the intention of the author clear? This is a function called what? InitArray. So presumably, it's something that initializes an array. I don't need to see documentation here. It's well-named. It takes an item and an array, two arguments. The array has a default value, meaning if you don't specify it,
05:02
this empty list will be provided as the default. If you do specify it, what you specify will be the argument, as you'd expect. So you can specify one argument or two, and the implementation is very straightforward. You append the item to the array, and you return the array. Clear? Simple? The intention is clear? Correct? I don't care.
05:20
I don't care if it's correct. I don't know. This is what I mean to do. All right, let's run this thing. So I'm printing the result of the invocation of this function, the call of this function. I pass the number one. And we get back, indeed, a list with a single element, just as we would expect. Everyone on the page here? Same page? All right, get ready. Let's call this same thing again with the same value.
05:44
That's a surprise. To me, that's a surprise. Now, there may be some very good, experienced Python developers here who say, no, that's not a surprise. That's exactly what I would expect. That's completely intuitive. Well, indeed, this is not a bug, and if you know what's going on, this shouldn't be a surprise.
06:00
In fact, if you call it again, you're gonna get this. This actually happens. You can go check it out if you're surprised. I'm not making this up. So what's going on here? So this function is an object in Python. This array is an attribute of that function, and it's created when the function is declared,
06:22
when it's defined, when it's constructed. So when you interpret this code, Python says, I'm gonna create this function, and this is when an object is created here, and it's associated with an attribute array. So what this is doing is it's modifying a property of the function itself. That's a big side effect.
06:40
That's a big surprise, if you don't know that. If you do know that, that's potentially very interesting, because you can now change a function when you call the function. So if you wanna do counters or other weird things, you can do that in Python. You can do all sorts of crazy things in Python. Very, very clever things. But that's what's going on there.
07:01
So we can judge this morally. We can accept it. We can be indifferent. I personally am indifferent. My point to this is to raise this topic of context. So when we're talking about being clear in our expression, right, so we're writing code. Beautiful code is clearly expressing the intent of the author. It's very important to understand the context within which code runs.
07:23
And what I'm gonna do is walk through a number of examples, different ways of building code and thinking about code that introduce different layers of context. And I wanna show you that reasoning about code, that is understanding what the heck is going on, is bound up in context.
07:41
So if we just take a step back, understanding what's going on is bound with the context of what is going on with that function. So I gave you that context. So in order for you to understand, I had to give you the background. And once I gave you the background, you're like, oh, yeah, yeah, totally. I understand. That's intuitive. It's not intuitive.
08:00
It's weird. All right, the first construct here, I'm gonna dive right into object-oriented. Most of us work with sort of object-oriented programming. I've done many, many years of this. In fact, by far and away, the most hours I've spent has been in the object-oriented camp. So I understand. I empathize with it.
08:21
All right, this is a class in Python. Let me walk through it. It represents a bank account. And there are four functions that are associated with this class. You can call them methods, but they're functions. These functions get bound to instances of this class. So if you create an instance of the account,
08:40
you get an object. All of these functions are bound to that object. So you can invoke them with the object.function or method notation. Because they're bound, Python will insert a reference to the object as the first argument in the call. So you get an extra argument to these. That's what self is. Think of self as a this, right, in Java, in C++.
09:03
So it's a reference to the object. This funny init thing, that's essentially a constructor. It's a convention, a naming convention that Python uses for the initialization of the object. And what we're doing is we're taking an initial deposit, and we're setting it equal to the balance. So we're instantiating. We're initializing the account.
09:22
All right, take a look at withdraw. This is an interesting one because it does something very clever. It makes sure that you don't take more out of the account than is there. So we apply a little min function. So I'm going to say I'm going to withdraw a certain amount. Self is the reference to the object. I'm going to calculate the minimum of the amount and the balance.
09:41
I don't want to overdraft this thing. So that's the amount that I'm allowed to withdraw. And then I'm going to deduct it from the balance, and then return the amount that I deducted. The intent is clear? It's pretty simple. Okay. Deposit is the opposite I'm adding, and then get balance is the way that I read the value. That's a pretty simple skeleton of an account
10:01
represented by a Python class. Here's how you use it. I'm going to instantiate the account with 100. Don't know what that is. 100-whatever. I'm going to withdraw 50. So that's the amount withdrawn. And then I'm going to read the new balance. I'm going to print the result. So this is how you use it. Very, very simple.
10:21
Nothing complicated here. But what I want to talk about is, in this particular case, context. What is the context that you're interested? So we're reasoning about this together. We're thinking, oh, what's going on here? What are the elements that come into play when you're implementing, when this thing is running, when this thing gets executed? The big thing that jumps out to me is this self.balance.
10:42
It isn't passed in to the function. I have a reference to self, but this thing is coming from someplace. So that's context. I need to know where that's coming from. As a person who's in charge of handling this implementation, this function, I need to know where this is coming from.
11:01
The rest of it is pretty self-explanatory. In the case of this particular implementation, it's coming from the constructor. And then any modification through the other functions. However, this is a class. And in Python, you can subclass. So in this case, it supports multiple inheritance,
11:22
so we're derived from two base classes, two parent classes. So you might call these mixins. Oh, sorry about that. Six. Let's see. All right. Hopefully that just applies to that. Well, who knows? Let's see.
11:41
Okay. So now we're invoking the super. Now that's gonna go delegate up into the initializers of these two classes. And that gets a little tricky. So if you've done object-oriented programming and you've had to sort out the context here, like what's going on, you may start to appreciate. You might start actually to become a little anxious right now and a little afraid
12:01
because you don't know what's going on here. I sure don't. I made this up, so I don't know. I mean, it's fictitious. But I have certainly spent lots of time digging into stuff like this. Now, keep in mind this represents a tree of subclasses. Right? So we're down. This thing could expand into all sorts of complexity. All that context is important to you when you're expressing the intention
12:21
of this particular function. You need to know what's going on. Now you can read documentation and trust it. That's probably what we do. But what if that documentation is wrong? You still need to know what's actually happening. It's kind of a big black box there. There's another area of context that we need to be aware of. And it's represented here by two functions
12:41
that modify the same value. What happens, postulating, if this thing gets run by two separate threads executing concurrently? That happens. It happens. Once in a while, you have to deal with concurrency. This is not thread-safe. This is going to go ahead and just blast this right here.
13:01
Boom. It's reading, and it's writing. This is also reading and writing. You've got all sorts of problems here. Now, I'm not saying it's fatal. You can easily fix this. You synchronize this, right? You wrap this with code that serializes these operations. You make sure they're atomic so that you're not modifying the same thing. But you need to know about that. That's more context that you have to think about. So we're sort of thinking about instance variables,
13:22
life cycle of those instance variables. Kind of like global, right? This thing's self-balanced. You don't know where that comes from. It's like a global variable, really. It's limited to the object, but as far as that function's concerned, it's global. And then you have to worry about concurrency. So these are things that you have to actually, in fact, worry about, because if you don't, your code's gonna break. You're gonna go out of business or something worse.
13:42
You'll get put out of business, because this will fail miserably in a concurrency scenario. Yeah, you have to think about that. So one of the ways that we can shave down some of this context, this background stuff that we need to worry about when we're writing our code is to move to a function, or I'm gonna call this function-oriented. So we're gonna get rid of classes.
14:01
Get rid of objects. Forget OO. Just deal with functions. This is old-fashioned programming. This is C programming, back in the day. We didn't have objects to do all sorts of crazy things with. We had to do this. All right, these are the same four functions. They're spelled differently. Rather than using a class, an object orientation,
14:21
I'm just gonna create a structure, some sort of data structure, to hold, to represent the account. And that data structure is gonna be opaque to the rest of the world. These functions know what it looks like, but the rest of the world doesn't, and they don't care. It's an opaque. It is essentially an object. The data structure I'm using here is a dict in Python. I just happened to use this.
14:40
I could have used all sorts of things. But I chose this, and I'm gonna store the initial deposit. So this thing here, this dict, is the data structure that represents the account. Think of it as a pointer to some contiguous memory if you're a C programmer. This happens all the time. You do a new. It initializes some block of memory up there, and then that pointer gets passed
15:01
to all of the functions associated with that object, that block of memory. So with the withdrawal, now I have to pass in the account, right? And the rest of it follows. It's all straightforward. You calculate the minimum. You read the balance. You subtract aloud, and then you assign the balance, and you return aloud. The same algorithm, just applied differently.
15:22
It's applied as a function. There's no objects here. Same with deposit, same with get balance. Make sense? Okay, good. Well, that was a resounding yes. Yeah. I mean, it's what objects are doing. Objects are sort of syntactic sugar for what's going on here. It allows you to do all sorts of dispatch
15:41
to different functions, but it's all doing this. But this is how you would use this thing. Create the account, right? I'm initializing with that function, new account. I'm withdrawing. I'm passing the account in, right? Same API, basically. I'm just not using account.withdraw. I'm using withdraw and then passing the data structure in here,
16:00
the payload, right? I get my result back, my new balance, and I print the result. Same thing. Okay? All right, so let's talk about context again. So we're gonna apply the same exercise to this. What's the context that we have to worry about? Well, there's no self.balance here.
16:21
So you don't have to worry about that. It's gone. It's literally gone. It doesn't exist. So I just saved you a lot of trouble right there. You don't have to worry about what is self.balance and how is it initialized and what are these superclasses doing with this thing? How is this? You don't have to think about that. It's gone. Everything that you need is passed in as an argument here to the account.
16:41
And you know that this was initialized by the function new account, right? Because this is the protocol that the user has to follow. So it's all here right in front of you. There's no context above it. However, what about concurrency? Is this thread-safe? This isn't close to thread-safe.
17:01
If you have two threads running these operations concurrently, account is being modified. That dict, that thing that represents the account, that's getting modified in place. Same thing here. You have to know that. And if you don't know that and you put this code in some situation where you're getting concurrent access, you're gonna blow your bank up,
17:22
and then you'll go out of business. So you have to know that. It's context that you have to know. But it's less context. So we've taken a step in the right direction. We've gone from something that was complex and had indirections and other, you know, to something slightly simpler. We can improve this, though. And this is what I'm gonna call purely functional.
17:41
So that was function-oriented, but this is purely functional. And I'm gonna make one change to this conceptually, and I'm gonna walk you through it. It's gonna look very similar, but there's one big difference here. So the new account is exactly the same. That is not modified. However, the withdraw function does something very interesting. Rather than modifying account,
18:02
remember the previous one that was modified? It creates a copy, a new version, and rather than simply returning allowed, it returns two things, allowed and the new account. There is no modification of this value here. None. Doesn't exist.
18:21
I'm instantiating effectively right here. This is what it looks like. A new account with the new balance. No modification. Only creation of new things. Same with deposit. The account is used to read the value, but the new value is created with a new structure here. A new value.
18:42
There's no modification of anything. Only new things. That's the usage. It's the same with the exception of now. I have updated account. Rather than just assuming that account gets... This doesn't get modified. It's never changed. This is updated, and so when I report the new balance, it's from the updated account.
19:03
So that's the usage of that. Does that make sense? So here's the payoff. It's actually profound. Here's the context. We don't have any self.balance, so there's nothing to worry about there. And concurrent access. Is this thread-safe? It is thread-safe.
19:21
It's quite thread-safe. You can run this in parallel as much as you want. Every call to one of these things, given two values that are the same, will result in the same result every time. There's no side effects. There's no modification of state. None. It's a new value. So I can run these things in parallel as much as I want. So you don't have to think about concurrency here.
19:41
I've just shaved off a concurrency problem for you. So now what? Now all you have to do is think about this problem. You don't have context outside of it. But there's a catch. This is Python. So you do have to worry about it. You don't know what someone else might be doing here because Python will happily let you modify things.
20:02
You saw that, right? You can modify the definition of function just by calling it, for crying out loud. It is easy to modify things in Python. So if you're using Python, you still have to worry. You can't sleep at night. You have to run all sorts of linting and checking and testing to make sure that you can actually reason about this without any fears.
20:22
All right, now, this is the payoff. You guys are waiting for the payoff, right? The actually purely functional. This is the actually purely functional model. We can't use Python because Python doesn't let us safeguard values. But there's a language that does. In fact, there are many languages that do. I like Erlang, so I'm going to use Erlang.
20:42
This is an actually purely functional implementation of this API. It is extremely similar to the Python version, but it's in Erlang. And I'm going to take a moment to explain the Erlang code here because I bet that most people are not familiar with this. That's okay. If you are, it's okay too.
21:01
Okay, so new counts of functions, initial deposit. What I'm doing here is I'm creating a different type of structure. This is sort of like the Erlang way to create a data structure. In Python, it was convenient to use a dict because Python doesn't do something like this. What this is, what you're looking at here, these curly brackets represent a tuple.
21:20
This lowercase account is called an atom, and it is an identifier. I could have said ACC234. I could make whatever I want. It's for readability. Think of it as like an enumeration where you say, you know, let red equal one and blue equal two, et cetera. The red and the blue, those constants, so you can read it better.
21:40
That's effectively what's going on here. But I don't have to declare this anywhere. I can just use it. So account is simply an identifier. And it's used in this case to tag this tuple. So if I see the tuple, I can have some notion of what I'm talking about. It's an account. So it's just a tag. It's a way to tag something. And then the payload here is a single value, which is the initial deposit. So the account is this tag account,
22:01
and that's what I know what it is, and the balance. Make sense? So that's when we create that. It's opaque. So the outside rule doesn't care about this anyway. When I do a withdraw, I pass this thing in, this structure in. And what's going on here in Erlang is called pattern matching. So it's going to take the value that's passed into this first argument,
22:22
and it's going to try to match it in shape to this pattern. So it's going to say, oh, does it start with two elements? Is it a tuple? Does it have two elements? And does it start with the atom account? Oh, it does. Okay, great. Well, the second value then is going to get bound to this thing called balance.
22:41
So effectively it's a way to sort of grab the payload very elegantly from the payload of this account and then use it. So now I'm going to use it by saying the min of amount and balance. That's allowed. The new balance is the subtraction, and now I am creating the new account right here. New account. Account with a new balance.
23:01
As hard as I try, I cannot modify anything in place in Erlang. As hard as you try, you can't. There's somebody in the room who made sure of that. In fact, there might be two people in the room who actually invented and made Erlang. And you can ask him, can you modify values in Erlang? And the answer is no, you can't modify them. So it is safe in that respect.
23:23
Deposit works the same way. The usage now is very similar to the Python version where we have a tuple here, and then we print the balance of the updated account. Now let's take a look at context because this is what's really interesting. There's no self.balance here. There's nothing outside of the function. So everything that the function deals with is passed right in here as arguments.
23:40
It's right here. Nothing else to worry about. Is it thread-safe? You bet. This is why Erlang is called a concurrency-oriented language because you can't mess things up because you can't modify things. You don't have to worry about contented access when you can't change anything. There is no context here.
24:00
There is literally nothing to think about or worry about ever outside of these two functions. Do you buy that? Do you believe that? This is true. I'm saying this is absolutely true. The payoff for this is this. You have this propositional calculus which says x can never equal x plus one.
24:23
You're dealing with a calculus of logic so things are true and things are false. And you get this wonderful isolation where you can reason very specifically about this without giving a single thought to anything outside the function. And this, in my definition, clarity of intent, is beauty.
24:40
This step into the functional realm lets us express ourselves very, very precisely without decorating, without introducing noise or context around the thing that we're talking about. This is very abstract. Still, I'm going to give you guys an example because that is going to...
25:01
So this is a principle. I took a few different models of building an API. Object-oriented, function-oriented, purely functional, kind of a convention of pure functionality in a non-pure, non-functional language like Python. The same thing would apply with Ruby. You might be able to get yourself into some type checking in other languages, but the way to really make sure
25:22
that you're not modifying things is to use a language that doesn't let you modify it, that treats this as a calculus. So we got into that point. Here's an example. So this comes from this book, Programming on Purpose, by P.J. Plauger. This was published in 1993, and it's a series of essays that he wrote.
25:42
And one of the essays is called... What's it called here? Probably doesn't matter. Writing Predicates. And in that, he talks about this algorithm, the greatest common denominator. And it goes something like this. The basic idea is to divide the smaller integer into the larger. The greatest common denominator is you've got two numbers, and you want to make sure what is the biggest number that divides evenly into both?
26:02
GCD. So the idea here is to divide the smaller integer into the larger and keep the remainder. So that's the first bit. If the remainder is zero, the divisor, the thing on the bottom, is your result. Otherwise, you replace the dividend with the divisor
26:22
and the divisor with the remainder and loop. Is that completely clear? It can't be. Don't tell me it's clear. I mean, you're smart. I said this, you're smart. If you think that's clear, it's superficially clear. It's one of those things that's like, okay, whatever. It makes sense. So I get nervous when I read things like this
26:42
because I'm literally, I have very limited ability to imagine things. So I'm like, I need to see this in code. So I took a look at the code. This is the pseudocode that he had literally on this page. I just wrote it in Python. I wanted to see if it worked. It does work. So I can run this thing. So let's walk through it. So I was thinking, I'll read the source code and then surely understand what's going on.
27:01
It'll make complete sense. And so I did this. I'm like, okay, m is equal to the absolute value of n. I get that. All right, whatever. And n is equal to the absolute value of n. Don't you get a little nervous here when you see this modification? I would love to build a slight anxiety in the back of your mind thinking, oh, that feels uncomfortable.
27:20
How can n equal an absolute value of n in all cases? No, no, no, only when n is positive. If it's negative, that certainly is not the case. It's a different equal sign, right? That's an assignment operator. You're poking stuff into memory there. We're not in a calculus, okay? So you can be anxious. All right, so we're modifying those things in place. Great. So then we're going to loop on n, the value of n,
27:44
until it equals zero, then we're going to stop. All right, that's our exit condition for this loop. So then I'm like, okay, temp. Oh, I really don't like temp. I don't know what that means. Okay, it's temporary. That's great. So whatever that is, it's going to go away, I guess. So that's the remainder. And then at that point, I'm just like,
28:01
okay, I don't know what's going on here. I really don't. Like this, you know, I read it. It sort of made sense to me. I'm looking at the code. This is even worse. Like this doesn't make, this makes much less sense to me. Like the English, so my goal was to like, I'm going to write this in Erlang. I want to see what this looks like, because in Erlang, I have a shot at like
28:20
actually maybe understanding what's going on here. So I thought, I'm just going to look at that. Nothing worked here. So I went back to the English. All right, so divide and keep the remainder. Divide and keep the remainder. Got it. I know how to do that in Erlang. It's a REM operator, infix. If the remainder is zero, the divisor is the GCD. Okay, I can pattern match on zero
28:41
and return whatever the divisor is, whatever that thing is. I can return that. Got it. And then I read else, otherwise, replace the dividend with the divisor and the divisor with the remainder of the loop. I'm like, oh, okay, I'm freaking out. So I just did this in steps. So here we go back to Erlang. So I know I needed a function called GCD with two arguments, M and N.
29:02
And then I'm like, I know I need to handle this operation because that's the main thing, right? So that's divide and keep the remainder. So I need to divide and keep the remainder. So that's there. And then I looked ahead a little bit. I knew I needed N for the next one. And here's the next one. So this is do something.
29:21
If the remainder is zero, the divisor, so that is N, is the GCD. So that's the result. So I'm done at that point. Otherwise, else, replace the dividend with the divisor and the divisor with the remainder of the loop. So I looked at that and the evil gaze for a while. I'm like, okay, this goes here and this goes here. Okay, that's it.
29:41
Now, by the way, this is in Erlang. Erlang has functions that have different heads. And a pattern matches in order. So if the first set of arguments don't match here, it falls into the next one. So if this is zero, then this will match. You'll get N as the second argument will be the result. If it's not zero, it will fall through to the next clause.
30:04
And then this gets bound to the remainder and then N is N. So this is the non-zero remainder of this operation here. And we're basically just flipping the results. So we're putting the remainder as N, so that's here and here. And then N becomes M right up here.
30:22
Now, notice anything weird about this implementation? Do something? How many of your functions are named do something? A few, come on, be honest. Actually, let me hold off on, I apologize. I usually will stop, but this is like a tight drama.
30:42
This is like a Shakespearean drama here. I'm sorry, the players have to play in this case. So here's why I said do something. I didn't know what it did. I knew I needed to do something. So I got this to work and I ran it and it worked. Okay, now, I have two problems that I could spot here.
31:01
Number one, anytime you're dividing, you should make sure that you don't have a zero down here. That's going to crash. So that's incoherent. We don't want a zero here. So I need to handle a zero case up here. That's easy to do. The other part is harder. What is the do something? What is that called? So I looked and I looked at this thing and I said, what is this doing?
31:21
What does it do? Well, it seems to be just handling the result of that operation. I don't know else to call it. So I just said, well, it's not do something. It's handle REM. Because that's what it's doing. The REM operation is handling the result of that. It's a little bit better. And then this is the case here. Again, multiple heads, pattern match on zero, return M.
31:43
So it doesn't matter what M is. As long as the other one is zero, the least common denominator in that case is going to be M. I also safeguard against the division. Okay. It seems like I'm done here because it works. But this second function really bothers me. Remember the whole beautiful code thing.
32:01
Does this express my intent clearly? It kind of expresses, it certainly expresses the algorithm that was described. But I don't really, I still don't understand what's going on. I don't understand how this works or why it works. And I know if I can understand this part, I have a shot at understanding the whole thing. And if I can put a name on this,
32:21
I might be able to get this right. But I just didn't know what else to call it. So when I get stuck on something like that, I tend to just change things. It's like when you're doing a puzzle, right? And you say, I'm going to put this puzzle, the jigsaw puzzle, I'm going to put this piece over here, and it doesn't fit. Sometimes you just turn it and see if it fits. It's not, maybe not very sound, like it's not very smart,
32:42
but you're just like, I don't know, let's try it and see if it fits. Oh, it didn't, let's try it again. So I thought, well, maybe if I just play around with these arguments, it'll help my brain to understand what's going on. Maybe in fact, this isn't the remainder, right? If this is N, maybe this is M. If I thought about this as M, would this help me? Would it help to trigger some thinking in my brain
33:02
to maybe sort of intuitively understand what is going on and be able to explain this to myself? Okay, so now this is called M. Right away, my brain starts to see, oh, these look similar. I've got a zero up here, and this is returning that value. All right, that looks similar. This kind of looks similar too.
33:20
So why don't I make it even more similar and just flip these arguments? So I'm just gonna flip the order of the argument, flip the order of the argument. Correspondingly, I need to flip this, flip it around, like flip these two and flip these two, right? This is just like math type of, this is like an identity. All right, so I'm just gonna flip it. All right, I flipped it.
33:42
Okay, wow, now this looks even closer to this thing, except this is N and this is M. Well, I don't know what these actually are. Maybe I just named this wrong. So if I name this thing M and this thing N, so I can just turn in the puzzle piece, just randomly. This is like literally what happened. This is how I'm dealing with this.
34:01
So I did that. I renamed it. And then I looked at it, and I'm like, well, so I think I might be able to put a name on this after all. I think what we have here is the exact same function. Interesting. That's a really nice realization when you're coding. Oh, actually, I only have one thing here.
34:22
There isn't two things. If I do that, except I don't do that, right, because who wants three lines when you can have two lines further reduced, right? Yeah, well, thank you, math. Thank you. Thank you, math. Great, universe. The universe dropped this into our lap here.
34:43
So I was very happy. I'm like, oh, look, I reduced this complex algorithm to two lines of code. I'm like, oh, it's two lines of code. That's really great. It's really elegant. But something magical happened in that moment. I was, like, so happy that I reduced it. But then I actually read it. I was like, so what's going on here? Oh, interesting. I actually understand this algorithm now.
35:03
This is a reduction strategy. I need to get one of these things to zero. I don't exactly know what the magic is with the remainder part. But I know what's going on here. I'm trying a remainder with one, and if it's zero, I'm done. But if it isn't, I'm flipping it.
35:21
You can see it right there. I'm flipping it, right? I go from one on one side and I pass it to the other. And then I use the remainder of the other part to try again. And I just keep doing that until it finally gets reduced. And this works. That's the algorithm. And it's like, oh, that's pretty cool. That's a really elegant, you know, sort of a searching strategy, flipping back and forth,
35:41
and now I have the result. I understand this now. It's pretty cool, I thought. So then just to really revel in this, I went back to this thing. This actually makes more sense to me now. The problem is that this is described as an algorithm,
36:02
as a set of instructions. So we're still viewing the computer as a CPU in memory. Do this. Poke this up here. Loop here, right? Execute cycle. Push this onto the registry, onto a register. Pop it off. Perform this operation, right? We're still writing an assembler. It's just higher level. C, Python, Java, whatever.
36:21
It's still this imperative model of telling the computer what to do. That's what we're doing here. This is declarative. It's describing sort of a mathematical relationship. But it also can be executed. This is one of the benefits of functional. Functional programming wants to drive you into a deep understanding of what you're doing.
36:41
And it introduces a completely new world of programming. So let me stop for a minute. And talk about my personal experience with this. The brave new world of functions. So I spent some time at University of Edinburgh in their artificial intelligence program
37:02
back a long time ago. And they used ML. So we were given all of our classroom assignments in ML, which is a functional language, and developed at University of Edinburgh. And everyone seemed to understand it but me. I was writing things like this.
37:22
But much, much more complex. Really deeply nested recursion and crazy things. People would navigate a maze, and it was like two lines of code with massive recursion and all sorts of different things. And I'm just like, I have no idea what's going on here. So this is like the opposite. I was using a functional language to confuse the daylights out of me.
37:41
So two weeks into that, I left. I'm like, I can't deal with this. I'm going to get an F. This is going to be horrible. I'm going to suffer. And I didn't like it. It was terrible. So I just moved to the computer science department where we dealt with C. And we monitored CPUs and other nice hardware-y type things. And I was very happy. Pascal, C, I get that stuff. So I was terrified of functional languages. I was terrified.
38:01
I was terrified. I don't like them. So if you have this sort of apprehension about using a functional language and starting to actually use that for your day-to-day work, I completely understand that. And it was probably a good 15-plus years of using OO, imperative, you know, everything. C++, C sharp, VB. Yes, Visual Basic.
38:23
Python, Java, all this stuff. And I felt that was pretty good. I felt like when I handed code off to a team or to somebody else that my intent was expressed very clearly. So I ran across a problem that was very difficult to solve using the tools that I was using. So it was Java and Python at the time. It was a currency problem.
38:41
So it was like one of these big concurrency things where lots of things are happening at the same time, a lot of complexity. And there was so much complexity in what I was doing and the libraries that I was using that these things would deadlock mysteriously. And the libraries were so opaque and complicated. I'm talking about clarity of intent. This was like entering the world of the insane asylum where you're just like, I have no idea what anyone is ever saying
39:01
other than that it's wrong and crazy. That's all I know here is that this is a crazy place. So I go into this code base, and I said there's no way that we can fix this. There's simply no way because we'd make a change to some piece of code and it would be that much worse. So it's like things would deadlock. We have this critical piece of code that was running, and then it would just freeze. We had to restart it. It was a monitor. And you can't be restarting monitors.
39:21
What do you monitor it with? So this is a problem. So there's this language called Erlang which handles concurrency really well, so let's use that. So the last thing I wanted to do was deal with functional programming. So I used Erlang. In fact, solved this problem very directly and very easily, very effectively. It was great. It was a great, I think, technical success.
39:41
But then I started to get into this stuff, this way of thinking, the same exercise that we went through, trying to take an algorithm or my understanding of a problem and reduce it to this type of expression. And something happened to me. This is kind of a conversion experience. It's like the get religion experience. I'm sharing this with you.
40:01
I changed my point of view about Erlang. Rather than thinking that Erlang is the best because it's highly concurrent, Erlang is the best because it's functional and it changed the way I approached solving problems. It changed the way I wrote software. And if there's one thing I could ask programmers to try and to do this is spend time in a functional language
40:20
that doesn't let you change state. All of this is driven by immutability. Immutability is another word for propositional calculus. So when you say that x is one, darn it, x is one. It isn't two, sorry. Try it. Do you just try to assign it to two after you told me it's one? The language will blow up in your face because it's ridiculous.
40:41
It's obviously one. You said it was one. Crazy, you're illogical. I can't deal with you anymore. That world view is a brave new world of functions. Here's sort of my take on this. It drives intent. It forces you to really understand and describe clearly what it is you're doing.
41:01
It makes it easier for you to read, but it also makes it easier for the other person to read when the other individual approaches your code. So this is driven by this question, what should this function do? I'm going to assert that answering the question what this function should do is functional programming. It's extremely hard. You will spend hours trying to figure out
41:21
what this function does, and it's a process. Remember, it's like experiment, try to get something to work. Get something to work, right? Fiddle with it, observe it. Okay, I've got a baseline here, and then you think about it. What's going on here? Why is this working? What should I name this? What is this actually doing? And then out of this emerges
41:41
this sort of cogent world view of your code. Who's heard of, not duck typing, but rubber ducking? Rubber ducking is great. So rubber ducking works by taking a duck and putting it in front of you and explaining your code to the duck. That's effectively what we did here. That's effectively what we did in this experiment.
42:01
I was trying to explain what the heck this thing was doing to the rubber duck. Well, I'm the rubber duck in this case, or you're the rubber duck. So if you don't have a rubber duck, you can take a person or a figurine or a glass or whatever else. You explain this to yourself. You explain it to the rubber duck. If you can't explain it to the rubber duck because the rubber duck says, I don't get it, you know when the duck doesn't get it. The duck just like looks at you blankly and saying,
42:21
I ain't buying it. This just doesn't make any sense to me. Then you reduce. You go back and really understand what you're doing. And then when you can say, oh, it's this strategy of flipping the values back and forth and when you get to zero, you found the result. Then the duck is like, oh, okay. I get it. I get it. I'm not sure about that whole remainder thing,
42:41
but it seems to work. And you've done a pretty good job of explaining the strategy. And it's two lines of code. So the duck is like happy. Now, that is a difficult process. And I believe that if you drive your code, don't settle. Don't say, oh, it's do something. Do something. Handle this and then leave in fear because you really don't know what happened.
43:01
You don't know why it's working. You just like test the daylights out of it. If you don't settle, this view of what you are doing, what is your intention will surface. And I believe your code will be pristinely beautiful. All right. Stop all of this. Where are we at?
43:20
All right. Why am I saying this? I hate religious dogma, especially in technology. I hate it in general, but I really hate it in technology. I don't want to persuade you as a fanatic of some religion. I don't care about that. What is the actual driver here? Why should you care about this?
43:41
So I started this whole thing out around, you know, these concepts of intent and beauty. What does this really mean? So here's what's driving it for me. Software is a conversation between humans. That's really wishy-washy. But it actually is a conversation.
44:01
You're writing code, you're laying it down for yourself and for your colleagues, for your users, for people who are forking your GitHub repository. You don't have to have GitHub to use Git, by the way. You can just have Git. You know, fork your Git repo, make changes. It's for them.
44:20
It's for your colleagues. It's for you later on. You lay the code down, because there is no standard of correctness. You're not going to walk away from this code. If you're lucky, maybe you can walk away from it. But chances are you're going to revisit it at some point. And when you revisit it, you won't remember it. You won't remember what it did. And if the clarity of intention isn't there, it's like having a really, really bad, horrible conversation with somebody.
44:42
What is a mark of a good conversation? There's respect. You listen. You try to understand. You communicate clearly. You say what you mean. You mean what you say. The process of writing software in this conversational mode where you put something down as an experiment, let it run, and be prepared to visit,
45:00
is a matter of respect for somebody else. You're taking the time to express yourself clearly. This is that concept of beauty. What's the actual payoff? Forget beauty. Forget religious sentiment. What's the payoff? Time. When you can approach a bug. Let's say you have a bug in your code. And you have a framework like this
45:21
where you approach your code, and it's like this is exactly what this author is doing. It's completely obvious. Oh, this is why it's a bug. This is why it's a bug. He or she didn't really understand this particular piece, so the bug can be considered and reasoned about in the context of a rational framework. If you have no idea what's being said
45:41
or what the intention of code is, how do you approach bugs? With fear, with trepidation. Joe just gave a presentation and talked about how in certain code, if you may change a single line of code, you'd introduce 2.5 bugs. I don't know if that's an actual metric. It's probably more than that in a lot of cases.
46:00
So I go into code. Oh, I think this might be the bug. You change it, and you pray to the gods of imperative state mutation. Please let me be lucky this time. You pray that the test passed because if the test passed, you're probably right. If the tests are okay. If the tests are not okay, you might not be all right.
46:21
It's a horrible process. Do you relate to that at all? You go in, you change something, you don't know what's going on, and you're just like, goodness gracious. And how long does that take? So again, forget religion. Is time important? Yeah, that's important. Is quality of code important? Pretty important. You've got buggy code, it's not going to last very long because someone's going to write the same code,
46:41
it's going to be better, and your code's going to die. It's just like Darwinism, gone. It was eaten by something that was better. That's important. We can buy into that. That's not religion, that's science. That's Darwinism. That's survival of the fittest. Create fit software. What happens if you need to make a change to the behavior, a change to the logic of the account functions?
47:01
You have a framework to approach it and think about it. Adding features. Any change at all can be approached rationally if you have a rational framework. So, this is not an aesthetic discussion, it's not a religious discussion. It's very important to what we do. And I believe functional programming, functional programming, these languages. By the way, Microsoft has created a really, really nice
47:20
functional programming language called F sharp. So, if you are interested and are a .net programmer, try it. What's the harm? Just try something. Like, say, hey, this guy seems to think that we might be able to write better code if we use this, so just try it. Like, spend a few weeks on something. What's the worst that could happen? You'll learn a new language, you'll get some experience with this sort of thing,
47:42
and then if you don't like it, throw it away. Forget about it. But if you do, it may change things for you. The way that Erlang, in this particular case, really has affected me, and I think improved the way I write software. All right. With that, we have a grotesque amount of time for questions and answers, but that is good. So, I'm going to stop there,
48:02
literally, stop, and open it up for Q&A. If there aren't questions, I will force you to ask questions. Yeah.