Live data from Hacker News

The use of `class` for things that should be simple free functions

quuxplusone.github.io

371–380 of 406 posts

Re: The use of `class` for things that should be simple free functions

#371

Earlier quoted context omitted.

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.

Python doesn't have private members, so that doesn't help you as much.

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.

Re: The use of `class` for things that should be simple free functions

#372
post #38

This reminds me of the blog rant "Execution in the Kingdom of Nouns": https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...

Luckily Java 8 introduced streams and functional interfaces - the latter of which is admittedly very object-oriented way of passing around functions.

For those wondering what I mean:

To support backwards-compatibility, Java could not simply introduce functions as first-class objects. Instead, it works like follows:

Syntax for a function `foo` which itself takes in a function called `intConcat` that takes in two ints and spits out string:

    foo(BiFunction intConcat) {
        // ...
    }
Notice that I have to explicitly state "BiFunction". There is also "Function" for single-argument functions, and nothing for more arguments (there are also `Runnable`, `Consumer` and `Producer` for fewer arguments in-or-out). This is because BiFunction isn't actually a function - it's an INTERFACE! Any class that implements 'apply' and 'andThen' functions with the right signatures will satisfy it, and can be passed in. You can make your own class and Java will happily accept it into this method.

Java then just adds some nice syntactic sugar to make stuff look like functions. E.g., if you do want to define an anonymous lambda like

    (x,y) -> "" + x + y;
What happens under-the-hood is that Java defines an anonymous BiFunction class. You can assign it to a variable and do everything you would want to do with an object:

    BiFunction bif = (x, y) -> "" + x + y;
I can call bif.toString() and all those other default methods defined on objects in Java. It's really not a function, it's an object holding a function:

    BiFunction {

        String apply(Integer x, Integer y) {
            return "" + x + y;
        }

        // ...
    }
and if you were to go and implement your own BiFunction as above (filling in the blanks) - you could pass it around exactly the same places as your "anonymous lambda" and it would work exactly the same way because it IS the same thing.

Like I said, a very object-oriented approach to functionality.

Re: The use of `class` for things that should be simple free functions

#373
post #163

Earlier quoted context omitted.

> inheritance is bad That depends. Inheritance makes it easy to break encapsulation (which is bad -- agreed). It can be hard to model "Is-A"-Relationships properly, but I wouldn't call it inherently bad. A circle isn't an ellipsis; but a chair is furniture. The quality of code stems from your quality of thought. > this means tight coupling between them, rather often unwanted. That depends on your design. Coupling is…

Inheritance reminds me of https://www.pcgamer.com/heres-whats-happening-inside-fallout... Your OpenConnection idea might make sense in some abstract way, but one thing I know about connections is that they have a habit of closing. They will close without any notice to your programming runtime, because the operating system will do it. What happens to your OpenConnection object then? Well it becomes invalidated, and no…

> What happens to your OpenConnection object then?

It raises an exception. This interrupts the normal flow of things and asks you to deal with the problem asap. If necessary, the handling code then could try to reconnect or abort. If desired, it could return a closed connection to convey that state-change, so that calling code is made aware that it needs to reconnect first and can't just reused the now closed connection. You could revert it to a normal connection (a "Has never ever been opened to begin with"-Connection). Depends on whether your driver/adapter/underlying connection thing cares about a difference in initial connects or reconnecting. If the handling code can't deal with it, it can bubble the exception up one level.

Swapping a `Connection` for an `OpenConnection` isn't heretic by the way, such structures are described by the state pattern. Objects model state, Exceptions model events (state transitions) but the later isn't explicitly described in the original Gang of Four book that way. I just found that exceptions are very usefull for this, given that you react to them in only a limited scope.

Be aware that this idea is culturally dependent. In Java, Exceptions are often discouraged from being used in such a way (exceptions should never be used for control flow and only convey error cases), in Python it's normal to communicate state transition that way, e.g. for-loops watch for StopIteration exceptions.

Re: The use of `class` for things that should be simple free functions

#374

Earlier quoted context omitted.

I'm not that familiar with functional programming but I think the answer is no. In the situation I'm describing, most of the arguments to the private methods are completely different except for the implicit this/self argument that contains the progress of the algorithm. Currying is about turning a function of multiple arguments into a function of one argument which itself is a function (which in turn takes a function…

If you have a function `foo(a, b, c)` and you find yourself passing the same a and b all the time, you can curry it into a function that only takes c: `f = curry(foo, a, b)` such that you can just call `f(c)`. The OO alternative would be to create a class: class Foo: def __init__(self, a, b): self.a = a self.b = b def f(c): return foo(self.a, self.b, c) And then you create an instance `obj = Foo(a, b)` and when you n…

Ah I see, I believe you've mixed up currying with partial function application, that's what confused me (but, again, I'm not an expert on functional languages so it could be me that's mixed up). I believe that currying doesn't take any arguments except the function itself:

    # Signature of f is (a, b, c) -> r
    g = curry(f)
    # Signature of g is a -> (b -> (c -> r))
    #    i.e. g is a -> blah, 
    #    where blah itself is a function taking b, etc.
Admittedly you can get partial function application out of currying (but only with parameters at the start of the parameter list):

    g = curry(f)
    h = g(a)(b)
    # Signature of h is c -> r
But, although they're related, they're different procedures overall: sometimes you'll really want the curried function without immediately applying some arguments to it.

Anyway, replacing "currying" with "partial function application" in your earlier comment: I suppose you're right to some extent, but the "this" parameter usually isn't that hidden and in Python you even pass the "self" parameter explicitly. I suppose the real major thing is you've put a bunch of parameters into a single class/tuple/struct rather than spelling them out individually; if you had a weird language that didn't support structs etc. then some partial function application would certainly be a very useful alternative to writing the same argument list out many times. But once you've got the struct, making your utility functions be methods is just minor syntactic sugar on top of that.

Re: The use of `class` for things that should be simple free functions

#375

Earlier quoted context omitted.

I think your Wallet.pay(amount) example argues for a different conclusion than you arrive at. I have a Wallet with $500 in it. I call Wallet.pay(100). In your approach, it returns a new Wallet with $400 in it. But I also still have the old, unmutated Wallet with $500 in it, which could be referred to by mistake (or by malice). That's probably not the best argument for immutable objects...

That's actually an argument in favor of linear or affine types. In your example, you'd still get a new Wallet from that function, but the compiler would keep you from accidentally using the old one afterwards. That way, you can't make that mistake, but still get the benefits of immutability.

I will admit that I don't know what "linear types" or "affine types" are. Your answer makes me feel like I am trying to convey an idea for which you have the perfect formalism / meta vocabulary. It might be that objects, or at least the way that I know how to use them in my favorite languges, are just a (potentially limited) implementation of said formalism.

I feel that objects offer a flexibility benefit though (which is why they often elude formal approaches) for the cost of purity.

Re: The use of `class` for things that should be simple free functions

#376
post #359
post #326

Earlier quoted context omitted.

It's not so much about "I need that later", but to start thinking about a program differently to begin with. If you think about the things that "are" rather than what should happen in what order, you get the chance to rearrange everything and do some optimizations that an eager processing might prevent you to do. Most of the OOP power comes from uniformity, which many systems break unfortunatelly. When you work with…

It seems to me that this is mostly a design problem. Why would you need to check this data everywhere? Checking the validity of the data is only necessary once. In a web context, this is the responsibility of the controller. Only once the data is sanitized should the controller inject it into 'anything else'. Sanitizing data is not the responsibility of a model/service/repository/view/anything else, and trying to do…

> Checking the validity of the data is only necessary once.

Exactly, which is why a class is the perfect singular location to place it. The object itself is just a pointer, the method doesn't get copied around, so object appear to be the perfect method to localize code.

> unexpected exceptions

True, exceptions introduce a communicative issue, but so does returning in-band values.

For example, what should the result of

    open('file.txt').read()
be, when the current user does not have permissions to read file.txt?

Different approaches exist:

    status, content = read('file.txt')
    content = read('file.txt', &status)
    status = read(&content)
I wouldn't argue against any of them, although I have my preferences of course. I like the exception model here, but you are right, rare exceptions, communicated poorly can be surprising and painful.

> Comprehensibility is in no way related to using objects.

On itself this statement is false, ... (hear me out)

> It all depends on the quality of the naming

... But this makes me understand what you're trying to say, and I 100% agree with it. In fact, I conducted some experimental research on identifier naming: https://link.springer.com/article/10.1007%2Fs10664-018-9621-... (Sci-hub or I can provide a preprint).

You are right in that objects and their use don't automagically turn a codebase in a field of readily availabe knowledge. Many mechanisms applied in OOP languages really work AGAINST comprehension (for example, buried exceptions originating from deep within an object graph). But still, objects are tightly coupled to comprehension, even historically speaking. They were first used to make it possible to model physical simulations without requiring users to know much about computer architectures (Simula 67). Objects are meant to "model", that is, symbolically represent concepts, entities or physical things in such a way that they might show agentic behavior. This is fundamentally different from having stupid data, smart functions but actually a completley different means of "Erkenntnis" (translates to "insight", but is more accurately conceived as "Epistemology"). The relationship between objects and readability / comprehension is complex, but I wouldn't call them "in no way related" (OOP can break comprehension, but it was invented to improve it. Irony.)

Also I would, again, like to second your words: Identifier naming might be the most imporant aspect of readability and comprehensibility.

Re: The use of `class` for things that should be simple free functions

#378

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures." Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He careful…

How much great learning the student could have done if he had not been insulted and physically abused by an impatient "educator".

Re: The use of `class` for things that should be simple free functions

#379

Earlier quoted context omitted.

I felt as though I should know the moral of the story, yet I did not. https://stackoverflow.com/a/11421598 Moral of the story is that closures and objects are ideas that are expressible in terms of each other, and none is more fundamental than the other. That's all there is to the statement under consideration.

It's code and state. Nothing more. Nothing less.

How do you model changes in state over time? (i.e. effects and processes?)

Re: The use of `class` for things that should be simple free functions

#380

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures." Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He careful…

I felt as though I should know the moral of the story, yet I did not. https://stackoverflow.com/a/11421598 Moral of the story is that closures and objects are ideas that are expressible in terms of each other, and none is more fundamental than the other. That's all there is to the statement under consideration.

True. But given that the difference in implementation appears to be arbitrary, the real differene between oop and fp must be evaluated in the domain of empirical research regarding their ergonomics. If one matches your natural thoughtpatterns more closely (be that abstract or concrete modelling) or educative efforts (it might be that one is easier to understand), or ease of implementation, that would be an argument for or against it.

Although Java might be conceptually weak, empirically speaking it's a big winner, because developers appear to value ease of education, connectedness with others, availability of jobs and job security more than feeling stupid for not understanding lambda calculus. (that doesn't render formalist approaches invalid)

Post reply on HN