[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)