[HN Gopher] Tests aren't enough: Case study after adding type hi...
       ___________________________________________________________________
        
       Tests aren't enough: Case study after adding type hints to urllib3
        
       Author : quentinp
       Score  : 159 points
       Date   : 2021-10-18 16:16 UTC (6 hours ago)
        
 (HTM) web link (sethmlarson.dev)
 (TXT) w3m dump (sethmlarson.dev)
        
       | david422 wrote:
       | I love static typing/type hints if for only 1 thing - code
       | maintenance.
       | 
       | Even code I wrote six months ago.
       | 
       | Not having to dig through 6 functions deep to try to figure out
       | whether "person" is a string, or an object, and if it's an object
       | what attributes it has on it etc. is huge. And not to mention
       | that some clever people decide - hey, if you pass a string I'll
       | look up the person object - so you can pass an object or a string
       | - which makes all sorts of convoluted code paths when someone
       | else was looking at "person" and only saw one type so now their
       | function doesn't work on both types etc.
       | 
       | I hate having to waste time figuring out the type of every
       | variable and hold it in my head every single time I read a piece
       | of code.
        
         | handrous wrote:
         | > I hate having to waste time figuring out the type of every
         | variable and hold it in my head every single time I read a
         | piece of code.
         | 
         | If a codebase doesn't have static types, it damn well better be
         | set up to be _highly_ grep-able. Including dependencies and
         | frameworks.
         | 
         | This is why Rails pisses me off so much. No static types to
         | help you out, _and_ you can 't grep (can barely google, even!)
         | methods and properties that aren't defined _anywhere_ until
         | runtime. Is this from core? Is it from some 3rd party gem? Well
         | fuck me, this file doesn 't even tell me _which gems_ it 's
         | relying on, so it could be literally anything in the entire
         | goddamn dependency tree.
        
           | blacktriangle wrote:
           | That's some Rails stupidity there, not a dynamic language
           | problem. Autoloading symbols by name is straight up dumb.
           | 
           | As for greppable though...then you may as well be using a
           | static language. The point of a dynamic language is to be
           | dynamic, ie you can do those things at runtime.
        
             | nitrogen wrote:
             | _The point of a dynamic language is to be dynamic, ie you
             | can do those things at runtime._
             | 
             | With Rails you have the option of pry-rails, and you can
             | get a list of descendants of important parent classes like
             | ActiveRecord with this:
             | https://apidock.com/rails/Class/descendants
             | 
             | With the combination of vim, rspec, pry, fzf, and ripgrep,
             | it's possible to become quite comfortable refactoring pure
             | Ruby and Ruby+Rails code. But it does take some time to
             | learn how to navigate the Rails runtime code generation
             | magic. The more magic the code, the more you might have to
             | use a debugger to break on method definition, but Ruby's
             | dynamicism lets you do that.
             | 
             | On the topic of frameworks with a lot of magic, having used
             | both Rails and Spring Boot (with Java and Kotlin), I'll
             | take Rails any day. It was way easier to introspect Rails
             | codegen magic with Pry, than Spring's codegen magic with
             | IntelliJ. With Spring Boot, even with Kotlin, we had the
             | burden of semi-manual typing, but lost a lot of the
             | benefits because a lot of DB interaction and API payload
             | handling was still only runtime checked.
        
           | rightbyte wrote:
           | > ... grepable ...
           | 
           | This is so important.
           | 
           | It is also the reason why I like global variables. They are
           | accused of making a spaghetti mess but ... in my experience
           | the opposite is true.
           | 
           | Fancy patterns are way worse to reverse engineer than simple
           | flat long functions accessing globals. Easy to debug too!
        
         | BeFlatXIII wrote:
         | > some clever people decide - hey, if you pass a string I'll
         | look up the person object - so you can pass an object or a
         | string - which makes all sorts of convoluted code paths
         | 
         | Do you have hints on how to avoid being one of those 10x clever
         | programmers while programming a prototype? I find that I am
         | most likely to write functions like that when there's some
         | variables that I don't want to pass 5 layers down the call
         | stack and then, in your example, would accept either a string
         | (in which case those variables use their default values) or the
         | Person object, where the variables are pulled from the Person's
         | attributes.
        
         | sseagull wrote:
         | This is absolutely how I feel. I've mentioned previously taking
         | over a project, and just not knowing the type of anything took
         | me months to overcome.
         | 
         | Also, type hints really help your IDE, even catching errors
         | before you even run tests.
         | 
         | There's also a visual cue that you are doing something wrong:
         | If a function returns 4 levels of Union[Tuple[List[int]],
         | Optional[str]........ Then you are doing something too complex
         | and the function should be broken up.
        
         | didibus wrote:
         | I've been working in Clojure for the last few years, and what I
         | learned is that the trick is to reverse the data dependencies,
         | so that instead of your function asking: "What is a "person"
         | and what attributes does it have if an object?". You have your
         | function declaring: "I take a person as a map of keys :name and
         | :age". And it is the caller who needs to ask itself: "What am I
         | supposed to provide to this function?"
         | 
         | This is a very different mindset, but once you adopt this
         | style, the lack of static types isn't as big an issue.
         | 
         | The reason you can do this in a dynamic language is that you
         | can very easily adapt one structure to another, so its okay if
         | not all your functions work directly on the same shared
         | structures.
         | 
         | It also has the advantage that this style really favors making
         | modular independent granular components that can be reused
         | easily, because they aren't coupled to an application's shared
         | domain structures, but to their own set of structures, creating
         | a natural sub-domain.
         | 
         | There are other aspects to make this style work well, like
         | keeping call-stacks shallow, and having a well defined domain
         | model at the edge of your app with good querying capabilities
         | for it.
         | 
         | Concretely it means say you need to add some feature X to the
         | code, you might think, ok this existing function is one place
         | where I could add the behavior, but for my new feature I need
         | to have :age of "person", but I don't know if the "person"
         | argument of this existing function would contain :age or not.
         | Dammit, I wish I had static types to tell me.
         | 
         | Well, in this scenario, instead, what you do is that you don't
         | add the behavior to that function. Instead, in my style you
         | would have:                   A -> B         A -> C
         | 
         | instead of:                   A -> B -> C
         | 
         | That means if after B is the right place for your logic, you
         | don't do:                   A -> B -> B' -> C
         | 
         | And hope that the "person" passed to B had the :age key which
         | is needed by B'.
         | 
         | Instead you would do:                   A -> B         A -> B'
         | A -> C
         | 
         | And when you implement B', you don't even care about "person",
         | you can just say you need person-age, or that you need a Person
         | object with key :age (which you don't care if it is the Person
         | object shared in other places or not).
         | 
         | Finally, you modify A, where A was the function that creates
         | the Person object in the first place, it has direct access to
         | your actual database/payload and so finding whatever data you
         | need is trivial in it.
        
         | layer8 wrote:
         | > I hate having to waste time figuring out the type of every
         | variable and hold it in my head every single time I read a
         | piece of code.
         | 
         | For the same reason, I'm not a fan of type-inferring variable
         | declarations.
        
           | lamontcg wrote:
           | I'm okay with "var = new FooBarBazThingyWithALongName()"
           | because I don't need to see the type name twice there.
           | 
           | In an IDE you can get the type annotation from the IDE over
           | every inferred var type, but I don't like requiring an IDE to
           | see that information and like it showing up in 'less' as
           | well.
        
             | layer8 wrote:
             | I agree it's redundant if the type name occurs twice in the
             | same statement. However, further evolution of the code
             | often causes the instantiation to be moved elsewhere, and I
             | wouldn't have confidence that the one doing that change
             | then also changes `var` back to the type name. Instead, it
             | would be nice to have syntax avoiding the duplication in
             | the fashion of `FooBarBazThingyWithALongName thingy =
             | new(...constructor parameters...);`.
        
               | Serow225 wrote:
               | C# 9 now has "target-typed new expressions" https://www.t
               | homasclaudiushuber.com/2020/09/08/c-9-0-target-...
        
           | dmart wrote:
           | Yeah, when writing type inference is obviously nice, but it
           | can be annoying to try to go back and read.
           | 
           | I think the best experience is having a language server
           | annotate the inferred types (like how rust-analyzer does it.)
           | But even then, it can become hard to read code on GitHub or
           | somewhere where tools are not available. Granted that's
           | becoming less and less of a problem, and even GitHub allows
           | using some VS Code extensions now.
        
             | layer8 wrote:
             | With good IDE support, writing types isn't that much of a
             | burden. Either write a function call first and use "assign
             | return value to new variable", or use autocompletion where
             | you only type the initials of a multi-word type name. Plus
             | IDE refactoring actions when a parameter or return type
             | needs to be changed.
        
         | karmakaze wrote:
         | I learned the same thing on a project that was using Java 1
         | non-generics. Not exactly untyped to typed but an analogous
         | experience. Everyone I asked said that it was too big to do. I
         | started anyway by enabling the warnings for nongeneric use. I
         | turned down the reporting limit to 1000 (I think) so as not to
         | be discouraged. After months and months of incremental work
         | alongside my main work, I got under the 1000 warnings. It got a
         | bit trickier after that. In the end, there was exactly 1 bug,
         | where an object.toString was being added to a dropdown box and
         | we'd see it from time to time as Class@hexhash. What I learned
         | then is that it isn't strictly about the bugs, it's the
         | confident way you can navigate the codebase and understand and
         | add in consistent ways. Now I add types to all my Ruby and it's
         | seems normal again.
        
         | blacktriangle wrote:
         | I would argue that any function that branches on argument type
         | is straight up doing dynamic typing wrong. Well branching may
         | not be the right word. Something resembling pattern maching is
         | fine, but like you say having a function that takes a string
         | for lookup OR the object is just a disaster, particularly when
         | you start stacking function calls. Dynamic types should closer
         | resemble things that all share an interface, not totally
         | different representations of that data based on the shape of
         | your code.
         | 
         | Javascript is by far the worst offender here with its ignoring
         | extra arguments. Javascript functions that totally change
         | effective type signatures based on number of args are the
         | devil's work.
         | 
         | I'd argue that if the types that a function accepts are not
         | easily defineable than you're doing dynamic typing wrong.
        
         | jfabre wrote:
         | I never understood this argument. In what kind of shop are you
         | working that passing a string named person to a method
         | expecting an object is tolerated. Or even passing different
         | types that don't share a common interface.
         | 
         | This would never fly in a code review in any of the companies
         | I've worked for.
        
           | kennywinker wrote:
           | I've seen essentially this code in so many organically grown
           | codebases (when they grew up without types). It's usually
           | close the the UI, because someone had to quickly add an
           | alternate path to support some new user interaction
           | function find_user(person) {             if user is string {
           | query_by_name(person)             } else {
           | query_by_name(person.name)             }         }
           | 
           | and yeah, we all know it's kinda messy, but also that logic
           | has to live somewhere and we need this feature asap so it
           | passes code review. I wrote a test for it, ship it.
        
             | freemint wrote:
             | Sounds like a brilliant case for multiple-dispatch.
        
             | sseagull wrote:
             | I came very close to writing almost this exact code just
             | the other day (except it was username or user id for me),
             | but came to my senses. It's just so tempting in a dynamic
             | language...
             | 
             | In a static language, you either can't do it, have to
             | really go out of your way to do it, or at least do function
             | overloading (which is a bit cleaner)
        
           | dboreham wrote:
           | You'd think. But I've seen many many many examples of this
           | pattern in production JS code.
        
           | tialaramex wrote:
           | In C++ you're only ever one missing "explicit" from
           | introducing such problems.
           | 
           | Suppose I call fire(bob). Programmers from other languages
           | might reason that since fire is a function which takes a
           | Person, bob must be a Person. Not in C++. In C++ the compiler
           | is allowed to go, oh, bob is a _string_ and I can see that
           | there 's a constructor for Person which takes a _string_ as
           | its only argument, therefore, I can just make a Person from
           | this string bob and use that Person then throw it away.
           | 
           | To "fix" the inevitable cascade of misery caused by this
           | "feature" C++ then introduces more syntax, an "explicit"
           | keyword which means "Only use this when I actually ask you
           | to" rather than as a sane person might, requiring an
           | _implicit_ keyword to flag any places you actually want this
           | behaviour to just silently happen.
           | 
           | This way, hapless, lazy or short-sighted programmers cause
           | the maximum amount of harm, very on-brand for C++. See also
           | const.
        
           | elzbardico wrote:
           | This was probably just a silly example for a quick
           | explanation.                 But all it takes is a method
           | that expects an integer Id to receive a string representation
           | of said id because of some obscure path in code that
           | notwithstanding your 100% line coverage the team is so proud
           | of, was never exercised on tests because nobody can have 100%
           | branch coverage
        
           | dec0dedab0de wrote:
           | I personally love it, and wish every library worked this way.
           | My argument is why go out of my way to make it not work, when
           | it would be easy to make it work. This is because I think of
           | modules/packages as user facing programs that are easy to tie
           | together, instead of simple building blocks.
           | 
           | What I really wish existed was a built in way to cast and
           | validate, or normalize and validate. I never care if
           | something is a string. I care that if I wrap it in str(), or
           | use it in a fstring, the result matches a regex. Or if I run
           | a handful of functions one of them returns what I need.
           | 
           | The only benefit I can see of type hints on their own is it
           | makes it easy to change a callable's signature, but I think
           | that's best avoided to begin with.
        
           | cyral wrote:
           | If only there was a way to enforce these parameter types
           | automatically
        
         | tus89 wrote:
         | Yay -- maintaining type definitions for arbitrarily complex
         | nested dicts/arrays that are often built up when processing
         | data and in all manner of different ways.
         | 
         | You know why "Any" appears in Typescript so much?
         | 
         | I doubt anyone here is an actual programmer, if you were you
         | would understand that having to define type definitions for
         | "objects" that are often just parsed from JSON API responses or
         | constructed through the processing of data is the biggest pain
         | in all languages, and why typeless languages are convenient and
         | popular for this kind of work.
         | 
         | Oh and then the API response changes slighty, breaking your
         | type definition, whereas typeless Python keep chugging along
         | because it is very tolerant of small changes to underlying
         | data, especially if written in a defensive manner.
         | 
         | If you want types, why are you using Python at all? Go write
         | bloated Java/.NET that takes a team of 10 six months to put
         | together a simple app that would be written in week in Python.
        
         | wvenable wrote:
         | The main argument for dynamic typing is speed in prototyping
         | but I find that's opposite for me. I'm much more comfortable
         | rapid prototyping and ripping stuff apart when I have a
         | strongly static typed environment telling me what I just broke.
         | 
         | Doing radical refactoring often involves just making those
         | changes and then fixing all the IDE or compiler errors until it
         | runs again.
        
           | Enginerrrd wrote:
           | Dynamic typing was great before I knew anything about
           | programming. I'm talking like, at a middle school level.
           | Fewer "Silly" errors.
           | 
           | After university, the opposite became true. No difficult to
           | diagnose undefined behavior because of ambiguity in typing.
        
           | Barrin92 wrote:
           | the main argument for dynamic typing in particular in the
           | context of object oriented programming is decoupling. It is
           | always the receiving object's responsibility to handle
           | whatever they get.
           | 
           | if you write dynamic OO languages with a static mentality in
           | mind, i.e. you try to enforce some sort of global type
           | expectation before the program runs, then obviously static
           | languages are better, because you're trying to write static
           | code.
           | 
           | Benefiting from dynamic languages means ditching that mindset
           | altogether.
        
           | tetha wrote:
           | Hm, growing somewhat experienced, I find myself adapating an
           | old quote more and more: Sufficiently advanced static typing
           | is indistinguishable from dynamic typing.
           | 
           | Now, I know, it's not true. It's entirely possible to build
           | weird things in python that are provably impossible to
           | typecheck statically. But modern language servers and their
           | type inference capabilities in rust, terraform, or even
           | straight up python are very impressive.
        
             | plmpsu wrote:
             | I don't understand your argument. Could you please explain
             | it?
        
               | tetha wrote:
               | Type systems simply have matured a lot.
               | 
               | It's not too long ago that you either had very clumsy
               | type systems - C, Java. These type systems were more of a
               | chore than anything else. Especially the generic
               | transition in java was just tedious, you had to type cast
               | a lot of stuff, and the compiler would still yell at you,
               | and things would still crash.
               | 
               | Or you had very powerful and advanced type systems -
               | Haskell and C++ with templates for example. However,
               | these type systems were just impenetrable. C++ template
               | errors before clang error messages are something. They
               | are certainly an error message. But fixing those without
               | a close delta what happened? Pfsh. Nah.
               | 
               | In those days, dynamic typing was great. You could shed
               | the chore of stupid types, and avoid the really arcane
               | work of making really strong types work.
               | 
               | However, type systems have matured. Today, you can slap a
               | few type annotations on a python function and a modern
               | type inference engine can give you type prediction,
               | accurate tab-completion and errro detection. In something
               | like rust, you define a couple of types in important
               | locations and everything else is inferred.
               | 
               | This in turn gives you the benefit of both: You care
               | about types in a few key locations, but everything else
               | is as simple as a dynamically typed language. And that's
               | when statically typed languages can end up looking almost
               | - or entirely - like a dynamically typed language. Except
               | with less error potential.
        
               | musicale wrote:
               | > a modern type inference engine can give you type
               | prediction, accurate tab-completion and _errro detection_
               | (emphasis mine)
               | 
               | "errros" are my nemesis in languages which automatically
               | create a new symbol with every typo!
        
             | slaymaker1907 wrote:
             | Please write a type for the following function:
             | def compose(start, *args):         def helper(x):
             | for func in reversed(args):             x = func(x)
             | return start(x)         return helper
             | 
             | There is no mainstream typed language which can write a
             | fully general type for the vararg compose function.
             | TypeScript is probably the one that comes closest, but last
             | I checked it still was unable to write a sufficiently
             | powerful array type. You can write a type for a version of
             | compose with a fixed number of arguments, but not for one
             | working over an arbitrary number of arguments.
        
               | munificent wrote:
               | _> There is no mainstream typed language which can write
               | a fully general type for the vararg compose function._
               | 
               | True. A more interesting question might be what level of
               | static safety and performance benefits you'd be willing
               | to sacrifice to be able to write functions like this.
               | 
               | Personally, I don't find the kind of code I can't fit
               | into static types particularly appealing, but I find the
               | code navigation, error checking, and optimizations of
               | static types to be priceless.
        
               | albrewer wrote:
               | I took a stab at it, there's not enough information to
               | figure out anything more specific:                 from
               | typing import Callable, Any            def compose(start:
               | Callable[[Any], Any], *args: Callable[[Any], Any] ->
               | Callable[[Any], Any]:         def helper(x: Any) -> Any:
               | for func in reversed(args):             x = func(x)
               | return start(x)         return helper
        
               | slaymaker1907 wrote:
               | Sure, that's probably as close as you can get, but
               | ideally it would be possible to write a type which
               | guarantees the input functions are compatible as well as
               | knowing what the type is of the returned function.
        
               | monocasa wrote:
               | I think you can in rust as long as args is a slice. Rust
               | doesn't have varargs except for c interop. A slice or Vec
               | of function pointers is the idiomatic way to do the same
               | thing.
               | 
               | Something like:                 fn compose<X, T>(start:
               | Box<Fn(T) -> X>, args: Vec<Box<Fn(T) -> T>>) -> Fn(T) ->
               | X {         move |x: X| {           let mut x = x;
               | for func in args.iter(). reversed() {             x =
               | func(x);           }           start(x)         }       }
        
               | slaymaker1907 wrote:
               | This only works if all of the functions return the same
               | type. However, you can write a compose macro which
               | operates as expected.
        
               | tetha wrote:
               | As I said, there are infinite possible programs that
               | cannot be type checked statically. This is derived from
               | the halting theorem trivially - lambda(p) = if p.halts()
               | then A() else B().
               | 
               | However, the intersection of programs I encounter in
               | practice with the number of programs that can be
               | statically checked is rather large.
        
               | phoneperson wrote:
               | You are probably thinking of Godel's incompleteness
               | theorems. I do not think that you can trivially derive it
               | from the halting problem.
        
               | loup-vaillant wrote:
               | Vararg functions also have limited use. Especially
               | considering that most of the time, your _args will all
               | have the same type, and therefore could just be passed in
               | an array or similar. The one mainstream exception I know
               | of is print functions, and we_ have* ways to statically
               | check those.
               | 
               | Your toy example, even generalised, has no practical use.
               | If I can write this:                 compose(f, f1, f2,
               | f3)
               | 
               | Then I can write that instead (Haskell):
               | f . f3 . f2 . f1
               | 
               | Or this (F#):                 f1 |- f2 |- f3 |- f
               | 
               | And now we've reduced the problem to a simple function
               | composition, which is very easy to define (Ocaml):
               | let (|-) f g = fun x -> g (f x)       (|-): ('a -> 'b) ->
               | ('b -> 'c) -> ('a -> 'c)
               | 
               | This generalises to any fold where the programmer would
               | provide the list statically (as they always would for a
               | vararg function): instead of trying to type the whole
               | thing, just define & type the underlying binary
               | operation.
        
             | Jensson wrote:
             | > Sufficiently advanced static typing is indistinguishable
             | from dynamic typing.
             | 
             | Static typing type checks are compile time, dynamic typing
             | doesn't. I don't see how these two could be
             | indistinguishable, in one you can't run the program with
             | type errors, in the other you can.
        
               | wvenable wrote:
               | I believe the author was talking about the act of writing
               | the software. Modern type inference means that you can
               | mostly code without needing to write down the types in
               | many cases. This line of code is the same in JavaScript
               | or C#:                   var instance = new SomeClass();
               | 
               | In function definitions, where you are definitely going
               | to need to provide parameter types it's extremely common
               | to document those types in a docblock in a dynamically
               | typed language. At least I always did. So making the
               | types part of the definition is not a significant
               | difference while writing the code.
        
               | merb wrote:
               | c# even has target-typed expressions which might be
               | useful aswell, i.e. instead of var instance = you write
               | SomeClass instance = new(); this might be preferable so
               | that you have all types on the left.
        
           | agumonkey wrote:
           | I used to have loads of fun abusing Eclipse real time type
           | checker, imagining software live with typed interfaces.
           | 
           | The IDE was my logical buddy, and every idea's possibility
           | was rapidly shown with it. And I need to massage things a
           | bit, I go faster because I know what's missing.
           | 
           | The only time I liked eclipse/java :)
        
           | munk-a wrote:
           | When I'm prototyping I tend to go inside out in a layered
           | fashion - some days I am really feeling the data layer -
           | other days I like to work closer to the fringes. To this end
           | type hinting serves as a quick and dirty code contract before
           | all my pieces are in place. I can splat out a bunch of low
           | level definitions that I know I'm going to need and then come
           | back the next day to add in struts - remembering my choices
           | easily as I go.
           | 
           | I know this isn't the approach of choice for most folks but
           | hey - I'm working with ADHD so I've got to make some
           | allowances for some neurodiversity.
        
           | ttymck wrote:
           | I agree with your point on prototyping. I've never been more
           | productive than when I have the (Scala) compiler acting as a
           | second set of eyes, essentially looking over my shoulder,
           | checking my business logic.
        
           | orwin wrote:
           | The biggest reason why i like type hints is because it force
           | me to reflect on the datatype i want to use before
           | implementing my code.
           | 
           | Last week, i could've done either a dataframe, a list of
           | list, a list of tuple, a dict of tuples, a dict of lists
           | (this was a bad idea that did not survive more than 2s in my
           | head) or a list of dict. I started coding with a dataframe in
           | mind (i guess i wanted to show off my numpy/pandas skills to
           | my devops colleagues), but adding type hints to my prototypes
           | shut down the idea pretty quick: lot of complexity for
           | nothing.
        
           | tabbott wrote:
           | I would argue that the really big benefit of dynamic typing
           | is that it enables a really nice interactive interpreter
           | shell experience. I think it's also important from a
           | prototyping standpoint that Python's static typing model does
           | a lot of inference -- you don't have to add an explicit type
           | annotation on every single variable.
        
             | nyberg wrote:
             | This is possible with statically typed languages with
             | Haskell being one of the examples where it's encouraged to
             | use the REPL to work towards a solution and/or qucikly test
             | ideas without having to write unit tests or full program
             | tests. Ocaml and friends fall within this category too and
             | none require extensive type annotation due to type
             | inference.
             | 
             | I feel that too much focus is on static languages like
             | C/C++ where types become a chore and judging it on that
             | rather than looking at the plenty of languages with type
             | inference brought by ML-style languages.
        
               | BeFlatXIII wrote:
               | > languages like C/C++ where types become a chore
               | 
               | It's been a long while since I used those languages, but
               | I remember the chore part wasn't so much of typing Int or
               | String and more so having to care if it's an Int, Short,
               | or Long or if the float is single or double precision. I
               | believe that those micro-optimizations are no longer
               | popular, but manually thinking low-level is not something
               | I enjoy.
        
             | loup-vaillant wrote:
             | > _I would argue that the really big benefit of dynamic
             | typing is that it enables a really nice interactive
             | interpreter shell experience._
             | 
             | I use the Python REPL quite often, and have non trivial
             | experience with Lua's. But the best experience I've ever
             | got was with OCaml: I type the expression, or function
             | definition, _without giving type annotations_ , and I get
             | the type of the result in the response.
             | 
             | You wouldn't believe the number of bugs I caught just by
             | _looking at the type_. Before I even start testing the
             | function. And that's when I don't have an outright type
             | error, which a dynamic language wouldn't have caught -- not
             | before I start testing anyway.
        
           | steve_adams_86 wrote:
           | > when I have a strongly static typed environment telling me
           | what I just broke.
           | 
           | Yes, I'm a total scatterbrain. Types let me remind myself
           | later that I did in fact forget what I'm doing and what I
           | did. It lets past-me protect future-me.
        
         | angelzen wrote:
         | This is doubly true as experienced programmers argue that
         | designing the data structures is the hardest part of coding.
         | Code follows semi-automatically.
        
           | nitrogen wrote:
           | I'd add data flows as another level above data structures. It
           | helps to think about how data flows into, through, and out of
           | a system, then it's more clear how the data needs to be
           | packaged, and from there, the code follows semi-
           | automatically.
           | 
           | Tangentially related: I think it'd be cool if there was a
           | development environment that combined a node-based dataflow
           | editor with normal text editing, so pure plumbing could be
           | implemented visually, but embedded within (and translated to)
           | textual code.
        
       | KingMachiavelli wrote:
       | I think mandatory type hints in method signatures and optional
       | type hints at assignment are a good compromise.
       | 
       | But if I had to pick either a language without any type
       | hint/inference or a verbosely strictly typed language - I would
       | must rather use the strictly typed language.
        
       | LadyCailin wrote:
       | Oh look, they're finally discovering that strong typing is
       | actually a benefit, and using a language without it is a huge
       | step in the wrong direction.
        
       | chromatin wrote:
       | Even though I had previously learned some rudimentary C, C++, and
       | Java, I really came-of-age with Python. Now, having written (and
       | maintained) nontrivial code bases in statically typed languages
       | including D and Rust (and dabbling in others with contributions
       | in C, OCaml, etc.), I am never going back -- except perhaps in a
       | few cases when a library like PyTorch or Pandas has no good
       | substitute.
       | 
       | (edit: corrected "Linda's" to "Pandas" heh, mobile kbd)
        
       | spicyramen wrote:
       | I wrote Python code for 4 years, then moved to GoLang I really
       | appreciate the typing languages as prevent so many bugs I just
       | was used to handle
        
       | m12k wrote:
       | I've only been in the industry for ~15 years, but it still feels
       | like every year, some ecosystem discovers the value of something
       | that another ecosystem has taken for granted for decades - type-
       | checking, immutability, unidirectional data-flow, AOT-
       | compilation, closures, pure functions, you name it. I'm glad we
       | seem to be converging on a set of best practices as an industry,
       | but sometimes I wish we were spending less time rediscovering the
       | wheel and more time building on top of and adding to the actual
       | state of the art.
        
         | taeric wrote:
         | I've been alive long enough to see that most things are useful,
         | and all things are oversold.
         | 
         | More, the nice easy things to build with major restrictions
         | pretty much gets thrown out the window for complicated things
         | that have constraints that most efforts don't have. This isn't
         | just a software thing. Building a little shed outside? Would be
         | silly to use the same rigor that goes into a high rise. Which
         | would be crazy to use the same materials engineering that goes
         | into a little shed.
        
           | nllsh wrote:
           | Not that I disagree, but I feel like you are overselling the
           | simplicity of [building a
           | shed](https://en.wiktionary.org/wiki/bikeshedding).
        
             | taeric wrote:
             | My point is that all methods and techniques probably have
             | worked for someone doing something.
             | 
             | And I should have leaned in on how much is still left to
             | implementation in terms of "shed." From weather, to what is
             | being stored. It isn't like there is a universal shed
             | design that will make everyone happy.
             | 
             | Nor is this saying that some things aren't truly valuable.
             | Just recognize that some places they don't help as much as
             | you would like. This isn't saying they are bad or
             | worthless. Just acknowledging that they are oversold.
        
           | Zababa wrote:
           | The metaphor doesn't really works, as in software lots of
           | high rise start as a little shed.
        
             | taeric wrote:
             | Quite the contrary, in my opinion. Lots of what makes
             | various parts of the physical hard are in the
             | infrastructure surrounding them. This is very similar to
             | the complexity in software setups.
             | 
             | Take game systems, as an example; far far more effort will
             | be spent in the art and general asset management than is
             | true for many business software setups. Which is why many
             | of the business best practices haven't necessarily moved
             | over to games.
             | 
             | Similarly, look at the general practices around building
             | and maintaining bridges in physical world. We call all
             | bridges by the same name, but reality basically dictates
             | that what works in some locations cannot and will not work
             | in others.
             | 
             | Now, you are right that we can grow large software out of
             | smaller in ways that the physical can't do. But, it is a
             | common fallacy to stall out a project by trying to be at
             | google's scale from the start. Ironic, in many ways, as not
             | even google was built to be at their scale from the start.
        
               | Zababa wrote:
               | I'm not sure what you're trying to say. Something like
               | "the software world is complex, and so is the physical
               | world, so comparing the two makes sense"? If that's what
               | you meant, you're right, but the problem is that
               | comparing the two doesn't lead to better insight in
               | those. If that's not what you meant, then sorry, I didn't
               | understand.
               | 
               | I think that in general we should stop using so much
               | metaphors in the software world. There's no need to go
               | look for a shed. If we had to statically type and test
               | every shell commands we typed, we would lose lots of
               | productivity. On the other hand, maintaining those very
               | large scripts that started as a simple line and are now
               | used for deploying all of our application, and tend to
               | fail in surprising ways, would be easier.
               | 
               | The other problem with metaphors is that they are also
               | hard to refute. I've never built a shed, nor worked on a
               | high rise. I don't see why that experience would be
               | relevant to building software, or necessary in a
               | discussion about static typing.
        
               | taeric wrote:
               | I'm claiming that most of the complications that will
               | actually influence many of the intrinsic choices of both,
               | will be dictated by external factors. Static typing being
               | an intrinsic fact of software, I couldn't tell you what
               | most of the software I use, used.
               | 
               | Calling for lack of metaphor is interesting. In many
               | ways, our industry is nothing but metaphors, so it is
               | surprising for me to see them called down.
               | 
               | I agree that no metaphor is perfect. But, by that same
               | logic, I would argue that no specified type is perfect.
               | Especially if done so in a taxonomy that does not admit
               | exceptions. (And again, I'm not against types.)
        
               | Zababa wrote:
               | > Calling for lack of metaphor is interesting. In many
               | ways, our industry is nothing but metaphors, so it is
               | surprising for me to see them called down.
               | 
               | I don't think that's true. There are lots of metaphor
               | because people love using metaphors, but it's not
               | inherent to our industry. Abstraction is, but abstraction
               | and metaphors are different. Metaphors seem to mostly
               | come from blog-post type content, where people want to
               | give you an intuition for something in less than 10
               | minutes. There's a really good article about this, in the
               | context of monad tutorials, which are some of the most
               | proheminent victims of these metaphors
               | https://byorgey.wordpress.com/2009/01/12/abstraction-
               | intuiti....
               | 
               | > I agree that no metaphor is perfect. But, by that same
               | logic, I would argue that no specified type is perfect.
               | Especially if done so in a taxonomy that does not admit
               | exceptions. (And again, I'm not against types.)
               | 
               | I would call types an abstraction rather than a metaphor,
               | though I agree with you that they are not perfect, in
               | that all abstractions trade precision and exhaustiveness
               | for speed. There are interesting alternatives to this
               | with property-based checking and whatever clojure.spec
               | is, and type systems themsleves are getting better, but
               | we're still not at perfection. And even then, I don't
               | think we will ever reach it. The "best" type systems
               | currently all seem to have some structural parts, and
               | some nominal parts, so there's no silver bullet.
               | 
               | I mostly use types to avoid stupid mistakes (I make lots
               | of typos, and Typescript helps a lot here), and to
               | improve developer tooling. I'd like to try some approach
               | with DDD and types, but my current company isn't big on
               | DDD, so I can't really judge it. I also like using unit
               | and integration tests. All of these make me feel safer
               | when doing changes. But some people are fine with
               | catching errors in production and quickly fixing them.
        
               | taeric wrote:
               | What I meant in our industry being nothing but metaphor
               | is basically me staring at so many OO taxonomies. Even if
               | you ignore deep OO trees (and I think you should), it is
               | hard not to see the way we define most data and
               | simulations as anything other than a very formal
               | metaphor.
               | 
               | That said, I was not trying to say that abstractions and
               | types are directly metaphor. I agree with your points. My
               | argument there was that, like metaphors,
               | types/abstractions are never perfect.
               | 
               | I use types to avoid type errors. Which is a big class of
               | error, to be sure. But they do little to help with logic
               | errors, in my experience. And they are flat detrimental
               | if they require pulling in more and more formalism to
               | cover cases that are of increasingly limited ROI.
               | 
               | If anything, I think our industry would do well to
               | embrace many of the modelling domains that allow use of
               | SAT solvers to find answers. And I don't think I've ever
               | seen a strongly typed one of those that wasn't hard to
               | follow. (I am interested in counter examples.)
        
               | munificent wrote:
               | _> Take game systems, as an example; far far more effort
               | will be spent in the art and general asset management
               | than is true for many business software setups. Which is
               | why many of the business best practices haven 't
               | necessarily moved over to games._
               | 
               | You're right that game development involves a lot of
               | asset stuff that other business software doesn't have to
               | worry about as much. (And, conversely, a lot of business
               | software has to worry about large mutable datasets much
               | more than most games.)
               | 
               | But I don't think that has much bearing on why some
               | business software practices haven't made their way to
               | games. I think the reasons are mostly:
               | 
               | * Games are structurally different from business
               | software, so the patterns that work for the latter aren't
               | always great for the former. MVC makes sense when the
               | "UI" is a relatively thin layer insulated from the
               | "logic". In most games, the "UI" (rendering, animation,
               | VFX, audio, etc.) is huge and more deeply coupled to the
               | game state.
               | 
               | * A lot of enterprise software practices are about
               | maximizing developer productivity in a single codebase
               | over a very long period of time at the expense of runtime
               | performance. Game codebases often have a shorter lifespan
               | and can't afford to sacrifice runtime speed for developer
               | speed.
               | 
               | * Game developers can be insular and are often either
               | oblivious to what's going on outside of games or think
               | it's beneath them and not applicable to their "real"
               | code.
        
         | AlexCoventry wrote:
         | I think the limiting factor in the case of python getting type
         | hints was that it was never designed for type safety in mind,
         | and that it took a while to establish consensus on a good type-
         | hinting system.
         | 
         | I don't think it's a matter of reinventing the wheel, in this
         | case, more a matter of bolting something like a wheel on a
         | system which didn't start with wheels.
        
         | axiosgunnar wrote:
         | I think the problem is to figure out what the best practices
         | actually are.
         | 
         | What we are observing here is ,,the market fixing it".
         | 
         | The process is messy and redundant, but effective.
        
           | [deleted]
        
         | jeffbee wrote:
         | Yes, and when that ecosystem discovers these obvious facts, the
         | discovery is always described as a "journey" in the
         | accompanying blog post. Having sense and good taste at the
         | beginning of a project doesn't warrant a blog post but slowly
         | stumbling over isolated aspects of good taste, now that's a
         | journey.
        
         | d0mine wrote:
         | Programming is sufficiently complex field that we can find
         | examples when the opposite things are the best: it depends on
         | context whether you need more or less types.
        
       | andybak wrote:
       | Having spent a decade with Python and more recently a few years
       | with C# I still can't quite put my feelings into words but here's
       | an attempt:
       | 
       | "The benefits of explicit typing are obvious and clear but they
       | downsides are subtle and hard to communicate"
       | 
       | I still think typing in general is a net win but I'm not sure
       | whether static typing is. You find yourself writing code that
       | just wouldn't be neccesary in a dynamic language - and I don't
       | just mean the direct code you write to declare and cast types.
       | There are more subtle costs.
       | 
       | I need to spend time with a good type inference in a language
       | with modern typing and dynamic features to sort out how I feel
       | about this.
        
         | layer8 wrote:
         | Types are effectively assertions about the values they
         | represent, and statically-typed code constitutes proofs that
         | the assertions actually hold at runtime. The static typing
         | forces you to be sufficiently rigorous in those proofs, which
         | may require additional code as you mention. Without static
         | typing, one has to rely on the "proofs" in one's head to be
         | correct (which humans aren't really good at), instead of having
         | the compiler double-check one's reasoning.
        
       | mdoms wrote:
       | It is extremely funny to me watching Silicon Valley types slowly
       | (very slowly) re-invent everything we knew about programming
       | languages decades ago.
        
         | pjmlp wrote:
         | Well for the hipster culture what we were doing wasn't cool.
        
       | gundamdoubleO wrote:
       | I've seen a lot of push back on adding type checking to Python
       | but we had a similar case at my company where we tried it out on
       | a new project and the clarity and readability of the code was
       | immediately beneficial to the entire team. Perhaps it's something
       | well suited to larger codebases.
        
         | handrous wrote:
         | I want type checking on pretty much anything that _will ever_
         | exceed about two screenfuls of code. If I can 't keep the whole
         | thing in my head at once, I want the computer to do it for me.
         | That's the point, right? Making computers do stuff for us so we
         | don't have to?
        
           | david422 wrote:
           | I kindof think of them as a giant set of unit tests. The
           | compiler/linter etc. can check every variable and every
           | function call to check to make sure you didn't mix up your
           | types, which _will_ blow up at runtime if you got them wrong.
           | 
           | So rather than write them all by hand, just get your tools to
           | do it.
        
             | handrous wrote:
             | I see them as documentation of what I _think_ something
             | means or is, which the computer can check for accuracy
             | (more or less), both as I write and as the codebase
             | changes.
             | 
             | That legibility to the computer is what makes them _much
             | better_ than documenting the same thing some other way. Are
             | they out of date? Were they wrong to begin with? The
             | computer will tell me, no action needed on my part. I need
             | to look up something in the context of what I 'm reading
             | right now--oh, look, the computer just told me exactly what
             | I needed.
        
         | matsemann wrote:
         | Just wish Python's typing was better. But it's impossible to
         | type hint the crazy "pythonic" code out there. Like the kwargs
         | used to do a Django query.
        
         | hobs wrote:
         | I think it's well suited to anything really - the amount of
         | casual problem solving and inference you can make from some
         | simple types is pretty big in my experience, and Python's
         | approach to allow you optionally buy into it is really nice.
        
         | tester756 wrote:
         | It's $current_year and there's still debate whether checking
         | stuff at compilation time is better than at runtime?
        
           | kzrdude wrote:
           | That's not really the debate in Python :)
           | 
           | Almost every Python user now has to "deal" with type
           | annotations. It's tempting to gradually add type annotations,
           | it's nice documentation.
           | 
           | But it also rubs me the wrong way to have annotations that
           | are never checked(!). In many codebases, you might just have
           | "casual" style type annotations in Python, and nothing ever
           | asserts that they hold. That's nagging on me, a bit.
        
             | inertiatic wrote:
             | Never checked? They're statically checked.
             | 
             | Also, tooling like https://pydantic-docs.helpmanual.io/ can
             | do runtime checking for important parts of your app or you
             | can use this https://github.com/agronholm/typeguard to
             | enforce all types at runtime (although I haven't measured
             | the performance impact, probably something to do in a
             | separate environment than production?).
        
               | kzrdude wrote:
               | They are statically checked, if you run a type checker.
               | Which many don't.
        
           | novok wrote:
           | People who don't think types are a good thing need to work in
           | a statically typed language for a year or two and then see
           | what a difference it makes in reality. Unproductive Java
           | bureaucracy != static typing.
           | 
           | I think the people debating it never tried it seriously.
        
             | wvenable wrote:
             | A lot of people grew up with Java -- especially early Java
             | -- as their primary language. It was taught heavily in
             | schools.
             | 
             | I think it ruined a lot of people to static typing and
             | exceptions because Java is/was terrible for both of those
             | things.
        
             | jonny_eh wrote:
             | After using TypeScript for even a little bit I find it
             | painful to go back to JavaScript for anything more complex
             | than white-boarding.
        
             | fiddlerwoaroof wrote:
             | I've done everything from Haskell to Java and I still
             | strongly prefer Clojure and Common Lisp-style dynamic
             | types.
        
               | arwhatever wrote:
               | Is it possible to elaborate in a comment? Honestly I
               | probably wouldn't take the time to read a lengthy
               | article, but if there's some elevator pitch then I'm all
               | ears.
        
               | fiddlerwoaroof wrote:
               | What makes CL/Clojure really work is that your editor
               | (emacs usually, but there's other options now) connects
               | to the live program and has access to the entire runtime
               | environment. So, you can do a lot of the things other
               | languages need static types for via introspection (e.g.
               | autocomplete: CL just asks the running program what
               | functions are available that matches the current pattern
               | and returns a list).
               | 
               | Secondly, since I've learned statically typed languages,
               | I already have a mental model for how they make you
               | structure your code, except dynamically typed languages
               | make patterns easy that would require something like
               | dependent types to check (see how complicated Typescript
               | is, because it has to be able to model JS idioms). My
               | experience is that a lot of the value of static types
               | isn't in the checking but in the modeling aspect: if you
               | follow the general patterns you'd use in Haskell
               | (represent algorithms like "apply a function to each
               | member of the list" as functions), you reduce the amount
               | of thought it takes to see the program is correct by
               | splitting it up. For example, if I have this pattern in
               | my imperative codebase:                   let result = []
               | for (let idx = 0; idx <= input.length; idx++) {
               | result.push(input[idx]+1);         }         return
               | result
               | 
               | I have at least three things mixed up together: accessing
               | each member of a list (and there's an easy to miss off-
               | by-one error in this implementation), transforming that
               | member and building up a result. If I translate this to a
               | functional style, it's easier to see that the
               | implementation is correct:                   const inc =
               | v => v+1         . . .         return list.map(inc)
               | 
               | Looking at this code, I can break down correctness into
               | three questions: is list.map implemented correctly? is
               | inc (the transformation) implemented correctly? And,
               | assuming both are correct, are these two functions
               | combined in the correct way? Types definitely can help
               | here but my experience is that 90% of the benefit isn't
               | the _checking_, it's the code structure you end up with
               | as a result.[1]
               | 
               | Now, if this is true, why do I prefer dynamically typed
               | languages? Well, it comes down to two things: I find the
               | "live programming" model of CL/Clojure more productive
               | and roughly equal to types when it comes to checking
               | correctness (and I don't think it's just me, I've seen
               | various papers, etc. that claim Haskell and Clojure have
               | roughly equal defect rates); and, I find the patterns I
               | like in CL/Clojure/Javascript require much more
               | sophisticated type checkers to actually validate, and
               | such type-checkers have a huge up-front learning cost and
               | still add a lot of boilerplate that exists mainly to
               | convince the type-checker that you know what you're
               | doing.
               | 
               | Finally, in a language with macros, you can roll your own
               | static guarantees: one project I worked on was doing a
               | bunch of calculations inside a database. We hit an edge
               | case where the DB's idea of a week didn't match our
               | requirements. As a result, I wrote a code generator that
               | generated Clojure functions and DB queries
               | simultaneously. In this situation, if you assume the code
               | generator is correct, you have a compile-time guarantee
               | that the Clojure versions of the queries are equivalent
               | to the calculations being done inside the DB.
               | 
               | [1]: This page surveys a bunch of studies on the question
               | of dynamic v. static types and finds the evidence in
               | favor of static types to be surprisingly small
               | https://danluu.com/empirical-pl/
        
               | novok wrote:
               | I could see a statically typed language that would give
               | you a live reflection system and macros. I think it's
               | more if you have to chose, you'd rather have that than
               | static types.
               | 
               | But I think it is possible to have all 3, it just doesn't
               | exist in any popular language that I am aware of.
        
               | fiddlerwoaroof wrote:
               | The problem is that the "live programming" aspect
               | violates a fundamental assumption of a lot of static type
               | systems: the "closed world" assumption that all the
               | relevant types are known at compile-time. If you can
               | dynamically extend/redefine the types on the fly, your
               | type-system guarantees start getting weaker anyways.
               | Instead, you need a system of contracts or something like
               | Racket has.
               | 
               | Also, if you have macros, you can always just embed a
               | Haskell into your language for the parts where you want
               | that sort of guarantee: https://coalton-lang.github.io/
        
               | magicalhippo wrote:
               | > This page surveys a bunch of studies on the question of
               | dynamic v. static types and finds the evidence in favor
               | of static types to be surprisingly small
               | 
               | Most of the studies seem to be rather poor though, so
               | difficult to draw any solid conclusions from them. Almost
               | all seem to drown in noise, or have flawed setups.
               | 
               | From personal experience, with a static type language I
               | can jump into an unknown codebase and make non-trivial
               | modifications much, much faster than if it's a dynamic
               | type language codebase.
               | 
               | I've wasted soooo many hours doing print(dir(x)) in
               | Python it's far beyond funny.
               | 
               | On the flip side, over the years I've helped countless
               | people with their C/C++/Delphi code in minutes,
               | frequently using libraries and API's I've never seen
               | before.
        
               | JoelMcCracken wrote:
               | I'm curious, after having done a significant amount of
               | Haskell, I have flipped that opinion. The biggest
               | difference is how the types help make things explicit and
               | clear.
               | 
               | (although, IMO, I think purity makes a very large impact
               | here too)
        
               | lemper wrote:
               | i have a small side project in clojure [1] and i always
               | miss type checking when working on it. not by much
               | because it's a small project but i am tired of iseq is
               | not a function error.
               | 
               | [1] https://dactyl.siskam.link
        
               | fiddlerwoaroof wrote:
               | I sort of think there are two mindsets behind this
               | debate: people that miss the guard rails of a static type
               | system and people that enjoy the experience of iterating
               | quickly in a dynamically typed language. I don't really
               | want to say everyone should pick one side or the other,
               | just that my experience doesn't bear out the claim that
               | "statically typed languages produce more maintainable
               | code". And, the little bit of empirical evidence for this
               | proposition is largely inconclusive:
               | https://danluu.com/empirical-pl/
        
               | simion314 wrote:
               | >and people that enjoy the experience of iterating
               | quickly in a dynamically typed language
               | 
               | Programmers spend more time reading code then writing it.
               | So I personally prefer the devs in the team will spend
               | more time typing the code or use a bit more brain energy
               | to think about types so later we can all read the code
               | and understand it and edit faster.
               | 
               | Dynamic works great for write-only scripts.
        
               | fiddlerwoaroof wrote:
               | People always say this about reading code and it's just
               | never matched my experience working in either sort of
               | codebase: one difference (comparing lisps and, say,
               | Typescript or Java) is that lisps just have fewer lines
               | to read. So, any assistance you get from the types is
               | counteracted by having to read more code.
               | 
               | But, additionally, I just don't find it true to my
               | experience that it's easier to read and understand a
               | dynamically typed codebase vs. a statically typed one.
               | Especially when you have a lisp-like environment that
               | makes accurate jump-to-definition possible.
               | 
               | EDIT: I think I just tend to think about codebases in
               | terms of operations rather than types. And, consequently,
               | when I build a codebase around compositions of functions,
               | the way I think about it isn't very different in either
               | paradigm.
        
               | jfabre wrote:
               | I have to agree, I've done over 5 years of C# and then
               | went to ruby and never looked back. Static type checking
               | raises the floor on incompetence, but also lowers the
               | ceiling on excellence. I have to admit I don't have
               | experience with the extremes which would be Haskell and
               | Clojure.
               | 
               | The amount of cruft I had to type in C# just to get shit
               | done... It's all implicit in ruby thank god for that.
               | 
               | I never EVER have to check the type of a variable at
               | runtime. I always know its type just by looking at its
               | name. Is it enforced in ruby? Of course not. Ruby assumes
               | I'm an adult and I know that I'm doing.
        
               | inertiatic wrote:
               | >I always know its type just by looking at its name.
               | 
               | Do you ever feel the names are getting too verbose and it
               | would be great to have tooling that would allow you to
               | get that information on mouse-over instead of having it
               | make your lines almost unreadable?
               | 
               | I mean, there's a reason mathematics have decided to keep
               | variable names short instead of having the names contain
               | all the context.
        
               | ylyn wrote:
               | > also lowers the ceiling on excellence
               | 
               | > I never EVER have to check the type of a variable at
               | runtime.
               | 
               | > I always know its type just by looking at its name
               | 
               | I guess you've only ever written web backends and menial
               | things like that?
        
               | handrous wrote:
               | > I always know its type just by looking at its name. Is
               | it enforced in ruby? Of course not. Ruby assumes I'm an
               | adult and I know that I'm doing.
               | 
               | TIL taking notes of things you want to be reminded of in
               | the future is for children and the incompetent.
        
               | mwcampbell wrote:
               | > Static type checking raises the floor on incompetence,
               | but also lowers the ceiling on excellence.
               | 
               | At 40 years old, I've seen enough of my own incompetence
               | that I'll gladly accept things that can mitigate it. As
               | for excellence, I suppose static typing would have
               | prevented a handful of clever hacks that I did in Python
               | and Lua when I was in my 20s, 12+ years ago. Truthfully
               | though, my memory of that period has faded enough that
               | I'm not sure, and I doubt that any of those hacks were
               | crucial for the products that I was developing at that
               | time. Yes, a type system as primitive as Java's at that
               | time would have felt like a straitjacket. The same might
               | have also been true for C#. But modern static type
               | systems are much more flexible, and I don't think I've
               | rejected a language based on its static type system in
               | the past several years. (I've recently done a project in
               | Elixir, but that was despite its dynamic typing, not
               | because of it.)
        
               | malfist wrote:
               | How in the world does type checking lower the ceiling on
               | excellence?
        
               | twelve40 wrote:
               | i'm guessing maybe sometimes type checkers make you jump
               | through the hoops to pass, and the OP finds that
               | distracting? To me though, the benefits of type checking
               | far outweigh the cost.
        
               | cle wrote:
               | I"m guessing by rejecting perfectly valid and correct
               | programs that are unable to be type checked. There is a
               | large space of "false negative" programs that a type
               | checker will reject, but that could be perfectly correct.
               | E.g. compare Python-esque duck typing with nominal
               | typing.
        
               | novok wrote:
               | You can have that by using Any as your type if you so
               | wish typically and escape the type system for those rare
               | circumstances.
               | 
               | Also C# suffers a similar issue with what I call
               | "unproductive Java bureaucracy", since it's basically
               | Microsoft Java. Bureaucracy is not static typing. You can
               | also have full dynamic dispatch and still have static
               | typing too.
        
               | jfabre wrote:
               | Tbh I'm conflating multiple things, I've heard a lot of
               | good things about Haskell.
               | 
               | But in C# for example, if the system was not designed
               | with dependency injection and everything being an
               | interface it's very hard to build a test harness since
               | you can't mock anything. Which means everything has to be
               | tested manually. (I haven't done any C# in a long time,
               | maybe it's not the case anymore)
               | 
               | So you have to create an interface and classes for every
               | implementations for every type in the system just so I
               | can change its type dynamically. By the time you're done
               | with all the cruft, you forgot what you were about to
               | code.
               | 
               | I'm infinitely more productive in Ruby compared to C#.
               | But I can understand dynamic languages not being
               | welcoming to juniors, since they can code themselves into
               | pitfalls that will bite them later.
        
               | bmm6o wrote:
               | > _But in C# for example, if the system was not designed
               | with dependency injection and everything being an
               | interface it 's very hard to build a test harness since
               | you can't mock anything. Which means everything has to be
               | tested manually._
               | 
               | I wouldn't have put it like that, but I think I know what
               | you mean. Mocks for unit testing do require that you have
               | defined an interface to implement, which means that every
               | class that you want to be mocked out needs to have an
               | interface extracted. It is extra work. Overall I think
               | the tradeoff is worth it, myself, especially if your IDE
               | can automated extracting an interface. But it is dumb
               | work that a smarter language/type system could avoid.
        
               | magicalhippo wrote:
               | > [...] which means that every class that you want to be
               | mocked out needs to have an interface extracted. It is
               | extra work.
               | 
               | If you define the interface first, then you can simply
               | copy-paste that into the class definition and off you go
               | implementing it. Hardly any more work at all.
        
               | jayd16 wrote:
               | https://docs.microsoft.com/en-
               | us/visualstudio/test/isolating...
               | 
               | This and other libraries can supersede method modifiers.
        
         | [deleted]
        
       | papito wrote:
       | Lack of type checking was a hot thing for a while. It made you
       | "move faster". It was actually sold as an advantage. Until we
       | realized that after moving faster you grind to a halt because now
       | you have a massive codebase, with hundreds or thousands of files,
       | and everything takes forever, and every change requires multiple
       | rounds of testing.
       | 
       | I believe it really has to do with the size and complexity of
       | modern projects. With a half-decent IDE you could sort of used
       | non-type-checked Python in 2012, but times have changed, and now
       | we are talking about statically checking Python and Ruby. And
       | Javascript, of course, now has it in form of TypeScript.
        
       | tabbott wrote:
       | I agree with all the benefits of mypy cited in this article. For
       | me, most important thing for the long-term health of a codebase
       | is its readability/maintainability, and mypy static typing makes
       | such a huge difference for that in large Python codebases. I'm
       | really excited to see large libraries doing this migration.
       | 
       | I'll add for folks thinking about this transition that we took a
       | pretty different strategy for converting Zulip to be type-
       | checked: https://blog.zulip.com/2016/10/13/static-types-in-
       | python-oh-...
       | 
       | The post is from 2016 and thus a bit stale in terms of the names
       | of mypy options and the like, but the incremental approach we
       | took involved only using mypy's native exclude tooling, and might
       | be useful for some projects thinking about doing this transition.
       | 
       | One particular convention that I think many other projects may
       | find useful is how we do `type: ignore` in comments in the Zulip
       | codebase, which is to have a second comment on the line
       | explaining why we needed a `type: ignore`, like so:
       | 
       | * # type: ignore[type-var] #
       | https://github.com/python/typeshed/issues/4234
       | 
       | * # type: ignore[attr-defined] # private member missing from
       | stubs
       | 
       | * # type: ignore[assignment] # Apparent mypy bug with
       | Optional[int] setter.
       | 
       | * # type: ignore[misc] # This is an undocumented internal API
       | 
       | We've find this to be a lot more readable than using the commit
       | message to record why we needed a `type: ignore`, and in
       | particular it makes the work of removing these with time feel a
       | lot more manageable to have the information organized this way.
       | 
       | (And we can have a linter enforce that `type: ignore` always
       | comes with such a comment).
        
       | exdsq wrote:
       | I can't understand programming without types - it's just so
       | weird...
        
         | kzrdude wrote:
         | Python is not without types, there are dynamic types.
        
           | exdsq wrote:
           | Sorry to be specific I find it so weird to program without
           | defining what I want in and out of a function
        
       | richard_todd wrote:
       | I think it's interesting that PEP 484 says ([1]): "the authors
       | have no desire to ever make type hints mandatory, even by
       | convention," while the opening of this article says "type hints
       | have grown from a nice-to-have to an expectation for popular
       | packages." Things don't always work out the way the PEP authors
       | expect.
       | 
       | [1]: https://www.python.org/dev/peps/pep-0484/
        
       | dado3212 wrote:
       | Honestly will never go back to languages without type checking,
       | it prevents so many bugs and is a huge help in understanding code
       | you haven't worked with previously.
        
         | SketchySeaBeast wrote:
         | > is a huge help in understanding code you haven't worked with
         | previously
         | 
         | This is huge for me. As someone who takes on already completed
         | projects, it's a huge help with debugging and understand what's
         | going on without requiring you to know the whole system forward
         | and backwards. Sure, you still need to build a mental map of
         | the general code flow, but you can look at a single function
         | and clearly see the obvious inputs and outputs. Combine that
         | with a a stack trace and you can debug that method as a single
         | unit and then start to look at where it's called and what its
         | downstream effects are. You don't need to start from the very
         | beginning of the call and then follow it through, keeping
         | mental track of what is available and in what form when and
         | where.
        
         | tunesmith wrote:
         | I've been a types advocate for years, but it wasn't until
         | working with Typescript that I started experiencing some of the
         | downsides...
         | 
         | To me, ideally, types are supposed to be a benefit not only in
         | safety, but in understanding the intent of a piece of code more
         | quickly. For an api or library interface, review the types to
         | see what its intentions are.
         | 
         | But there's something about the typescript type system, with
         | all the picks and keyof and typeof... sometimes it just feels
         | like it's way too easy to go overboard, to the point that it
         | occludes meaning. I understanding struggling with types if
         | you're struggling with figuring out _exactly_ what your
         | boundary does and does not allow, but when you 're struggling
         | with types just because you're struggling with the kabillion
         | different ways that some other typescript programmer chose to
         | use the utility types... there are times when I feel like even
         | Scala is easier.
        
           | Zababa wrote:
           | The problem is that typescript is here to type existing JS,
           | and existing JS has been written without thinking about
           | types. "fresh" TS might be better for that.
        
           | IshKebab wrote:
           | Your criticism boils down to "it's possible to overcomplicate
           | things". Sure if you completely remove static typing then you
           | can't overcomplicate static typing. But is that really an
           | argument against static typing?
        
           | Shacklz wrote:
           | > with all the picks and keyof and typeof...
           | 
           | Depends of course a lot on the codebase but all typescript
           | codebases that I've seen so far and considered "well-
           | maintained" didn't really use keyof and typeof all that much.
           | The only way I can imagine how one ends up with lots of those
           | keywords is when you start with a dynamic language approach,
           | and then tell the compiler afterwards what that type might
           | be, instead of defining the type beforehand - might that be
           | the issue?
        
         | seiferteric wrote:
         | It is kind of ridiculous not to have types. I think in the old
         | days handling types felt to heavy for scripting languages, but
         | now with type inference and stuff I don't think it is any
         | longer.
        
         | handrous wrote:
         | > Honestly will never go back to languages without type
         | checking, it prevents so many bugs and is a huge help in
         | understanding code you haven't worked with previously.
         | 
         | I see static types as one of the most powerful communication
         | tools around, as far as code goes. I can't relate at all to
         | people complaining that they waste time. They must work _very_
         | differently from how I do, is all I can figure. It 's that, or
         | they don't realize how much time they're losing to
         | communication-related tasks, or refactoring, or writing (and
         | maintaining!) extra or more verbose tests, or having even one
         | more bug per year make it to production, or whatever, that'd be
         | saved by static types, so aren't correctly accounting for the
         | time savings. One of the two.
        
           | alpaca128 wrote:
           | I can remember that in the beginning it felt cumbersome to me
           | because I wasn't all too familiar with that language's type
           | system and so I had all kinds of errors thrown at me.
           | 
           | But it's something you just have to get used to, and now that
           | I understand it much better I feel more productive and have
           | more confidence in my code. And the communication aspect is
           | definitely a great help too. No handwritten documentation can
           | be this consistent, completely independent of who touched the
           | code (though to be fair, it's still difficult to get the
           | naming right).
           | 
           | I can't imagine the people calling it a waste of time got
           | over the hump in the beginning. To me it's obviously a
           | timesaver. It does a tedious, difficult (for humans) task and
           | does it quickly & with perfect accuracy. Beforehand worrying
           | about all the type signatures and interfaces felt like 4D
           | Sudoku across various modules, now I can concentrate on the
           | interesting parts.
        
           | adrianmonk wrote:
           | Consider these 4 possible combinations for programming
           | languages:
           | 
           | (1) Low-level, static types
           | 
           | (2) Low-level, dynamic types
           | 
           | (3) High-level, static types
           | 
           | (4) High-level, dynamic types
           | 
           | For whatever reason, historically #1 and #4 have been most
           | popular. C, C++, Pascal, Ada, and Java are #1. Python,
           | JavaScript, Perl, and BASIC are #4.
           | 
           | There haven't been a lot of #2 or #3 languages. Some #3
           | languages (TypeScript and Python with types) have come along,
           | but relatively recently.
           | 
           | A person who experiences only #1 and #4 might notice that
           | they can whip up programs faster in a #4 language than in a
           | #1 language, then falsely attribute that difference to the
           | static types. Whereas the real reason is working at a
           | different level of abstraction.
        
             | pansa2 wrote:
             | Exactly. IMO we still don't have a good #3 language - in
             | particular, all of the popular languages that can be
             | compiled into native code are in category #1.
        
             | fuckf4ce wrote:
             | Lumping Java and C together is a stretch. Even Ada and C. I
             | think Ada is a good example that your proposed
             | classification scheme is invalid -- what is low vs high
             | level? Maybe Java has historically been weak in the
             | language features department but it has had non-optional
             | automatic memory management from the get go. What about C#?
             | It's not exactly unpopular.
        
             | DylanSp wrote:
             | I don't really see how Java is closer to C than it is to
             | Python in terms of what level of abstraction it's working
             | on, could you elaborate?
        
               | nayuki wrote:
               | It's a matter of opinion whether Java's level is closer
               | to C or Python. But I can name a bunch of high-level
               | features in Python that aren't in Java:
               | 
               | List literals, dictionary literals, tuples, bigint
               | literals, byte strings, f-strings, sequence unpacking
               | assignment, named parameters (kwargs), decorators,
               | closures (functions within functions), metaclasses,
               | generators, async, list/set/dict/generator
               | comprehensions, multiple inheritance, natural JSON
               | support, ...
        
               | Jensson wrote:
               | However Java do support dynamic typing for every object
               | even if it is a bit cumbersome so putting it at the same
               | level as C++ isn't accurate either.
        
               | jayd16 wrote:
               | C# has most of this stuff and I think Java has at least a
               | few of these but I wouldn't say any of these are really
               | much of a differentiator.
        
               | pjmlp wrote:
               | And at what level of abstraction does ISO C work on?
               | 
               | "C Is Not a Low-level Language, Your computer is not a
               | fast PDP-11."
               | 
               | https://queue.acm.org/detail.cfm?id=3212479
        
               | Jensson wrote:
               | That would be a good point if you actually could write
               | the final code for CPU's, but you can't since the CPU
               | internals do that for you. So from an application
               | programmers perspective machine code is as low as it gets
               | and C maps really well to machine code so C is a low
               | level language.
        
               | SubjectToChange wrote:
               | > So from an application programmers perspective machine
               | code is as low as it gets and C maps really well to
               | machine code so C is a low level language.
               | 
               | C only "maps really well" to PDP-11 style machine code.
               | If you want SIMD, parallel algorithms, heterogeneous
               | programming, memory hierarchies/domains, etc then ISO C
               | is completely useless.
        
             | shpongled wrote:
             | #3 languages have been around since the 80's (SML) and 90's
             | (Haskell)
        
         | zz865 wrote:
         | I feel the same way but wonder if I'm right when the majority
         | of jobs are JS and Python.
        
           | shrimp_emoji wrote:
           | Both of those languages' communities have essentially
           | admitted that not having type checking was a mistake and try
           | to patch it with TypeScript and MyPy.
        
       ___________________________________________________________________
       (page generated 2021-10-18 23:01 UTC)