I can't seem to wrap my head around OOP. There's too many concepts in OOP. In FP it's just data and functions for transforming that data.
I can't seem to wrap my head around OOP. There's too many concepts in OOP. In FP it's just data and functions for transforming that data.
So many people think "OOP == classes", and I think it's really a shame that (in many ways superior) alternative representations were relegated to the sidelines for so long. I see Java's relatively recent inclusion of algebraic datatypes as a tacit admission that contemporary computing requires different primitives, and I expect to see a shift in best practices towards objects represented as immutable data structures combined with effectively-pure functions. Just like the FP folks have been saying all along!
> This work developed out of an initial attempt to understand the actorness of actors... Sussman suggested the experimental approach of actually building an "ACTORS interpreter"... When it was completed, we discovered that the "actors" and the lambda expressions were identical in implementation.
The Haskell (since you brought up Monads) equivalent to explaining Objects would be like explaining Records or Sum types, which would be very easy to explain and don't require talking about inheritance like Objects would.
And there are best practices for every single language and paradigm.
it takes 5 minutes to understand it, the jargon is complex, the implementation is not.
case in point
objects are a very leaky abstraction that usually brings people to build taxonomies, mostly unrelated to the parent.
reuse of code, which is the selling point of OOP,also comes often short with a miriad of very specialized subclasses that have nothing in common anymore.
OOP is not horrible, but it requires a good amount of discipline to get it right, while FP has less concepts and you can (usually) silo the "and now for the tricky bits" (cit. Robert Virding) in a small core.
the rest is simple pure functions that have also the benefit of being stupid trivial to test.
Also, you don't have to be purely functional nowadays, and that helps a lot.
If only I learned FP sooner, my life as a programmer would have been so much better.
There's a quote that floats around that's like "as soon as you understand monads, you lose the ability to explain monads." It takes a ton of leg work to make them click. They're simpler, but definitely not easy (in the Rich Hickey sense).
Be good at it?
More like a thousand hours.
there is nothing that is really simple, but before being simple, everything looks difficult.
p.s. I love Rich Hickey and especially his keynotes at Clojure conferences.
I've watched simple made easy at least 10 times.
Does this not apply to every language ever?
See, a monad is like a box. You can put something in the box and close it very easily. But once it's closed, you can't open it anymore. The action of closing the box is called the wrap operation.
What you _can_ do though, is tell someone else to do something to the thing inside the box. For example, if you put a toy in and close the box, you can ask a friend to go ahead and add a new one in. Or remove the toy. These are monadic functions.
That's it. That's literally it. It's just a pattern of hiding the data and letting monadic functions be the only ones that deal with it.
In the case of the Maybe monad for example, you'd have the unwrap function that tries to get whatever is inside out, but it might not be able to (if the box is empty).
I also find the appeal to a physical object to explain a functional concept is rather amusing.
I'm not gonna give you an explanation but I will say that IMO the best way to actually learn about them is to just look at the typeclass and a bunch of instances of it and how they are used. If you ignore all the theory (and the "theory") and just look at the code you will find that they aren't that hard to sort out from a usage perspective.
If you go back to Phil Wadler's original paper on implementing monads in haskell, he doesn't talk about category theory or boxes or anything. He lays out a handful of common things you might do in programming but which seem totally unrelated. For each one he implements a solution and then reveals that all these solutions fit the same interface.
The challenge was to describe it to a 5 year old. 5 year olds are better with concrete thinking than abstract reasoning. This is what made using the turtle in Logo a genius move, as it allowed younger children to write programs by conceptualizing a physical turtle that can move, rather than thinking in terms of an abstract function that mutates data.
For turtle geometry, it is also important to acknowledge the metaphor and shortcomings of a more traditionally analytic framing of drawings. Specifically, X and Y coordinates for a drawing are surprisingly hard to work with. For example, I challenge you to describe the fractal snow flake in an easier way than using directions as a metaphor. Same for the dragon curve. This can be seen akin to picking a different coordinate system, I presume?
Like if a bunch of smart people keep going on and on about something called a "floobiz", and every time they try to explain it to you, it just looks like a fucking cup. The problem isn't that you don't understand what a cup (floobiz) is, it's that you don't get why they keep going on about them like they're something special.
metaphors are good for beginners, to grasp the concept, they don't need to be perfectly valid.
for example the popular OOP metaphor a car is a vehicle becomes useless pretty much immediately.
I'm not opposed to metaphors in general, but metaphors are lot like abstractions. If you don't get the right one then the details leak everywhere and you may as well have not used it to begin with.
You want to assemble a 16-piece puzzle, but there's too many of your friends around for you all to work on it at once, it would be a disaster. So you invent a game. The rules are:
- Everyone only gets to put a single piece. There's 16 of your friends, so it works great.
- You all sit in a row on the floor, and the puzzle gets passed from the last to the next.
- Nobody can talk. All you can do is put a piece in and pass the puzzle.
You then realize that there's actually 17 of you (you have 16 friends and forgot to count yourself) so every turn one of you sits at the end, receives the finished puzzle and checks it.
It works great the first few times (you have _a lot_ of puzzles), but then something happens. One of your friends lost a piece. They can't talk, so they just panic and the turn ends.
What you need to do is change the game a bit so this doesn't happen again (it's better to get no puzzle than a panicked friend). So you add two rules:
- If you can't add your piece for some reason, you pass a piece of paper saying why instead of the puzzle.
- If you receive a piece of paper, pass it to the next person.
With everyone equipped with the paper and the piece, the next turn starts. Your friend lost their piece again, but there's no problem: you've got a lot of puzzles, and more importantly, your friend knows what to do. They write ‘I lost my piece’, and give the paper to the next person.
You, sitting at the end, receive the piece of paper and know exactly what happened.
Congratulations, that's the Either monad with a puzzle as the Right, and a piece of inscribed paper as the Left, and the ‘bind’ operation is the rule set that explains how you communicate with the next person.
You can use the same kind of explanation for State, except you come up with a game that uses the same object as the state and every ‘friend’ makes something different that the next one needs.
Note: Saying you lost your piece out loud would be like throwing an exception. I guess you can re-use this as an explanation for that.
I honestly think the best way to explain the Monad typeclass is by just showing the code. It really isn't that complex:
instance Monad (Either e) where
return = Right
Left err >>= _ = Left err
Right a >>= f = f a
And then show how you can use `>>=` to chain operations on `Either` values. Then you show a second instance, such as: instance Monad (State s) where
return a = State $ \s -> (s, a)
ma >>= f = State $ \s -> let (s', a) = runState ma s in runState (f a) s'
Then explain how you can use `>>=` here to do something seemingly completely different.Damnit, now I've accidentally written a monad tutorial :(
Don't worry about it! I'm very happy to receive criticism.
I tried to do it in this way because the task was to explain it to a 5-year old. I haven't (yet?) met a person that young who could understand an abstract language like Haskell. It's just not happening.
My explanation is probably indeed way too convoluted, but I still believe monads can be intuitively explained to a 5-year old somehow.
A monad is a description of actions to do and their order, in place of the actual actions. This description can be passed around and eventually acted upon.
Objects (not Alan Kay’s original ones but say Java) are abstractions that model state and behaviour together. Objects can share common behaviour through interfaces
But that explanation wouldn't make sense to a 5-year-old
In OOP it's just data and methods for manipulating that data.
Which ones? You're familiar with structs, right? OO is just structs with a little magic & sugar. Not even that much.
Objects, methods, properties, instances, classes:
Imagine if, when defining a struct type, you could put references to functions on it, such that any struct of that type would contain those same fields with references to the same functions you put in the type definition. Then, if you create a struct of that type, the compiler and/or runtime will helpfully and magically appends an extra argument to those function signatures, assigning it some conventional name ("this", perhaps) and, if you call such a function "on" a struct of that type, the compiler/runtime will quietly, in the background, pass a reference to the struct you called the function "on" in that last argument slot, so that within the function you can make use of the function's "parent" struct (as, perhaps, a variable named "this").The struct type with slightly-magical function references is a class.
The fields on the struct containing references to functions with the magical "this" argument appended when invoked, are methods.
A struct of that struct-type is an object, or instance of the struct type, if you will.
Fields on the struct are properties or members or whatever you like to call them.
Static:
What if you could tell the compiler/runtime not to bother appending that "this" argument to some of those functions you attached to a struct type definition? Or to have a given field on a struct type definition always point to the same location for every single struct of that type, so that they all essentially share a single variable? That's what "static" means. Inheritance:
What if you could tell the compiler/runtime that it should associate one or more other struct type definitions with the struct type you're currently writing, and that if it can't find a given field (including ones that are refs to functions, aka methods) on a struct of this type, it should check an associated struct of the other type(s) and only error if it can't find it there, either. With the result that a struct type so constructed effectively contains all the fields of the structs associated with it, unless a duplicate exists on that child struct type, in which case that takes precedence.That's basically inheritance. It's all about setting up and manipulating those kinds of relationships & precedence for lookups. That's all.
Abstract, et c.:
Just ways to have the compiler enforce constraints and requirements on a struct type definition. Final
I do solemnly swear this is a constant, not a variable.Now, there are implementation details under the hood for all this, but that covers actual usage, terminology, and concepts pretty well. You don't need to dig into the details of e.g. vtables (one tool for efficiently settling those inheritance-leveraging field lookups) unless you're implementing OO itself.