Hacker News new | past | comments | ask | show | jobs | submit | pmg101's comments login

I quite agree. It's simply a slider isn't it, with "no time, no knowledge, pure guesswork" at one end and "infinite time, thorough analysis, perfect understanding" at the other end.

Clearly the left end is dangerous but the right end can also be, due to opportunity costs. Making a judgment on where the slider should be for each decision/change is a key skill of the craft.


I also find that my "context windows" can only hold so much. I can't understand everything all at once. So if I do very deep dives, I tend to forget (temporarily) other parts of the problem/stack/task/whatever. The solution for me is to create as much as possible as self-contained testable units. Not always possible, but something to strive for.

And I find that this skill of organising is the limit for how large/complex systems I can build before they become unmaintable. This limit has increased over time, thankfully.


I think this is what the author meant by dividing complex problems in sub problems that are easier to tackle.


Thanks for the tip, I went to investigate. I like everything about it except for the lack of third party messaging. I need to meet my friends where they are and that is on Signal or WhatsApp.

Maps is also non negotiable, having maps in your pocket is one of the true wins of a smartphone imo, giving you freedom to explore.


No problem. 3rd party messaging does seem like the biggest need being voiced by potential users right now. It does have a navigation app, by the way, but don’t know details yet.


My experience is that once people have static typing to lean on they focus much less on the things that in my view are more crucial to building clean, readable code: good, consistent naming and small chunks.

Just the visual clutter of adding type annotations can make the code flow less immediately clear and then due to broken windows syndrome people naturally care less and less about visual clarity.


So far off from what actually happens. The type annotations provide an easy scaffolding for understand what the code does in detail when reading making code flow and logic less ambiguous. Reading Python functions in isolation, you might not even know what data/structure you’re getting as input… if there’s something that muddles up immediate clarity it’s ambiguity about what data code is operating on.


>So far off from what actually happens

I disagree strongly, based on 20 years of using Python without annotations and ~5 years of seeing people ask questions about how to do advanced things with types. And based on reading Python code, and comparing that to how I feel when reading code in any manifest-typed language.

>Reading Python functions in isolation, you might not even know what data/structure you’re getting as input

I'm concerned with what capabilities the input offers, not the name given to one particular implementation of that set of capabilities. If I have to think about it in any more detail than "`ducks` is an iterable of Ducklike" (n.b.: a code definition for an ABC need not actually exist; it would be dead code that just complicates method resolution) I'm trying to do too much in that function. If I have to care about whether the iterable is a list or a string (given that length-1 strings satisfy the ABC), I'm either trying to do the wrong thing or using the wrong language.

> if there’s something that muddles up immediate clarity it’s ambiguity about what data code is operating on.

There is no ambiguity. There is just disregard for things that don't actually matter, and designing to make sure that they indeed don't matter.


>I'm concerned with what capabilities the input offers, not the name given to one particular implementation of that set of capabilities. If I have to think about it in any more detail than "`ducks` is an iterable of Ducklike" (n.b.: a code definition for an ABC need not actually exist; it would be dead code that just complicates method resolution) I'm trying to do too much in that function. If I have to care about whether the iterable is a list or a string (given that length-1 strings satisfy the ABC), I'm either trying to do the wrong thing or using the wrong language.

You can specify exactly that and no more, using the type system:

    def foo(ducks: Iterable[Ducklike]) -> None:
        ...
If you are typing it as list[Duck] you're doing it wrong.


I understand that. The point is that I gain no information from it, and would need more complex typing to gain information.

I keep seeing people trying to wrap their heads around various tricky covariance-vs-contravariance things (I personally can never remember which is which), or trying to make the types check for things that just seem blatantly unreasonable to me. And it takes up a lot of discussion space in my circles, because two or more people will try to figure it out together.


>The point is that I gain no information from it

No, you do gain information from it: that the function takes an Iterable[Ducklike].

Moreover, now you can tell this just from the signature, rather than needing to discover it yourself by reading the function body (and maybe the bodies of the functions it calls, and so on ...). Being able to reason about a function without reading its implementation is a straightforward win.


>No, you do gain information from it: that the function takes an Iterable[Ducklike].

I already had that information. I understand my own coding style.

>Being able to reason about a function without reading its implementation is a straightforward win.

My function bodies are generally only a few lines, but my reasoning here is based on the choice of identifier name.

Yes, it takes discipline, but it's the same kind of discipline as adding type annotations. And I find it much less obnoxious to input and read.


>I already had that information. I understand my own coding style.

Good for you, but you're not the only person working on the codebase, surely.

>My function bodies are generally only a few lines, but my reasoning here is based on the choice of identifier name.

Your short functions still call other functions which call other functions which call other functions. The type will not always be obvious from looking at the current function body; often all a function does with an argument is forward it along untouched to another function. You often still need to jump through many layers of the call graph to figure out how something actually gets used.

An identifier name can't be as expressive as a type without sacrificing concision, and can't be checked mechanically. Why not be precise, why not offload some mental work onto the computer?

>Yes, it takes discipline, but it's the same kind of discipline as adding type annotations.

No, see, this is an absolutely crucial point of disagreement:

Adding type annotations is not "discipline"!

Or at least, not the same kind of discipline as remembering the types myself and running the type checker in my head. The type checker is good because it relieves me of the necessity of discipline, at least wrt to types.

Discipline consumes scarce mental effort. It doesn't scale as project complexity grows, as organizations grow, and as time passes. I would rather spend my limited mental effort on higher level things; making sure types match is rote clerical work, entirely suitable to a machine.

The language of "discipline" paints any mistake as a personal/moral failure of an individual. It's the language of a blame-culture.


> Good for you, but you're not the only person working on the codebase, surely.

I actually am. But I've also read plenty of non-annotated Python code from strangers without issue. Including the standard library, random GitHub projects I gave a PR to fix some unidiomatic expression (defense in depth by avoiding `eval` for example), etc. When the code of others is type-annotated, I often find it just as distracting as all the "# noqa: whatever" noise not designed to be read by humans.

And long functions are vastly more mentally taxing.

> often all a function does with an argument is forward it along untouched to another function. You often still need to jump through many layers of the call graph to figure out how something actually gets used.

Yes, and I find from many years of personal experience that this doesn't cause a problem. I don't need to "figure out how something actually gets used" in order to understand the code. That's the point of organizing it this way. This is also one of the core lessons of SICP as I understood it. The dynamic typing of LISP is not an accident.

> An identifier name can't be as expressive as a type without sacrificing concision

On the contrary: it is not restricted to referring to abstractions that were explicitly defined elsewhere.

> Why not be precise, why not offload some mental work onto the computer?

When I have tried to do it, I have found that the mental work increased.

> No, see, this is an absolutely crucial point of disagreement

It is.


In my experience, arguing co versus contra is a sign you are working with dubious design decisions in the first place. Mostly this is where I punch a hole in the type system and use Any. That way I can find all the bad architectural decisions in the codebase using grep.


> using the wrong language

IMO this is the source of much of the demand for type hints in Python. People don't want to write idiomatic Python, they want to write Java - but they're stuck using Python because of library availability or an existing Python codebase.

So, they write Java-style code in Python. Most of the time this means heavy use of type hints and an overuse of class hierarchies (e.g. introducing abstract classes just to satisfy the type checker) - which in my experience leads to code that's twice as long as it should be. But recently I heard more extreme advice - someone recommended "write every function as a member of a class" and "put every class in its own file".


I’d say I use type hints to write Python that looks more like Ocaml. Class hierarchies shallow to nonexistent. Abundant use of sum types. Whenever possible using Sequence, Mapping, and Set rather than list, dict, or set. (As these interfaces don’t include mutation, even if the collection itself is mutable.) Honestly if you’re heavily invested in object oriented modeling in Python, you’re doing it wrong. What a headache.


This is totally not how I used typed Python. I eschew classes almost entirely, save for immutable dataclasses. I don't use inheritance at all. Most of the code is freestanding pure functions.


I can remember in the mid-00s especially, Python gurus were really fond of saying "Python is not Java". But `unittest` was "inspired by" JUnit and `logging` looks an awful lot like my mental image of Log4J of the time.

> But recently I heard more extreme advice - someone recommended "write every function as a member of a class" and "put every class in its own file".

Good heavens.


Not coincidentally, these are two of my least favorite parts of the standard library. Logging especially makes me grumpy, with its hidden global state and weird action at a distance. It’s far too easy to use logging wrong. And unittest just feels like every other unit testing framework from that era, which is to say, vastly overcomplicated for what it does.


Exactly my experience. I call Python a surprise-typed language. You might write a function assuming its input is a list, but then somebody passes it a string, you can iterate over it so the function returns something, but not what you would have expected, and things get deeply weird somewhere else in your codebase as a result. Surprise!

Type checking on the other hand makes duck typing awesome. All the flexibility, none of the surprises.


This is because of Python's special handling of iteration and subscripting for strings (so as to avoid having a separate character type), not because of the duck typing. In ordinary circumstances (e.g. unless you need to be careful about a base case for recursion - but that would cause a local fault and not "deep weirdness at a distance"), the result is completely intuitive (e.g. you ask it to add each element of a sequence to some other container, and it does exactly that), and I've written code that used these properties very intentionally.

If you passed a string expecting it to be treated as an atomic value rather than as a sequence (i.e. you made a mistake and want a type checker to catch it for you), there are many other things you can do to avoid creating that expectation in the first place.


Type annotations are just like documentation though. Just because the annotation says int the function can still return a list.


Annotations can and should be checked. If I change a parameter type, other code using the function will now show errors. That won't happen with just documentation.


> Annotations can and should be checked

Unfortunately Python’s type system is unsound. It’s possible to pass all the checks and yet still have a function annotated `int` that returns a `list`.


True and irrelevant. Type annotations catch whole swathes of errors before they cause trouble and they nudge me into writing clearer code. I know they’re not watertight. Sometimes the type checker just can’t deal with a type and I have to use Any or a cast. Still better than not using them.


Do you mean that you're allowed to only use types where you want to, which means maybe the type checker can't check in cases where you haven't hinted enough, or is there some problem with the type system itself?


The type system itself is unsound. For example, this code passes `mypy --strict`, but prints `<class 'list'>` even though `bar` is annotated to return an `int`:

    i : int | list[int] = 0

    def foo() -> None:
        global i
        i = []

    def bar() -> int:
        if isinstance(i, int):
            foo()
            return i
        return 0

    print(type(bar()))


So don't do this then? The type system does not have to be sound to be useful; Typescript proves this.


> So don't do this then?

Don't do what?

- Don't write unsound code? There's no way to know until you run the program and find out your `int` is actually a `list`.

- Don't assume type annotations are correct? Then what's the point of all the extra code to appease the type checker if it doesn't provide any guarantees?


Don't do this stupid party trick with `global`.

You may as well argue that unit tests are pointless because you could cheat by making the implementations return just the hardcoded values from the test cases.


This isn't a "party trick" with `global`, it's a fundamental hole in the type system:

    class C:
        def __init__(self) -> None:
            self.i : int | list[int] = 0

        def foo(self) -> None:
            self.i = []
        
        def bar(self) -> int:
            if isinstance(self.i, int):
                self.foo()
                return self.i
            return 0

    print(type(C().bar()))


This seems to work in TypeScript too:

    class C {
        i: number | number[];

        constructor() {
            this.i = 0;
        }

        foo(): void {
            this.i = [];
        }

        bar(): number {
            if (typeof this.i === 'number') {
                this.foo();
                return this.i;
            }
            return 0;
        }
    }

    console.log(typeof new C().bar());
It seems to be a problem with "naked" type unions in general. It's unfortunate.


Agreed, but I've never found it to be especially problematic. The type checker still catches the vast majority of things you'd expect a type checker to catch.

If you want to be able to change the type of something at runtime, static analysis isn't always going to be able to have your back 100% of the time. Turns out that's a tradeoff that many are willing to make.


Yes, 100%. I believe that any new Python codebase should embrace typing as much as possible[1], and any JavaScript library should be TypeScript instead. A type system with holes like this is better than no type system.

[1] Unfortunately, many important 3rd party libraries aren't typed. I try to wrap them in type-safe modules or localise their use, but if your codebase is deeply dependent on them, this isn't always feasible.


In some cases don't you need to actually execute the code to know what the type actually is. How does the type checker know then?


It doesn't. There are cases where the type-checker can't know the type (e.g. json.load has to return Any), but there are tools in the language to reduce how much that happens. If you commit to a fully strictly-typed codebase, it doesn't happen often.


You can actually annotate the return type of json.load better than that:

    JSON = float | bool | int | str | None | list[“JSON”] | dict[str, “JSON”]


Yeah, people from statically typed languages sometimes can't understand how dynamically typed languages can even work. How can I do anything if I don't know what type to pass?! Because we write functions like "factorial(number)" instead of "int fac(int n)".


I wish that's how python functions were written. What i usually see is `draw(**kwargs)`.


Man gives up day job to focus on hobby.

Now hobby is his day job.


Why?


No good robot goes unpunished


Would be cool if you could play one sided pong with the scrollbar and bounce a ball around the window.


It's kind of stupidity farming isn't it. Is there anything wrong with that? The stupid people get to do some engaging, which is after all what their brain evolved to enjoy, everyone's a winner? Maybe?


And decision fatigue is a real thing. Even if the ice cream flavour/engineering decision is maybe not perfectly optimal, there's some value in not having to make the decision myself


For me doing the dishes is a 10-15 minute chore in the sink with some hot water but most people don't seem to object that their dishwasher takes 3 hours to do the same job with superheated steam or whatever. It still saves them the 15 minutes.


Don't forget that the dishwasher also uses less water and the dishes get sterilized by the steam.These features may or may not be important for a particular user.


Unfortunately the people doing the fucking around will not be the people finding out.


Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: