HN Theater @HNTheaterMonth

The best talks and videos of Hacker News.

Hacker News Comments on
Stop Writing Classes

Next Day Video · Youtube · 5 HN points · 28 HN comments
HN Theater has aggregated all Hacker News stories and comments that mention Next Day Video's video "Stop Writing Classes".
Youtube Summary
Jack Diederich
Classes are great but they are also overused. This talk will describe examples of class overuse taken from real world code and refactor the unnecessary classes, exceptions, and modules out of them.
HN Theater Rankings

Hacker News Stories and Comments

All the comments and stories posted to Hacker News that reference this video.
Nov 03, 2022 · 3 points, 0 comments · submitted by alexmolas
Reminds me of this talk, "Stop Writing Classes": https://youtu.be/o9pEzgHorH0

Changed my perspective on this newfound tool I'd learned to use called "indirection".

My favorite quote is something like "don't waste 10 minutes now to save yourself 2 minutes later".

Monoliths and Microservices: https://youtu.be/gOZFmFNl1uk

Types vs Tests: https://youtu.be/apu-J0msaiY

Stop Writing Classes: https://youtu.be/o9pEzgHorH0

Breaking up your test suite: https://youtu.be/9_3RsSvgRd4

Jan 23, 2022 · Attummm on Pydantic
Its onlt recently that classes are embraced by python community. Before that dictionaries where seen as pythonic and classes where not.

https://youtu.be/o9pEzgHorH0

"Lots of times people think they might need something later. You don't. Or you can just do it later if it comes up."

https://youtu.be/o9pEzgHorH0?t=339

For glue code scripts like this you usually don't end up using encapsulation and inheritance or any other fancy OOP design patterns. It's usually grungy IO-bound procedural code, the sort of thing you'd write in bash if bash weren't terrible for long scripts. Don't burden it with premature abstractions. If you end up needing a class, just write the class later.

Jtsummers
It's important to remember that YAGNI is a guideline not a rule. If someone spends 10-minutes thinking about the future instead of writing code immediately, and they realize a need that introduces some extra upfront effort but reduces longterm maintenance (fewer changes in the future because it already matches the desired structure), then YAGNI doesn't apply. For standard work, this is a pretty common situation to find yourself in: Recognizing that you've done X before and it led to a particular structure, so when doing X' start with the final structure instead of insisting on 100 extra refactors along the way.
“So I spent a week doing OAuth2 stuff. It took a couple days to read through [Google’s] ten-thousand lines of code, so then I went and looked for other libraries. There's `python-oauth2`... it's the second version of python-oauth, it doesn't actually do OAuth2. But that took a while to figure out. So that one was a little bit better, 540 source lines of code, 15 classes, but I wasn’t going to put that in either because it didn’t actually do what I needed to do. So I rewrote that to just do bearer-token authentication, it’s ‘python-foauth,’ or if you prefer, ‘eff oauth.’”

-- Jack Diederich, “Stop Writing Classes”, https://youtube.com/watch?v=o9pEzgHorH0

neolog
In cases where the name is ambiguous you can use another name like python-oauth-v2.
0xbadcafebee
With CPAN this was solved by hierarchical package/class naming.

  Net.pm
  Net/IMAP.pm
  Net/IMAP/SSL.pm
^ Each of those is published by a different author, and inherits the previous one to add features. As a result CPAN is a tremendously useful library of OO, not because they're all different, but because they are designed to inherit and extend existing code, rather than re-write the whole thing and have 500 differently-named-almost-identical codebases.

CPAN's been around for decades, and everybody talks about DRY code, and yet no other language seems to want to actually reuse and extend code.

I'm still amazed at how Python code with type hints looks so different to Python without them. Consider the program near the end of the article - if we remove the type hints:

- The `AstNode` abstract base class becomes completely unnecessary and can be deleted.

- We can't use `@dataclass` and are forced to write an `__init__` method for each class. But this is good, right? "Explicit is better than implicit"?

- It's then clear that our three classes each have two methods, one of which is __init__. Following the advice from "Stop Writing Classes" [0], each class should just be a function (in this case, a generator).

These changes reduce the code above "AST representation of the Ruby statement" from 70 lines to 40, and simplify some of those lines as well - for example, `arg_reprs = (arg.representations() for arg in self.args or [])` becomes simply `arg_reprs = args`.

The knock-on effects of type annotations seem to be doubling the complexity of the code. This is why I'm far from convinced that static type checking in Python is worth the cost.

[0] https://www.youtube.com/watch?v=o9pEzgHorH0

BiteCode_dev
It's more of a design issue here. You could do away from classes while keeping types by using 2 lines:

    Expr = Callable[[str]], Callable[..., Iterable[str]]] 
    AstNode = Expr | Callable[... ], Callable[..., Iterable[str]]]
You then don't event need to call representation() , as each node is callable. No classes needed.

But my instinct tells me a node will eventually have more attributes and methods. Stuff to get parents, children, siblings, filtering and so on.

So it's probably a good call to use a class for later. Also you want to keep AstNode as a node could be an expression, a statement or a comment.

rkangel
To echo the sibling - I believe the most important skill a developer can have is reworking* code, and doing it readily and continuously. If you can do that, then you don't ever have to worry about what code MIGHT do in the future, you just design it for what it DOES do and then rework it later if necessary. It's quicker and the result is cleaner. Also, even if you tried your hardest to predict what was going to be needed, there are always things you can't predict and at that point you either rework your code or have bad code.

* I could have said refactoring, but some people have very specific definitions attached to that word so I use something more general here

mumblemumble
> But my instinct tells me a node will eventually have more attributes and methods.

I couldn't agree more with Jack Diederich's thoughts on that sentiment, at 5:40 in the video linked above:

https://youtu.be/o9pEzgHorH0?t=340

> Lots of times people think they might need something later. You don't. Or, you can just do it later.

nerdponx
YAGNI is a double-edged sword. It's almost no additional overhead to write a class now if you think you might need to add methods and internal state later. But it's quite a bit of refactoring work if you decide not to write a class and then realize you need one after the fact.

Maybe this is an indictment of Python as a language, but I think it's a case of not taking anything too literally. YAGNI fetishism is like DRY fetishism all over again.

mumblemumble
I guess? At the same time, working in some languages (Racket and C come to mind), it's surprising how long I can go without anything ever becoming a class, ever. Seemingly indefinitely.

My sense is that many of us are taught to think in terms of classes and methods instead of data structures and functions, so it's very easy to slip into thinking you need objects in order to manage state, even when that's not the simplest way to do it.

Strictly speaking, though, I think that the only time in Python that you really have no workable alternative to classes is when you also need dynamic dispatch.

Groxx
Personally: it's much easier to YAGNI in languages with relatively-strong compile-time checks and relatively-sophisticated tooling.

If you lose either of those, a little bit of preventative work goes a loooooong way. The friction (largely due to risk) of correcting things later is so large that it just starts snowballing unless your team is extremely attentive.

In that sense, Python is... middle of the road, with an extremely broad spread. Tooling is an odd blend of surprisingly good vs incapable of doing simple inference / detecting all calls, and type safety depends heavily on how you write things (some tactics are fairly strong, many are so weak as to be useless or make things much worse).

mumblemumble
Personally:

Python has a great type checker. Several, actually. They're not compile time, because Python doesn't exactly have a compile time, but still. 11/10, use everywhere, spend less time on unit tests, be happy.

Second, to me, these sorts of concerns about refactoring are. . . maybe not a smell, but worrisome, all the same. They suggest poorly-modularized designs with no internal component boundaries that might have limited the scope of impact of such a change. That said, even if you do find yourself needing to do a far-reaching refactor like that, refactoring in a dynamic codebase isn't necessarily harder, it's just different. Not being able to lean on the compiler to help you boil the ocean, for example, is fine, because boiling the ocean wasn't necessarily the best approach in the first place. The strangler pattern will help here, not just with type errors, but also with other problems the compiler might not have been able to help with, and will also be friendlier to the people who do your code reviews.

dragonwriter
> Python has a great type checker. Several, actually. They're not compile time, because Python doesn't exactly have a compile time.

Python does exactly have a compiler time, since it actually compiles things.

The typecheckers are run before that (but I think that's usually true of “compile time” checkers, too, they just don't break between checking and compilation; the analysis I would imagine almost invariably comes before actually generating compiled code; especially for languages where, unlike Python, typing effects what code gets generated rather than only whether the typechecker passes or fails.)

BiteCode_dev
https://docs.python.org/3/library/ast.html

Experience helps with this.

It's a middle path as usual: naive approaches waste as much time as overenginering.

gjulianm
I don't think you can draw that conclusion from that code. I've been using Python for a few years and moved some projects to static type checking. It's not always worth the cost in every situation, of course. For such a simple script I wouldn't add type checking. But for larger projects where there are multiple classes and a lot of types, I've found that the effort is very much worth it: a lot of type errors are found faster (some of them hadn't been caught by unit testing), the code becomes more consistent and easier to read, and developing is easier if you have an IDE that understands the types and gives you appropriate suggestions.

It's also worth it in plumbing code, as it tends to be more difficult to test properly and having types will catch a whole class of errors.

The fact that the program at the bottom of the article can be simplified is not an issue of typing, in fact static typing could also be used in your approach without any issue.

josteink
> The knock-on effects of type annotations seem to be doubling the complexity of the code. This is why I'm far from convinced that static type checking in Python is worth the cost.

If adding types to your existing code cannot be done without creating new types, that’s a sign that your type-system lacks expressiveness.

For instance C# 1 lacked a good and general way to describe types for functions (that is, decoupled from a class).

In later C# revisions this have been greatly improved, making this pain go away, and accepting type-safe function-value parameters as easy as any other parameter.

I’m guessing Python is lagging quite a bit in that regard. That doesn’t mean that type-safety (when done right) isn’t massively useful and worth the effort 200 times over.

masklinn
I think you’re missing their point. They’re not necessarily saying types are not useful, they’re saying types encourages a form of thinking which tends towards class-based overcomplications.

Now this is also a very yagni view, and maybe TFAA uses the structure they build for inspection before the final consumption, but their point that the posted script could be half the size and work the same without loss of readability… is compelling.

nerdponx
The sentiment in Python has been trending toward using "classes" (either with dataclass or attrs [1]) as essentially namespaced logicless containers, and away from dicts (or TypedDicts [2]) and namedtuples/NamedTuples [3]) for that purpose.

This is very different from the useless "you don't need classes" classes.

Think of classes nowadays serving as being both traditional OO classes and Scala case classes: they can be types of stateful objects, or just dumb record types, which you now can easily extend with logic if you do happen to need it (and you often do, in nontrivial applications).

Now this is also a very yagni view, and maybe TFAA uses the structure they build for inspection before the final consumption, but their point that the posted script could be half the size and work the same without loss of readability… is compelling.

I find this "it would be half as long and work the same" argument totally bizarre. Do you look at a basic Rust program and say "it would be half as messy and look the same if we did away with all the move/borrow stuff"?

Of course not. Yes, YAGNI is fine and all, but at least recognize that there is an upside to this tradeoff. I am not going to need internal state on the object, but I sure as hell am going to want the type checking.

And yes, you could (perhaps should) use Typescript or Kotlin for new web server projects instead. But some people want Python with types, including me.

I wouldn't accuse Python of being "concise" in the first place, so I am happy to add some up-front verbosity in exchange for a much smoother developer experience later in my project, with significantly less mental overhead as my IDE can do a lot of work for me.

1: https://www.attrs.org/en/stable/

2: https://docs.python.org/3/library/typing.html#typing.TypedDi...

3: https://docs.python.org/3/library/collections.html#collectio... and https://docs.python.org/3/library/typing.html#typing.NamedTu...

dragonwriter
> The sentiment in Python has been trending toward using "classes" (either with dataclass or attrs [1]) as essentially namespaced logicless containers, and away from dicts (or TypedDicts [2]) and namedtuples/NamedTuples [3]) for that purpose.

But...TypedDicts and NamedTuples are classes (that is, the thing returned from those callable is a class and can be subclassed or have methods added just like any other class), so it's kind of odd to say you are using classes instead of...classes.

masklinn
> The sentiment in Python has been trending toward using "classes" (either with dataclass or attrs [1])

That is not even remotely what is being done here. And just because you can do it doesn't mean you should or must do it. Not every argument needs to be bundled in its own object.

> I find this "it would be half as long and work the same" argument totally bizarre. Do you look at a basic Rust program and say "it would be half as messy and look the same if we did away with all the move/borrow stuff"?

Again, missing the point. The point was not about the typechecking but the design perspective it engenders and encourages.

> Of course not. Yes, YAGNI is fine and all, but at least recognize that there is an upside to this tradeoff.

Why would I? There is none in the article's snippet.

> I am not going to need internal state on the object, but I sure as hell am going to want the type checking.

Type checking doesn't require classes. You can typecheck a funtion.

> I wouldn't accuse Python of being "concise" in the first place, so I am happy to add some up-front verbosity in exchange for a much smoother developer experience later in my project, with significantly less mental overhead as my IDE can do a lot of work for me.

You're still missing the point entirely, but I hope you're happy about all those strawmen you beat down.

nerdponx
Type checking doesn't require classes. You can typecheck a funtion.

I am not talking about type checking a function. I am talking about type checking data structures, replacing this:

    record = { "a": 1, "b": 2 }
with this:

    record = Record(a=1, b=2)
The latter can be statically type checked much more easily than the former.

This is analogous to Scala case classes and Haskell records. And it can be a significant productivity improvement in big codebases, because it reduces your reliance on documentation and working memory. Instead, it lets you depend on the type checker and IDE to provide auto-completion to prevent a whole class of errors.

In fact, it lets you write less runtime code and have shorter, simpler code paths in some cases, because you can mostly skip checks like "if x is an int" (at least in internal/private functions), and even skip the corresponding unit tests, and let the type checker deal with it instead.

Edit: as for the example in the article... really? This is just single dispatch. What would you write instead, one big if/else statement? A dict of functions to dispatch to? `AstNode` is an interface, and all of these subclasses implement the interface.

foxes
Then types in python are not expressive enough. Other languages most of the time you don't even necessarily need to specify the types - the compiler/interpreter can figure it out and still type check the program.
dragonwriter
Even in languages where this is possible, it's usually idiomatic to explicitly type the public interface including exposed fields, and often it's common to explicitly type them narrower than the inferred type to avoid implementation details leaking into an unintentionally broad backward compatibility contract restricting future evolution.
nerdponx
Maybe, maybe not. Data classes are meant to feel like Scala's case classes, or Haskell's record syntax.

The point is to stop passing around dicts/hashmaps/tables and tuples, and to start passing around containers with a known and fixed set of keys/names, and known types for the values/elements.

I don't know of any language where the compiler automatically infers field names and types, and I don't know if I would ever want such a thing.

josteink
> I don't know of any language where the compiler automatically infers field names and types, and I don't know if I would ever want such a thing.

Typescript can do this to unannotated code, and it’s surprisingly useful.

nerdponx
I've already started checking out Typescript as a general-purpose scripting language to replace Python, Ruby, Perl, and to some extent Bash/Zsh. Knowing this makes it even more attractive. The only annoyances I've had so far are: difficult-to-search docs, and the `tsc` command-line compiler is slow.
sportsracer
Typing in Python is, at times, at odds with the language.

Said another way, patterns which were once Pythonic are now discouraged by the type system (or, mypy I suppose, since the door is still open for other type checkers to emerge). Examples:

- Duck typing. I was taught that's Pythonic, yet mypy really only accepts nominal subtyping i.e. it infers type compatibility from the type hierarchy. There is typing.Protocol, but I've never encountered that in code. Ruby's new type annotation language RBS introduces interfaces, so maybe it'll stay true to its duck typing roots.

- Lists with mixed types. I might as well bookmark this page, because this behavior of mypy always surprises me: https://mypy.readthedocs.io/en/stable/common_issues.html#inv...

In both cases, mypy reminds me way more of Java's type system than of Python's, which is maybe why heavily typed Python code often ends up looking like Java.

dragonwriter
> Said another way, patterns which were once Pythonic are now discouraged by the type system (or, mypy I suppose, since the door is still open for other type checkers to emerge).

There are several typecheckers, and while they are at slightly different points, they evolving together and together with the core typing library.

> Duck typing. I was taught that's Pythonic, yet mypy really only accepts nominal subtyping i.e. it infers type compatibility from the type hierarchy. There is typing.Protocol

Which entirely undercuts “mypy only accepts...”

> but I've never encountered that in code.

That's a library author issue, not a mypy (or typing, the standard library) issue.

> In both cases, mypy reminds me way more of Java's type system than of Python's

Python’s type system has always supported both nominal and structural subtyping.

> heavily typed Python code often ends up looking like Java.

That's probably because Java (and languages with closely related type systems) are the typed languages most people writing typed Python have prior experience with, so it's where their typing instincts go.

jasfi
If you're going to use Python with types, then you may as well use Nim. Nim has a similar syntax, but with better performance. You can also interface with Python code via Nimpy.
nerdponx
I like Nim, but I don't think there is much of anything similar between these languages except the syntax.
craftinator
I love using Nim, it's an absolute pleasure after years of fighting with Python's "I hope I got this right..." style of type guessing. The biggest problem most people run into is that python has a huge library ecosystem.

I haven't actually tried interfacing them, it's next on my docket. Have you found any good learning resources for that?

jasfi
Nimpy: https://github.com/yglukhov/nimpy

A good test as an example: https://github.com/yglukhov/nimpy/blob/master/tests/tpyfromn...

I've tried it and it works fine. The only problem I ever had was trying to call a specific Python function at the same time from multiple Nim threads. I'm not sure where the fault lay exactly, I just made that part of the code single-threaded and moved on.

viraptor
> We can't use `@dataclass` and are forced to write an `__init__` method for each class. But this is good, right? "Explicit is better than implicit"?

I don't think that applies. @dataclass is an explicit wrapper with specific result and can be inspected and interacted with.

Implicit would be something like "when a class has more than one attribute and doesn't define init or repr, add those methods assuming they're wanted/needed".

> The `AstNode` abstract base class becomes completely unnecessary and can be deleted.

You're mixing type annotations with class usage. You can have either one without the other.

For example without AstNode you could still have an `Expr|MethodCall|Lambda` type hint on relevant variables. Or even alias that expression to AstNode without having the class and inheritance.

Finally, classes help describe/construct the structure present in "stmt". Sure, you could replace those with functions, but that means your data is either reduced to untyped / string-tagged lists/dicts, or you're running the exact function immediately inline. It doesn't matter for this toy example, but it would in any practical use of this code.

mumblemumble
@dataclass is one of those ones where it depends on how you look at it.

To me, it's a great application of "explicit is better than implicit." Because @dataclass explicitly indicates that this class has a certain set of behavior, and is meant to be used a certain way. If you manually implement exactly the same behavior, you've made the individual statements more explicit, but you've made the class's actual function implicit. A reader needs to figure it out by reading the code and working backward from there. An editor of the code needs to recognize the intended behavior and not break it, otherwise the understanding that other people built up by reading the code may break in subtle ways.

Yes! Scaling problems are extremely nice to have. May you be so blessed that all of your problems are scaling problems!

People spend so much time trying to preemptively solve scaling problems and proactively design around scale. I am being 100% serious when I say that this is an absolute total mistake. Scaling problems are just the absolute best to have, you shouldn't run away screaming from them before they happen. Twitter had the fail whale for years, you know what it didn't do? Tank the business. In scaling problems:

• You have money flowing in, and confidence that the thing is working.

• The load on the system has been dynamically adjusted down for you as your users have a suboptimal experience, giving you smaller messes to clean up.

• Often there is an expensive short-term solution to remove the egg on your face if so desired. You have one Really Important client who is pissed, you give them their own private app with twice the hardware until you can get this resolved, and now they love you for life because they see that you did backflips for them.

• You can profile the system. Complex systems, under instrumentation, literally tell you where to look for what is wrong. “Why is that taking 400ms?!”

• You can run side-by-side tests, clone the input from the stressed system to the new system and verify that the load has decreased while the underlying model is the same.

• Very often you will find logical bugs that you needed to fix anyway, they just become much more apparent when the system has high contention. Two things were implicitly ordered by time and thus did not break unless requests were concurrent, so they were doomed to break eventually, but now they break almost immediately.

• Your corporate overlords immediately understand the business value in getting the situation resolved “correctly” whereas convincing them of refactors usually takes time. “We got the system limping along for now but if we don’t fix the underlying issue soon then this will happen again.” “Well we are really happy with how explosively this product is going, I guess we can delay those other two apps while you fix up this one.”

• At the same time that license is not a license to dither, as usually happens with such speech. You feel committed to investigating and refactoring and fixing, with that time, because the uncertainty of the system is a sort of abstract menace exerting deadline pressure. It is legitimately exhilarating to figure out and fix the actual problems, and gives you a good feeling about your contribution.

This is why I would generally tell people to not start out a project with Kubernetes, for example. You are trying to up-front the cost of a scaling problem but that scaling problem is gonna be really nice to have. Do it later! Get some egg on your face.

There is an abstract reason too, which I should mention. Lots of systems die because they do not have a “revenue problem”—long term they will make enough money to cover their costs—but a “cashflow problem”—short-term they overcommit and run out of money. You fail to pay your employees on-time one month, and by the next month you find yourself potentially needing to spend time and money hiring new employees to replace the ones you just terrified. So you fail to deliver a functionality on-time and a new client chooses a competitor. Stuff like that. Can happen just because if you have N payments coming in the uncertainty of those payments can grow like √N while you keep only a constant buffer of cash-on-hand, but can happen in more elaborate ways. Almost always you can ease the pressure by deferring payments until the latest possible.

When I was planning my wedding, my wife wanted to pay everything as soon as possible to save on our mental load; I resisted. And that was clutch because there were times during this when we were saying “ok we got $100 left to cover food and gas this week before I get paid on Friday, what can we do with that?” where if we had up-fronted those costs, presumably we would have unnecessarily eaten ramen or less for the three weeks before. Like it is easy to say “here is what we can save per month, so this is a reasonable budget for the wedding that won’t kill us”—solving the revenue problem. But cashflow problems still exist even when you know that you will be able to cover the cost eventually. Solving a scaling problem is up-fronting a cost that can easily be delayed and if you don’t delay it then there is a very real chance that your project can be canned way before you run into that scaling problem.

Especially let me link @jackdied’s talk “Stop Writing Classes” where this is a theme,

https://youtu.be/o9pEzgHorH0

Something something like “They subclassed a dict. Because they might need to add some functionality to it later. You know what? You can just do that later!” Same with scaling problems. This won’t scale, but we’ll worry about that later. And then when we worry about that, we will know by measurement whether our shiny new cloud machines need to be RAM-optimized or CPU-optimized or whether they need to be cheap-as-possible-but-ten-times-as-many-of-them. Whereas if you up-front this cost you literally are making all of your scaling decisions based on zero data and hunches.

hrishios
Agreed about scaling problems, but the examples you provided illustrate that, in my experience, there are still things you should be doing from the get-go.

Building things that don't scale is often taken to the extreme where it's nothing more than a few HTML pages strung together by self-rolled crypto and javascript. This might be good for idea validation (first couple of days), but it shouldn't persist past this point.

Understanding when the inevitable problems pop up and being able to solve them in reasonable enough time not to lose your users is - to me - not a nice to have problem. It's a showstopper that will turn egg on your face problems into fatalities.

Few things I would recommend are:

* Instrumentation - You don't need to go full ELK before you find product-market fit. Hell you can log everything, and with two users you can always read all the logs. But something is key. * Up and down scripts - How long will it take you to set up a new copy of the product? Do you have to trawl bash logs and have a team meeting to find out all the configs that need to be set up? * Load-testing - Again, nothing fancy. Just put curl on a bash loop and see where the system starts to hiccup. Once you have real users with complex data on real hardware you'll wish you profiled things, even slightly. * Instrumentation - I say it again because the sheer number of systems I've found on the verge of silent failure and user frustration because the errors weren't being propagated, be it from client side to server or from the logs to an alert, is massive. With modern SPAs it's easy to think everything works when all your users see is a blank page. * Please don't roll your own crypto. Unless you're working in a language that is 2 days old you really shouldn't need to or be doing this.

In short, if you're using HPAs and Kubernetes you've gone too far. But don't choose your instance size at random, be able to set up a new instance in an hour, and have at least 60% confidence that if there is an error you will know about it.

It reminds me of a pycon talk [0] which, while i dont agree with the whole thing, has the message "we ship features, not code". That also reminds me of "when a measure becomes a target, it ceases to be a good measure".

LOC is a decent measure, but features are our targets

[0] https://m.youtube.com/watch?v=o9pEzgHorH0&t=1235s

k3liutZu
> LOC is a decent measure, but features are our targets

Only if your measure of success is tied to fewer LOC.

el_oni
I'm not sure I got across what I mean.

Ones target should be shipping features. If you use 10k LOC to get a feature out, or 500 lines of more concise, optimised code, what matters is the feature.

If you have LOC targets to meet you are incentivised to produce the former rather than the latter.

My point is that a high number of lines isn't as important as good features. Though the two can get conflated

greggman3
that just sounds like it will have different unintended consequences like making every line as terse, complicated and unreadable as possible just to get it down to less LOC.
Also known in python as "if your class has only two methods, one of which is init, it's a function" in the "stop writing classes" https://www.youtube.com/watch?v=o9pEzgHorH0

EDIT: typo, changed link

tarkin2
And if you have many methods, but only one 'public' method, use a closure.
ric2b
I often run into that situation because I'm trying to make the main method a lot simpler by breaking it up into smaller methods with good names. Why should I avoid it?
gindely
I think before you do that, you should be careful to ask why the methods are private. Frequently, code like that exists because the private methods are reusable code that is not related to the title of this class. So the solution is first to make the private methods into public methods on separate classes.

And then the resulting classes probably meet some other rule that says "reduce trivial classes to pure functions or closures".

Generally speaking, before a person writes a private function, they should remember that it means "if you misuse it you'll break my otherwise unprotected invariants." Whenever that's not true, you shouldn't write "private". In particular, it doesn't mean "I was too lazy to structure my code correctly so I tried to hide it from the public implementation".

tarkin2
If the code could be extracted into generic top-level pure functions, I generally don’t unless its used in more than once in the codebase. Otherwise I find a number of contextless generic functions/classes without any idea of their use exhausting to look at eventually.

I have some functions that live in closures that are fairly generic. I’m always contemplating extracting them into a higher level. But they’re currently only used in one place, and they currently reside close to that one place, and this seems sensible.

tarkin2
Classes with one public function can be converted into a closure. Classes with two or more public functions can be converted to a closure that return those functions through an object. Then classes and closures become closer.

Classes, of course, offer inheritance. Yet most prefer composition over inheritance. Classes, too, offer polymorphism through typing. But dynamic languages use duck typing. In most dynamic languages where duck typing and composition are used, I find little need for traditional object orientated programming.

I frankly prefer closures, composition and duck-typing over the classes, inheritance and polymorphism through typing. The only thing missing is type checking and Golang's interfaces offer a way through that without the overhead of traditional object orientated design.

Sniffnoy
Your link goes by means of Google; here's a direct link: https://www.youtube.com/watch?v=o9pEzgHorH0
TomMarius
Or it's a named stateful or immutable data container with validation? You don't always want to use primitive types. But I am not a Python dev.
makapuf
Then build a function from validated parameters (or partially apply some parameters).
None
None
j88439h84
Yes, this is the point of `dataclasses` and `attrs`.
moonchild
Python doesn't have private members, so that doesn't help you as much.
heavenlyblue
Python has private members through double underscore methods
ben-schaaf
They're only private in a sense that you're not supposed to access them, there's no enforcement by the language in any way beyond renaming them `_<class>__<attr>`. They're still accessible and they still show up in `dir`, `__dict__`, etc.
travisjungroth
That’s not their purpose and they’re not really private. Leading double underscore methods cause name mangling (the class name gets added to the method name) and they’re used for solving specific problems with inheritance. Don’t use them just to make something private. You’re screwing up your inheritance if you don’t realize what you’re doing.

Leading single underscore is private by convention. Still should generally be avoided, but it’s the proper use.

cessor
Members in Python are never private. Variables that are supposed to be used internally only are marked with and underscore, but that just conveys intent and isn't enforced by the interpreter/runtime. But you can emulate private data like this:

    >>> def a(u, v):
    ...     def b():
    ...         return u + v
    ...     return b
    ...
    >>> b = a(1,2)
    >>> b()
    3
Like this, there is no way to access u & v from b.
moonchild
Looks like classes can't be closures, though:

  >>> def f():
  ...     x = 5
  ...     class Blub:
  ...             def incx(self):
  ...                     x += 1
  ...             def getx(self):
  ...                     return x
  ...     return Blub()
  ... 
  >>> j = f()
  >>> j.incx()
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 5, in incx
  UnboundLocalError: local variable 'x' referenced before assignment
cessor
When using it like this, the interpreter opens a new namespace where the variables are bound. The x is declared in the outer scope, so it can't find it. However, you can ask python to keep searching vor the name `x` in the closest outer scope, that is what the `nonlocal` statement is for:

    def f():
        x = 5
        class Blub:
            def incx(self):
                nonlocal x
                x += 1

            def getx(self):
                return x

        return Blub()

    j = f()
    j.incx()
    print(j.getx())
This will print 6, as expected.
sideshowb
Or anything that needs validation before you try to run the main part. e.g. the init() method may give you a shopping list of data you're going to need to pass to run() but which couldn't have been deduced until init() validated its parameters.

Sure, you could handle this the functional way by passing a getData() function to run() so it can do so itself but I'm not sure that's any more readable.

fastball
At the same time, if your function has an expensive initialization step that only needs to be called once (and doesn't change between calls), then the pattern of using `__init__()` + `__call__()` can be very nice.
im3w1l
I would consider this pattern:

    def x():
        expensive precompute
        def y():
           cheap stuff
        return y
It's a matter of taste I guess.
fastball
For comparisons sake:

  class Thing:
    def __init__(self, arg):
      # expensive precompute
      self.result = ...
    
    def __call__(self, arg):
      # cheap stuff that uses self.result for something
      ...
And then usage is:

  f = Foo("some val")
  bar = f("another val")
So yeah, both definitely do the same job. And your pattern is probably faster. But I generally like using Python's dunder methods as I think they allow for more consistent/recognizable code patterns.
iib
Do python linters catch that? It is not a code error and sometimes not a design error either, but a warning would be useful, and linters may have the tools necessary for that.
kubanczyk
It's a warning in pylint by default: "Too few public methods (1/2)"
cessor
Here is how to disable it:

# pylint: disable=too-few-public-methods

m463
what if it has just values (like an enum)?
twic
I have a few little classes that are clients for network services. They have a constructor which sets up an HTTP client or socket or something, and maybe prepares some metadata, and then a method to make a call to the service.

I could write these clients as lambdas or nested functions which close over the stuff which init creates. But why? An object makes it much clearer that there is state.

makapuf
One of the great tools to use here is `functools.partial`

After that, it's a question of taste. Agreed, callable objects can be useful but if you don't modify your "self" during function calls, partial funciton aplication might be clearer.

raverbashing
If there is state then it makes sense for it to be a class

But the parent comment applies

brnt
Using init/a constructor implies state, does it not?
raverbashing

    def __init__(self):
        pass

Not necessarily. Or the constructor is just setting some values that are later used by the calculation but are not modified and used later
MaxBarraclough
I think that depends on what we understand by 'state'. Do we count immutable data? Consider faking a 'bind' with something like:

    var quadrupler = new ConstantMultiplier(4);
    var output = quadrupler.applyTo(42);
The 'quadrupler' instance is stateless in that it's immutable, but it presumably has a (constant) member to store the value 4.
yxhuvud
Yes. If nothing else, the calculation of that immutable data may be costly enough to motivate not calculating it multiple times. Hence you want to treat it as state. But that doesn't mean you want to expose it to the user of the class which may have a nice simple interface of cheap operations after the init is done.
mcv
After ES6, I jumped at the opportunity to write classes in javascript, but lately I've realised I barely write classes anymore, but I write a lot of functions that return functions. And this example is perfect for that.

For example, I recently wrote this:

  const labelizeElement = ((element) => element.type.replace(/\s+/g, ''));
  const labelizeRelation = ((relation) => relation.type.toUpperCase().replace(/\s+/g, '_'));

  const groupByLabels = (labelizer) => (set, element) => (
    set[labelizer(element)]
      ? { ...set, [labelizer(element)]: [element, ...set[labelizer(element)]] }
      : { ...set, [labelizer(element)]: [element] }
  );
  const groupByElement = groupByLabels(labelizeElement);
  const groupByRelation = groupByLabels(labelizeRelation);
I needed two functions that did almost the same thing, but not quite. I don't want to write a ton of nearly-duplicate code, so instead I extract the bits that are different, and write a function that I actually need.

So yes, I'm inclined to say that only mutable state needs a class. And the more you get used to working with constants and immutables, the less mutable state you're going to find.

I used to be a big fan of OO, but somehow experience seems to have landed me in the functional programming camp.

MaxBarraclough
Personally I'd avoid currying and go with a function with three params, like:

    const labelizeElement = ...
    const labelizeRelation = ...
    
    const groupByLabels = (labelizer, set, element) => ....
    
    const groupByElement = (set, element) => groupByLabels(labelizeElement, set, element);
    const groupByRelation = (set, element) => groupByLabels(labelizeRelation, set, element);
This makes it slightly simpler to create a binding function around groupByLabels which fixes, say, the middle argument. I also think that avoiding the chained use of the => operator makes the code more readable to the average JavaScript dev.

C++ has (strange looking) support for the bind pattern in its standard-library now [0], and Python3 has functools.partial but it only supports left-to-right binding [1] (not that there's any particular reason to avoid lambdas).

[0] https://en.cppreference.com/w/cpp/utility/functional/bind

[1] https://docs.python.org/3/library/functools.html

ergothus
> But why?

This is my key question, and I ask with minimal assumption that either one is better.

What are the criteria we are even using to judge? Clarity that there is state is one. Elsewhere there are arguments about ABI that I fail to have practical knowledge of. There was some discussion of the data structure describing your program that completely lost me. Explicitness is lost when discussing closures.

What makes one better than the other in ways that arent entirely subjective?

zaphirplane
I/O in constructor ? I thought that is an avoid
shoo
I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.

In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.

Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.

I prefer structuring code as functor objects as it lets you access the bound parameters as attributes (if you want to expose them) which can also sometimes be useful in code or in test

twic
> I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.

> Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.

It's not the same, though, because a socket etc is being constructed in the constructor. Here's an abridged (and possibly wrong!) version of a monitoring client:

    class Monitor:
        def __init__(self, monitoring_url, app_name):
            self.monitoring_url = monitoring_url
            self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
            self.details = ujson.dumps({'app_name': app_name, 'pid': os.getpid()})

        async def send(self):
            async with self.session.post(monitoring_url, data=self.details, raise_for_status=True) as resp:
                pass
If you treat that as currying, you will create a new ClientSession every time you call ping(). A ClientSession contains a connection pool, so that means you will create a new socket instead of reusing one.

> In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.

In Python and Java, you can easily refer to bound methods to produce callables from objects, so this seems like unnecessary work.

Mar 13, 2020 · michelpp on I Don't Use Classes
I also tend to avoid writing many classes (in Python). Jack Diederich has a great video called Stop Writing Classes:

https://www.youtube.com/watch?v=o9pEzgHorH0

It's about Python, but the observation can apply to several languages. Pithy quote:

"I hate code and I want as little of it as possible in our product."

Feb 02, 2020 · Psyladine on Old CSS, New CSS
>That assumes that all of this complexity was done for complexity’s sake.

Or they just don't know any better.

https://www.youtube.com/watch?v=o9pEzgHorH0

Stop writing classes is one of my favorites. Also many pycon talks are great, or any talks by Brandon Rhodes.

https://youtu.be/o9pEzgHorH0

Oct 20, 2017 · ben174 on Becoming Foolish
Relevant video: Stop Writing Classes

https://www.youtube.com/watch?v=o9pEzgHorH0

collyw
Thats the nice thing about a mixed paradigm language, you can use the features when appropriate.
Raymond Hettinger's talk about good code reviews -- https://www.youtube.com/watch?v=wf-BqAjZb8M

Carmack's talk about functional programming and Haskell -- https://www.youtube.com/watch?v=1PhArSujR_A

Jack Diederich's "Stop Writing Classes" -- https://www.youtube.com/watch?v=o9pEzgHorH0

All with a good sense of humor.

someone7x
Came here to add "Stop Writing Classes", a fantastic talk to show how to refactor away from dogmatic OOP.
mixmastamyk
Yes, RH's Beyond PEP8 is great, even if you don't do Python. Will put the others in my queue.

I'm reminded of Crockford's "Good Parts" of Javascript, I believe where he introduced me to the "Mother of all Demos."

johnhenry
Everything I've seen by Crockford is great!
Stop writing classes - Jack Diederich - https://www.youtube.com/watch?v=o9pEzgHorH0

Show them this.

I'm still grateful for the moment I grew dissatisfied with OOP, or at least OOP in the Java/C# way. I was almost getting to an intermediate level in python (by myself) and had been thinking about a video game architecture problem I just couldn't solve.

I was still "indoctrinated" in the gang-of-four patterns and inheritance. When I discovered the solution, Entity Component Architectures[1], I also discovered the "composition over inheritance" philosophy. My mind was blown! Coupled with some Python's characteristics (duck-typing, everything is public, first-class functions) I was having a blast. Then I saw the video above, and all fell into place. Class based OOP have specific uses, but It's a shame how heavily enforced the paradigm is.

    “Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.” – John Carmack
Now, for scripts or not-to-big programs in Python, I mainly use NamedTuples for passing data around and (mostly) pure functions.

P.S. Another funny thing: At my job I use J2EE aka "lots of hinheritance and classes and boilerplate". Sometimes I just have to go home and write some code in python or lua to "cleanse my palate". It helps me get motivated for my personal projects.

[1] https://en.wikipedia.org/wiki/Entity_component_system

rodrigoreis22
I saw this video and showed them.. lol. I partially agree with the game example shown in the video.. I found the final code quite hard to understand. But I think that's due to my lack of knowledge in all the native stuff Python can do. I like when he says that you don't have to plan too much ahead for the future.. I think this generate phantom requirements that doesn't contribute for the solution. Just change the code when you need it. Wow, this ECA is heaven! :)
Vaskivo
> I think this generate phantom requirements that doesn't contribute for the solution. Just change the code when you need it.

YAGNI -> You Aren't Gonna Need It [1]

If you want to know more about about Python I recommend Raymond Hettinger's videos [2]

About ECA (which I presume is Entity Component Architecture) I have to say two things: 1) I've seen this pattern/concept be called many different things: Entity-Component, Game-Object Game-Component, Entity-Systems, etc. And at least two major ways of implementing it. 2) Like most design patterns, they exist to help you design program using languages that use heavy class-based OOP and static typing. In Python everything is dynamic and it uses duck typing, so can "draw some inspiration" from the pattern instead of implementing it by the book.

[1] https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it [2] http://pyvideo.org/speaker/138/raymond-hettinger

Jul 03, 2014 · 1 points, 1 comments · submitted by grey-area
informatimago
Why do you bother us with that? Just use a closure in lisp! Fuck! This is a problem that has been solved for 50 years!
Don't think its this is just limited to java

(Stop writing classes - python) http://www.youtube.com/watch?v=o9pEzgHorH0

"The signature of 'this shouldn't be a class' is that it has two methods, one of which is __init__." [0]

Why not write a function? This addresses all of the objections. Then refactor by pulling out concerns into other functions.

Need to share logic around the functions? Use a decorator. Passing too many attributes? Create a class to contain them.

I think classes are best avoided until it's obvious that encapsulating state provides a benefit, which I don't see in this case.

[0] https://www.youtube.com/watch?v=o9pEzgHorH0&t=3m

programminggeek
I actually like class methods for one particular benefit - namespacing. Actually, in Ruby, I use modules for that, but same difference in Ruby right...?

Functionally though, there isn't much difference between Module.do_stuff() and module_do_stuff() and if you are writing functional code, there isn't much reason to care either way too much. Most Ruby I see though isn't terribly functional.

stormbrew
Comparisons to python are difficult here because in Ruby there's no such thing as an object that doesn't have a class. Even the classes have classes (which is, as I touched on elsewhere, what class methods actually are -- instance methods on the class).

You can store a proc in a variable or a constant, but you're getting into some very ugly patterns at that point and you still haven't really left classes behind.

In Ruby everything is encapsulated and there's really no point in fighting that.

lmm
> Comparisons to python are difficult here because in Ruby there's no such thing as an object that doesn't have a class. Even the classes have classes (which is, as I touched on elsewhere, what class methods actually are -- instance methods on the class

So exactly the same as Python then?

> You can store a proc in a variable or a constant, but you're getting into some very ugly patterns at that point and you still haven't really left classes behind.

How so? If you need to pass around something that quacks like a proc, do it as a proc.

stormbrew
> So exactly the same as Python then?

Recently? Sort of, yes. Though in python it's more like everything has a blessed hash, and the assumption that everything is an object is much more thoroughly baked in in Ruby. Also the way that classes interact with their instances is quite different.

For example, in python a member of the class that is not overridden in an instance also happens to be a member of an instance. This is how default attributes work:

    >>> class x(object):
    ...     pass
    ... 
    >>> x.blah = 1
    >>> x().blah
    1
This actually gives you something qualitatively like a static method (through the staticmethod decorator), it is callable from an instance as well as the class and behaves always as if it has no access to the instance's state.

This kind of blending of instance and class doesn't happen in Ruby. The class is a completely distinct object with its own independent state, and is instantiated just like any other object, some syntactic sugar aside. It is an instance of class Class and its methods are the methods of class Class, while instances created by calling its #alloc method have the methods that are defined on it.

Python now shares some concept of everything being an object, I'll grant, but their models remain quite different and you really do have to work significantly against the grain to effectively use ruby in a non-OO fashion. In ways you simply don't in Python.

Love the video mentioned in the slides:

https://www.youtube.com/watch?v=o9pEzgHorH0

Summary of the links shared here:

http://blip.tv/clojure/michael-fogus-the-macronomicon-597023...

http://blog.fogus.me/2011/11/15/the-macronomicon-slides/

http://boingboing.net/2011/12/28/linguistics-turing-complete...

http://businessofsoftware.org/2010/06/don-norman-at-business...

http://channel9.msdn.com/Events/GoingNative/GoingNative-2012...

http://channel9.msdn.com/Shows/Going+Deep/Expert-to-Expert-R...

http://en.wikipedia.org/wiki/Leonard_Susskind

http://en.wikipedia.org/wiki/Sketchpad

http://en.wikipedia.org/wiki/The_Mother_of_All_Demos

http://io9.com/watch-a-series-of-seven-brilliant-lectures-by...

http://libarynth.org/selfgol

http://mollyrocket.com/9438

https://github.com/PharkMillups/killer-talks

http://skillsmatter.com/podcast/java-jee/radical-simplicity/...

http://stufftohelpyouout.blogspot.com/2009/07/great-talk-on-...

https://www.destroyallsoftware.com/talks/wat

https://www.youtube.com/watch?v=0JXhJyTo5V8

https://www.youtube.com/watch?v=0SARbwvhupQ

https://www.youtube.com/watch?v=3kEfedtQVOY

https://www.youtube.com/watch?v=bx3KuE7UjGA

https://www.youtube.com/watch?v=EGeN2IC7N0Q

https://www.youtube.com/watch?v=o9pEzgHorH0

https://www.youtube.com/watch?v=oKg1hTOQXoY

https://www.youtube.com/watch?v=RlkCdM_f3p4

https://www.youtube.com/watch?v=TgmA48fILq8

https://www.youtube.com/watch?v=yL_-1d9OSdk

https://www.youtube.com/watch?v=ZTC_RxWN_xo

http://vimeo.com/10260548

http://vimeo.com/36579366

http://vimeo.com/5047563

http://vimeo.com/7088524

http://vimeo.com/9270320

http://vpri.org/html/writings.php

http://www.confreaks.com/videos/1071-cascadiaruby2012-therap...

http://www.confreaks.com/videos/759-rubymidwest2011-keynote-...

http://www.dailymotion.com/video/xf88b5_jean-pierre-serre-wr...

http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hic...

http://www.infoq.com/presentations/click-crash-course-modern...

http://www.infoq.com/presentations/miniKanren

http://www.infoq.com/presentations/Simple-Made-Easy

http://www.infoq.com/presentations/Thinking-Parallel-Program...

http://www.infoq.com/presentations/Value-Identity-State-Rich...

http://www.infoq.com/presentations/We-Really-Dont-Know-How-T...

http://www.mvcconf.com/videos

http://www.slideshare.net/fogus/the-macronomicon-10171952

http://www.slideshare.net/sriprasanna/introduction-to-cluste...

http://www.tele-task.de/archive/lecture/overview/5819/

http://www.tele-task.de/archive/video/flash/14029/

http://www.w3.org/DesignIssues/Principles.html

http://www.youtube.com/watch?v=4LG-RtcSYUQ

http://www.youtube.com/watch?v=4XpnKHJAok8

http://www.youtube.com/watch?v=5WXYw4J4QOU

http://www.youtube.com/watch?v=a1zDuOPkMSw

http://www.youtube.com/watch?v=aAb7hSCtvGw

http://www.youtube.com/watch?v=agw-wlHGi0E

http://www.youtube.com/watch?v=_ahvzDzKdB0

http://www.youtube.com/watch?v=at7viw2KXak

http://www.youtube.com/watch?v=bx3KuE7UjGA

http://www.youtube.com/watch?v=cidchWg74Y4

http://www.youtube.com/watch?v=EjaGktVQdNg

http://www.youtube.com/watch?v=et8xNAc2ic8

http://www.youtube.com/watch?v=hQVTIJBZook

http://www.youtube.com/watch?v=HxaD_trXwRE

http://www.youtube.com/watch?v=j3mhkYbznBk

http://www.youtube.com/watch?v=KTJs-0EInW8

http://www.youtube.com/watch?v=kXEgk1Hdze0

http://www.youtube.com/watch?v=M7kEpw1tn50

http://www.youtube.com/watch?v=mOZqRJzE8xg

http://www.youtube.com/watch?v=neI_Pj558CY

http://www.youtube.com/watch?v=nG66hIhUdEU

http://www.youtube.com/watch?v=NGFhc8R_uO4

http://www.youtube.com/watch?v=Nii1n8PYLrc

http://www.youtube.com/watch?v=NP9AIUT9nos

http://www.youtube.com/watch?v=OB-bdWKwXsU&amp;playnext=...

http://www.youtube.com/watch?v=oCZMoY3q2uM

http://www.youtube.com/watch?v=oKg1hTOQXoY

http://www.youtube.com/watch?v=Own-89vxYF8

http://www.youtube.com/watch?v=PUv66718DII

http://www.youtube.com/watch?v=qlzM3zcd-lk

http://www.youtube.com/watch?v=tx082gDwGcM

http://www.youtube.com/watch?v=v7nfN4bOOQI

http://www.youtube.com/watch?v=Vt8jyPqsmxE

http://www.youtube.com/watch?v=vUf75_MlOnw

http://www.youtube.com/watch?v=yJDv-zdhzMY

http://www.youtube.com/watch?v=yjPBkvYh-ss

http://www.youtube.com/watch?v=YX3iRjKj7C0

http://www.youtube.com/watch?v=ZAf9HK16F-A

http://www.youtube.com/watch?v=ZDR433b0HJY

http://youtu.be/lQAV3bPOYHo

http://yuiblog.com/crockford/

ricardobeat
And here are them with titles + thumbnails:

http://bl.ocks.org/ricardobeat/raw/5343140/

waqas-
how awesome are you? thanks
Expez
Thank you so much for this!
X4
This is cool :) Btw. the first link was somehow (re)moved. The blip.tv link is now: http://www.youtube.com/watch?v=0JXhJyTo5V8
Hickey gives great talks, but I also really liked Jack Diederich's talk "stop writing classes" at PyCon 2012

https://www.youtube.com/watch?v=o9pEzgHorH0

Jan 25, 2013 · krosaen on The IDE as a value
I like this trend towards values instead of objects, Rich's talk is really worth watching too http://www.infoq.com/presentations/Value-Values. Using this approach in UIs is stretching the idea even further into a realm where I previously thought OO had a sweet spot.

I still like to convince myself, though, as to why this approach makes sense over the traditional OO / encapsulation approach. My current line of reasoning is that while having well defined services and interfaces can help organize a software system, the actual arguments and return values of these end points are really better thought of as values - and the expected structure of these values can certainly be part of the API. You just don't force people to construct and destruct objects for everything you are passing around.

A couple of related resources on this topic:

"Stop writing classes" from PyCon http://www.youtube.com/watch?v=o9pEzgHorH0

Rob Pike's reflection on using data vs OO:

https://plus.google.com/101960720994009339267/posts/hoJdanih...

chipsy
I agree with the trend, and also with the sentiment around interfaces.

Mutable/encapsulated approaches(which don't end at OOP, but rather continue into language design) act to define protocols. Protocols have a necessarily stateful nature; in the best case, the state is a single value at a single time, but in many real-world situations, state change events are frequent and need careful attention. Source code itself tends to need hundreds of changes to reach application goals, regardless of the language.

Functional and immutable style, on the other hand, acts on processes that are inherently computative, and this is the "nitty gritty" of most business logic. Even if the system is designed in an imperative fashion, it can bolster itself through a combination of a few "key methods" that apply functional style, and a type system that imposes a substantial amount of immutability.

The tricky part with OOP as we know it is to recognize when you're making a protocol, and when you're making an immutable computation. Many of the OO type systems used in industry lack the expressiveness to distinguish the two, and rush the natural life cycle of the architecture by imposing classes too early.

seanmcdirmid
I don't really get it. From the blog post, Granger is describing a very imperative object system, but you seem to be claiming that it is somehow a value-oriented functional system. What am I missing?
krosaen
That's a good question - there are still objects, or groupings of data, and even tags that identify which behaviors or functions apply to them in various contexts. What strikes me as different is the entire hierarchy is a nested data structure that is easy to reason about and modify at runtime without the use of a fancy debugger. The use of 'encapsulation' that hid the underlying data in each grouping and also bind functions / methods directly to each object in this case would only make it harder to work with. Why? Because to view, construct, augment at runtime or serialize the hierarchy would require constructors, serializers, deserializers etc, instead of having something that is just a data structure, ready to be viewed, put on a queue, sent over the wire etc.

The idea of 'behaviors' also provides flexibility in what functions can act on any grouping of data - the key value pairs needn't be associated with a 'class' which dictates what the associated functions will be adds more flexibility. As the author hints, there are other ways of achieving this agility - dynamic mixins.

Finally, while having a well defined protocol (or API or endpoint or whatever you want to call it) is valuable and helps organize code, I think taking this idea to the extreme and saying that every single object or piece of data you pass around as arguments or return values from these end points needs to be expressed as an abstract protocol itself is where you really start to lose. An expected format of the data structures of the arguments and return values can and should be part of an API, but needing to wrap them in objects doesn't really help - and that's where this trend I speak of begins to seem like progress to me.

seanmcdirmid
This is quite the standard dynamic languages argument, I'm not seeing much new here but this wasn't meant to be new. However, my point is that this is heavily object-oriented and heavily imperative. It is just a different kind of object system that people might not be used to. If this ever catches on, we'll just need another Treaty of Orlando to re-harmonize the object community.

To be honest, a lot of the object system seems to be confused and muddled. I mean, there are a million different ways to get the kind of flexibility in what the author call "behaviors" (a term incredibly overloaded, BTW), protocols, traits, type classes, etc...dynamic mixins are nothing new here also (I've designed a couple of research languages with dynamic mixin inheritance, fun stuff).

As an academic, I want to see reasons why X was chosen and comparisions with previous systems that chose Y instead; but I know I won't get this from most of the people who do the interesting work in this field, so I have to figure it out for myself. Calling this an object system opens up a floodgate of related systems that I can compare it against.

snprbob86
> I still like to convince myself, though, as to why this approach makes sense over the traditional OO / encapsulation approach.

I've become a 100% believer that Values are preferable to Objects & Encapsulation, full stop. However, I don't think that means you abandon objects, state, encapsulation 100%. I think that just means that you minimize them significantly.

I just watched a talk by Stuart Halloway [1] on Datomic where he makes a little "play at home" fill in the blanks table of design decisions and implications. He makes the assertion that if you take "program with values" to be the only given filled in table cell, you can make some arbitrary decisions for one or two other spots and the rest of the spots will fall out trivially with interesting properties. I guess the point is that programming with Values just affords you so much freedom and flexibility.

[1] http://www.infoq.com/presentations/Impedance-Mismatch

Jan 22, 2013 · 1 points, 0 comments · submitted by ominous_prime
Dec 10, 2012 · krosaen on Better Python APIs
I suggest the author and readers watch the excellent pycon talk, "Stop Writing Classes":

http://www.youtube.com/watch?v=o9pEzgHorH0

That said, some nice factoids in the post about making your objects readable in the repl etc.

Oct 09, 2012 · inglesp on An Object is not a Hash
I think you're thinking of Jack Diederich's talk "Stop Writing Classes". You can (and should!) watch it online here: http://www.youtube.com/watch?v=o9pEzgHorH0
drostie
Thank you, it's much appreciated.
HN Theater is an independent project and is not operated by Y Combinator or any of the video hosting platforms linked to on this site.
~ yaj@
;laksdfhjdhksalkfj more things
yahnd.com ~ Privacy Policy ~
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.