[HN Gopher] Things I Was Wrong About: Types ___________________________________________________________________ Things I Was Wrong About: Types Author : abyx Score : 161 points Date : 2020-09-27 07:37 UTC (8 hours ago) (HTM) web link (v5.chriskrycho.com) (TXT) w3m dump (v5.chriskrycho.com) | mantap wrote: | A good static type system is better than dynamic typing. But, | dynamic typing is better than a bad static type system. | | A bad static type system will slow you down, forcing you to | appease its irrelevant complaints, and pervert your code into a | form that no sane programmer would choose to write it in, if it | weren't for the type system looking over their shoulder. I won't | mention any names, but suffice to say such languages exist. | | On the other hand, a good type system recognises that its goal is | to allow the programmer to write code first in the form that | makes sense to them, and _then_ be expressive enough to describe | it. | igouy wrote: | good is better than bad ! | donatj wrote: | I hated types in college, I thought they were a huge waste of | time, and on their way out. | | But over the years that opinion shifted. I got experience with | the actual types of problems we would see. Again and again | runtime errors in production, usually from type issues. This | really pushed me to invest my time into static analysis tools, | and static analysis tools work best when types are at the very | least annotated. This was an lead in to compiled typed languages | where these types of runtime errors are rare if not impossible. | | As someone in a similar boat, I feel still like it's more fun to | knock out a really quick prototype in an untyped language. | | For something I actually have to maintain and build tests for, a | well typed language is absolutely preferable. I used to quip that | no one could build maintainable JavaScript, and I enjoyed writing | JavaScript, but now with TypeScript I think it's largely doable. | | I think what really changed in me is my desire to knock something | out quickly was replaced with the desire to have stable software | where components could be built well from the get-go and not need | modifications for years. | Toutouxc wrote: | Is 'Maybe Haskell', mentioned in the article, worth reading? Or | can anyone recommend a similar book of the 'just enough to be | dangerous' kind on Haskell or maybe Clojure? | christoomey wrote: | 'Maybe Haskell' is a fantastic intro to using types and why you | might care about them. It's focused in scope and really meant | to give you a taste of some functional ideas and a bit of | Haskell, and in that effort it does a great job. | | Also, you can't beat the price ($0). If nothing else, probably | worth browsing through to see if something resonates with you. | filoeleven wrote: | I learned Clojure by reading _Clojure for the Brave and True_ | and can recommend it as a good intro. It helps that the | language itself is simple and consistent. You can get | remarkably dangerous with only a few hours of study. | | https://www.braveclojure.com/ | tom_mellior wrote: | I don't know about Maybe Haskell, but you can give Real World | Haskell a try for free: http://book.realworldhaskell.org/ | | I found it a pretty good introduction. Unfortunately at some | point it devolves into typical Haskell "how can we turn this | clear, readable three-line function into a _two_ -line function | by peppering it with obscure operators". | jgilias wrote: | I once told a JavaScript guru colleague of mine that I was | spending my free time dabbling in Haskell. His response was 'lol, | why would you do that?'. His point was that spending time | learning things that you're not going to be using directly any | time soon is a waste of time. My point was (and still is), that | learning such things opens up a completely new way of thinking | about problems and potential solutions. | quickthrower2 wrote: | Does he use Redux by any chance :-) | mumblemumble wrote: | Limiting what you learn is, by definition, self-limiting. | | On the one hand, that's a useless truism - we can't learn | everything, so you do have to pick and choose. On the other | hand, I still find the framing useful, because it reminds me | that, in a sense, the decisions about what _not_ to learn are | more impactful. | | To the example of programming languages: Only focusing on | perfecting my skills at the tools I currently use would leave | me less able to understand the limitations of the tools I | currently use. And would limit my ability to take my career in | new paths where I might have a need to use other tools. On the | other hand, learning every single new thing might distract me | from properly mastering my current tools, or might eat up so | much of my free time that I'm effectively spending all my time | thinking about work and never recharging my batteries. | wccrawford wrote: | My experience matches yours. Every time I learned a new | language, I gained a new understanding of a different way of | doing things, and I was able to bring some of that | understanding to my daily job's language. It's absolutely made | me a better programmer. | | I didn't just study those languages for nothing, though. I | always had a purpose in mind for them, with the possible | exception of Ruby which just seemed neat. | loup-vaillant wrote: | _" Never learn anything you don't see immediate use of"._ | | Such short term thinking could explain why dynamic typing is so | appealing: simpler implementations, more possibilities than | most mainstream static type systems, while requiring basically | no learning at all. | jariel wrote: | People often think about coding, but that's generally a small | piece of it. | | Typing is a form of structure, and especially a kind of | documentation. | | When encountering APIs of various kinds 'typing' is part of how | they are expressed. | | It's hard to build large monoliths without typing. | | When working with 'wide scope' problems, when the typing is more | oriented towards the data, that's another thing altogether, which | is why I think for many systems untyped JS and Python works well | enough there. | | What most typed systems lack is a fluent way of mapping to 'data | types' which are often external to the system, or at least | externally defined. | carapace wrote: | Thesis, Antithesis, Synthesis | | Types Bad, Types Good, Types are a gateway into sophisticated | automation of programming tasks but not a panacea. | hn_throwaway_99 wrote: | I'll add a 4th thing to his list based on my experience moving | from Java to Typescript: | | _Nominally_ -based type systems like Java (where you can only | write Foo f = new Bar() if Bar has Foo somewhere up it's static | type chain or interface hierarchy) are way more of a pain in the | ass than _structurally_ -based type systems like TypeScript | (where you can say f: Foo = new Bar() as long as TypeScript | determines Bar has all the required fields of Foo). | | Primarily this makes refactoring _much_ easier because you don 't | have to necessarily change things up a (potentially brittle) type | hierarchy. Instead, you can just add new properties to your type | to fulfill the set of properties required on the target type. | couchand wrote: | That isn't always a benefit, though. When interfacing with a | database schema that uses small integers for ids, I'm all the | time defining types like `struct CustomerId(u32)` and `struct | InvoiceId(u32)`. It's incredibly valuable that the compiler | will not let me mix up a customer and an invoice, even if both | of them are 42. | tasogare wrote: | This is why interfaces exist. | lolive wrote: | Strings, integer and boolean are enough to model the world. | Period. Ps: oh, and 640k should be enough for everyone! | kobe_bryant wrote: | I'm working on a system where the users supply equations as rules | and in a roundabout way we came across static vs dynamic typing. | | having users have to define each equation and its type (boolean, | numerical, etc) would make it simple and foolproof, but add extra | work and requires defining each equation separately when a lot of | them are just partitioning the space (imagine 30 combinations of | x, y and z) so we ended up deriving the type from how its result | is being used, but that puts the work of getting it right on the | user | rogerkirkness wrote: | Rewriting our app from Node to Go reduced cloud spend by 20x, did | not increase dev time, made it much easier to read and grow the | project past 5k lines. Literally no downside. | | If your database is typed on the bottom, and your documentation | is typed on the top, you have a type sandwich in every non-script | application. Might as well be consistent. | raverbashing wrote: | Yeah I agree most of the pain of types comes from the ways | they're implemented (especially in early C++/Java etc) | | Hence my beef with them. PyLint (as an example) can deduce and | type check your program for you. Why do I need to annotate the | types for every single thing? Python is not exactly a weakly | typed language if you go down the details. | | So if the compiler knows (or even worse in the case of Java: the | IDE knows), why do I need to tell it that? Then you end up with | the cases of several prototypes for the same function for things | that are essentially the same. | | I can understand type annotations for documenting interfaces. | Those make sense. | wutbrodo wrote: | I which there was a little more elaboration on _why_ he | originally disliked types so much. This perspective is still very | strange to me, as the value of types seems self-evident for | systems more complex than a script, and I'd like to understand it | better. As it stands now, my only assumption is that this comes | from the place the author is coming from: people who don't think | types are useful are generally coming from a place of ignorance. | | This feels facile and self-serving though ("people who disagree | with me don't know what they're talking about"), so it's more | likely that I have a blind spot. Are there any current or former | type-haters that can help shed light on their perspective? | | EDIT: I should note that my production experience is primarily | with (modern)C++ and Python, so I don't even have the benefit of | more modern static typing systems like Rust's. | edanm wrote: | I originally worked with C++. I then moved to Python and loved | it, partially because of the dynamic type system. I'm still in | the dynamic-types camp, though I haven't really learned a | language with a good static type system yet, and the little | I've learned of Haskell has made me super interested in a good | type system. | | To answer your question, the reason I prefer Python to C++, in | terms of types at least, is that I feel the type system makes | you get the order of building a program wrong. I think the | right way, in most cases, is an agile-style, prototype-first | approach. You should usually start with the simplest prototype | of something that does what you want, then slowly expand and | generalize as you learn more about the problem domain and make | product and design decisions. | | With C++, you usually start off with lots of decisions on how | your data looks, and it's usually really hard to make changes | afterwards. This forces you to make a _lot_ off the | architectural decisions when you know the least, and are fairly | locked into those decisions. | | The main benefit proposed benefit of a type system, or at least | of C++'s type system, is that the compiler can help you catch | type errors. However, these errors represent a small fraction | of possible buga - so you're going to have to write tests | anyway, and it's not hard to add type tests as well (where | relevant- the idea is that it's often less relevant than you | think!). | | Caveats to my opinion: | | 1. I haven't worked in C++ for many years, and I know that | there's been a lot of changes. Not sure how my opinion stacks | up to current cpp. | | 2. If the type system gives you more than just catching some | class of errors, I can see that it might be worth it. Again, no | real experience with anything other than Python and js in many | years. | | 3. I'm pretty sure I'm right about the correct way to build | software in terms of steps. The older waterfall approach where | everything is designed to front makes little sense in most | projects I've seen, or at least in the early phases. Possibly | something like Excel, in exchange there's very little new | product development and in which the product domain is super | well understood, has different trade offs. | | Having said that, I might be wrong about how much cpp forces | you to do things the other way. That was my experience and most | people's that I know- but that was also the common way to do | things in the past anyway. Possibly, there are good ways of | doing that kind of development in cpp today. | steveklabnik wrote: | I can't speak for Chris, but I can speak for myself. | | I did the bulk of my early programming in statically typed | languages. Specifically, late 90s C, C++, and Java. Then I | found Perl, and pretty much went into dynamically typed | languages only for the next near-decade. | | At the time, I felt like the types didn't pull their weight. | There was a _lot_ of extra writing, and you got very little | benefit for it. Plus, dynamically typed languages had these | rich features that just weren 't really accessible in | mainstream statically typed languages, and so they kind of | became associated with each other in my brain even if that | wasn't specifically true. For example, seeing stuff like | https://www.cs.ait.ac.th/~on/O/oreilly/perl/cookbook/ch11_08... | absolutely blew my mind, and I had no idea how you could do | something like this in the statically typed langauges I was | exposed to at the time. | | It wasn't until I discovered languages that had significantly | more powerful features, and had a significant amount of type | inference, that I felt the equation changed. The former to give | me something better than purely "don't make some kinds of | simple mistakes", and the latter to make it ergonomic enough. | wutbrodo wrote: | > Plus, dynamically typed languages had these rich features | that just weren't really accessible in mainstream statically | typed languages, and so they kind of became associated with | each other in my brain even if that wasn't specifically true. | | This resonates a lot with me (though as you say, isn't really | a typing thing). I work on ML systems for autonomous | vehicles, so I switch between Python and C++, and I | definitely occasionally start writing some C++ logic in an | elegant functional way and realize that C++'s boilerplate | makes it less readable than the usually-less-readable naive | construct like a loop (eg I'm surprised at how often | std::transform comes out looking terrible). | | Do you mind if I ask about the size of the systems/teams | you've worked on throughout this process? My experience with | C++ has been in large systems worked on asynchronously by | lots of engineers (with strong code review policies in | place), and my experience with Python has been everything | from moderately-sized systems down to smaller ones down to | scripting. The Python case also included work with an | inexperienced (and frankly, partially unintelligent) team of | engineers, which made the discipline imposed by typing all | the more valuable, but I have found the documentation and | structure that typing imposes on code to be invaluable in | communicating | | As I said in my other comment, I'm still concerned this is a | little facile, but having started my career on C++ systems at | Google, I wonder whether I've just set a standard so high for | the health of a system that many of those who dismiss | typing's value don't understand that those benefits are | possible (I'm certainly an order of magnitude better at | reading code than anyone my current team, but this is | confounded by the fact that everyone else has a weaker | engineering background and stronger robotics or ML domain | expertise). | | To be clear, I don't think this applies to you necessarily; | your approach of trading off the benefits of ergonomics vs | structure resonates strongly with me, and I'd imagine it's | even more the case on pre-modern statically-typed languages. | What I'm really curious about is the perspective expressed in | the OP, which, in the 2010s, doesn't even see _value_ in | types beyond perhaps negligible benefit from some compiler | checks. | | Thank you very much for the perspective! | edem wrote: | I've been using Kotlin (which is __very __similar to Typescript, | the language the author also mentions) to great effect mostly | because 2 of those points (type inference and sum types) and | something else that 's also __very __powerful and not mentioned | in the article: extension functions. | jondubois wrote: | For me it was the opposite. I started out with dynamically typed, | then switched to statically typed for many years, then I realized | that types were almost worthless (and even harmful in some cases | in terms of how they influence design/architecture) and I | switched back to dynamically typed and never looked back. | | Nothing beats good testing and good architecture design. | choeger wrote: | For me it is somewhat different: I always used typed languages | and occasionally have to read and improve untyped code. I must | admit, I feel half blind in the latter. When debugging a python | web service, trying to figure out the control or data flow, I | very often scratch my head and wonder "what _is_ this thing "? | This applies both to library functions and code from colleagues. | Sometimes it feels like untyped languages are write-only | languages. | christophilus wrote: | This is my biggest gripe, too. Discipline about commenting | helps, but only so much. | | In my case, I have Clojure spec or the JavaScript equivalents | at the important boundaries in my code. That makes this issue | much less painful and also solves a few other issues to boot. | | To be honest, in more advanced type systems, I'd ask: "What is | this" only to be shown a convoluted type signature. I then put | a println or breakpoint in there just like I would with a | dynamic language and poked around. | JackMorgan wrote: | Types are controversial because we can't measure engineer | productivity. Full stop. I see people on here arguing that they | are faster one way or the other, yet we have absolutely no | empirical evidence for such a claim. What makes it more complex | is that "fighting with types" often takes one out of the flow in | a different way than usual progamming challenges. Since it's | unmeasurable, we cannot see the effect of productivity relative | to team size, feature churn, product timeline, unit test | discipline, etc. | | For example, I'm completely convinced types add significant | productivity increase to any team of more than two programmers. | Likewise types are a net productivity improvement to software | that has to be added to multiple times in a year. If it's a one- | off write and throw away, or tiny team, it probably doesn't help | as much. Also if it's a big team, but with highly siloed | developers, likely doesn't add as much value. I think fighting | with types FEELS slower than it is, like how dealing with a car | that doesn't start immediately or a street with a lot of stop | signs feels slower than just getting out and walking, despite the | empirical evidence to the contrary. | | However, these beliefs of mine are BACKED BY ZERO EVIDENCE. Just | let that sink in. We all are arguing about a topic that | inherently cannot be measured. We should be trying to tackle the | measurement issue first, rather than just keep yelling about | chocolate vs vanilla forever. | | When all we have to go on is feelings, we get people arguing | which is better: cars or bicycles, without any discussion or | facts about top speed or total distance. The cyclist is always | going to talk about wind in their hair feeling like they are | going so fast, or how it slows them down to look for gas stations | and just stand there pumping gas when they could be making | progress pedaling. And to stretch the metaphor even further, | there are plenty of environments when a sedan is slower than a | mountain bike, and plenty where they are the same. I suspect all | this is true with types, yet we have absolutely no way to know. | For all we know, the "collective wisdom" of types might be | exactly backwards, because basing engineering decisions on | feelings rarely correlates to empirical results. | adnzzzzZ wrote: | I personally believe that productivity differences depend | mostly on the programmer's personality. Certain types of | personalities will be more productive with statically typed | systems, others with more dynamically typed ones. If this is | the case then the measurement problem becomes one of | personality assessment rather than anything else, and even when | we manage to get this measured correctly, because it's a | personality issue, we'll still have people arguing cars or | bicycles without facts being able to help at all (except to | point that it's mostly a matter of preference so the discussion | is pointless). | fulafel wrote: | I think from an empirical mindset we have to grant that the | lack of evidence probably means there is no significant | advantage. Because there have been lots of studies, we can't | simply say we don't know if there's an effect, or we don't know | how to measure it. | JackMorgan wrote: | Don't conflate the inability to measure productivity with the | conclusion that for this one issue the differences can't be | that large. That's like saying, "just because we can't | measure distance or velocity, rockets and cars probably are | similar speeds, or it depends on the driver's personality." | It's empirically unsolved how productive an engineer even is, | therefore all other discussions are 100% subjective. | | We don't know if there's an effect, because we have no way at | all to measure it without costs so staggeringly large no one | will ever try. We'd need similar engineers, doing all the | same practices, with the same stories, and just one | difference. But then the engineers would need to have been | selected to be similar skill and speed previously, which | would require doing other projects with all fixed practices | and languages to measure developer skill relative to peers. | This also doesn't take into account team cooperation and | morale, creative thinking around problem solving, etc. It's a | bummer, but without some breakthrough in AI research allowing | developers to be measured based on the code they write, it's | seeming more and more like an unsolvable problem. | dynamite-ready wrote: | Types are useful, but they are currently trending, so now, they | might often be forced into situations where they might not be | needed. | | Programming languages and their type systems are tools, at the | end of the day. | | Occasionally, an overwrought system of types will slow you down, | or quite possibly make simple changes impossible. | | On another day, some other type declaration could save you hours | of debugging, or speed up your program by orders of magnitude... | | Simply put, I feel that many Java programs probably could happily | be replaced by Python or Node JS. | | On the other hand, some safety critical UI projects (or at least, | some key portions of them) absolutely would benefit from | Typescript or Elm. | | I wish there was more discussion about when each is a better fit, | rather than talking about how much worse A is than B. | jondubois wrote: | Type hype is real. It almost seems like job-creation propaganda | at this stage. | | When I judge things, I look at practical outcomes; and the fact | is that I produce better software with more features within the | same timeframe if I use JavaScript rather than TypeScript and | the product in both cases is equally robust. This has been true | for me both independently and as part of a team. | | With JS, I can write more code and more tests within the same | amount of time and there is no drop in quality. | | I'm very surprised that nobody else seems to be experiencing | the same thing. I've been back and forth many times between the | two paradigms and for me it's clear as day. | valuearb wrote: | I switched my Javascript code base to Typescript a few months | ago. There is a productivity cost that is declining over time | as I get more used to Typescript. But the conversion also | flushed out some significant bugs in the JavaScript base. | | And I only get to work on this code once a week. So when I | come back to it I've found it's much clearer how it works and | I get productive much faster. | | I will bet any coworkers who have to work on your code wish | it was in Typescript. | rswail wrote: | Sorry anecdata isn't data. You might be able to write more | code in terms of LOC but you're also writing tests that a | strongly typed system wouldn't need. | | You don't _know_ that your code is "equally robust". You | don't know what sort of "drop in quality" you have because | you're not using strong types. | | You are making a judgement that isn't backed by anything | other than intuition. | rswail wrote: | The focus of industrial development for a long time has been | focussed on the processes, or the verbs, not the things, or the | nouns. | | Focusing on the Nouns and the types of those Nouns lets you | understand what states they can be in and more importantly, the | ones they cannot be in. By using types as much as possible to | express those invariants and constraints, you can make sure | that the processes don't do something that they shouldn't. | | Proving software correct is a very complicated and, as yet, | unsolved in most practical cases. Strong typing and strong type | systems are a step in that direction. | | If a compiler ensures that a sum type's possible values are | always exhaustively matched, then you can be sure that the | processing at least _considers_ all of the possible values. | | That leads to positive results when programming. | | If you use Option types and then ensure that the None type is | handled, the possiblity of nulls is dramatically reduced, if | not eliminated. | | If immutability is enforced, that removes the possibility of | inadvertent modification, especially somewhere deep in a call | stack. That leads to safer concurrency, which is an absolute | requirement for using the compute capacity to its fullest | extent. | | Strong typing like this isn't "overwrought", it's making sure | that your code doesn't do something to a thing that it | shouldn't do. | fizixer wrote: | I was wrong about types too. I used to think after I had mastered | half a dozen languages, I should start spending time on typed | languages, maybe eventually learning type theory, Haskell and | what not. | | Now I focus on things that matter: deep learning, AI, and | robotics. | chousuke wrote: | For my use cases, the vast majority of errors with dynamic | languages boil down to being able to run scripts that have | undefined variables; I'm prone to (mental) typos, so if I had a | dialect of Python that failed before runtime when undeclared | references exist, I could probably cut the number of iterations I | need to arrive at a working script by at least half. | | I've never found a valid use case for allowing undefined | references, though I suppose it does make the language | implementation significantly easier. | | Writing tests is not really a solution either. Tests are also | code and suffer from the same problem. | christophilus wrote: | At least in JavaScript, these are flagged by a linter. I | imagine Python has a decent linter and code editor integration | of said linter? | petters wrote: | pyflakes finds these. It has virtually 0% false positives and | runs extremely quickly. | | Run it on save or in a git commit hook. | | (I agree that they should be a syntax error though) | samanator wrote: | Would be very difficult (or impossible) to make this a syntax | error since | | exec() | | Modifies the global scope | samanator wrote: | Pycharm warns you when that happens. I'm guessing they are | using a combination of the abc module (to parse the abstract | syntax tree) and some linter that can be found on pypi. | | Would a validator that runs before your main python executable | solve your problem? | samanator wrote: | Oops I meant the ast module, not the abc module | knocte wrote: | Just make the jump for once: | https://github.com/knocte/2fsharp/blob/master/python2fsharp.... | (yes you can write scripts with this language). | chousuke wrote: | I most often choose Python because it's what's available, and | it's good enough for the things I need to use it for. Other | people can generally understand my code, too. | | F# probably is a good language, but I haven't had a good | enough reason to use it. | diminish wrote: | To play against the current wave, and give a contrarian pov: | | I'm grown up with statically typed languages from C/C++/C#/Java | and later and didn't know about dynamic languages till recently. | | 1. Which types are we talking about? | | - In earlier static-typed language, the processor-based types | `uint64` looked very strict, optimized the code for hardware | architecture. | | - Having mathematically and ontological correct types is one | approach `integer`/`float`/`string`/`datetime`. | | - Each type being well defined and each object-class being used | as type is another strict approach. | | Look at the types of Rust and Kotlin and see the cacophony of | decisions made: | | - Rust https://doc.rust-lang.org/reference/types.html | | - Kotlin https://kotlinlang.org/docs/reference/basic-types.html | | 2. When you want to implement a method which applies to multiple | libraries, you end up writing `fooString`, `fooInteger`, | `fooDatetime` etc (or foo(String), foo(Integer)) etc. DRYing is | too hard. So you end up writing 10x more methods | | 3. No you don't avoid `null` checks at all. | | 4. Generalizations are harder to implement. | | 5. Interfaces are uglier compared to dynamic languages. | | 6. The code becomes less readable, not concise and verbose | | 7. Every new and old typed language is less elegant compared to | the dynamic. Look at typescript vs javascript, typescript code is | ugly, verbose, non-readable at all. | inertiatic wrote: | >2. When you want to implement a method which applies to | multiple libraries, you end up writing `fooString`, | `fooInteger`, `fooDatetime` etc. DRYing is too hard. So you end | up writing 10x more methods | | As discussed in the article, you want a sum type here. | | >3. No you don't avoid `null` checks at all. | | Entirely language dependent. | | >4. Generalizations are harder to implement. | | For some definition of harder. Harder to implement, causing you | to think harder about what should be allowed under these | generalizations, leading to less bugs, leading to things | being... easier to actually implement in the long run. | | >6. The code becomes less readable, not concise and verbose. | | Since you only recently got into dynamic languages, just wait | till you try to get into a large codebase without types. Where | everything basically devolves into containers of arbitrary | things that you don't know what they contain until you've gone | through the thing with a debugger. | | Maybe your day job becomes more interesting that way. Making | CRUD apps is admittedly boring, but this isn't in my opinion | the way to keep one's self on their feet. | joppy wrote: | How many languages have true sum or union types though? For | example, in Haskell I can't declare a function as (foo: | (String | Int) -> Int) and then call (foo "bar") or (foo | 123), I need to create some kind of wrapper type (like | Either) to contain the possibility of a String or Int. | | If this were possible, there would not be such a | proliferation of useless types throughout code, and code | could be updated incrementally much more easily, since if I | want to add the possibility of another type in a functions | inputs, I don't have to go and update every callsite. | dddbbb wrote: | Haskell's Either is a 'true' sum type, it corresponds | exactly to the way sums have been defined in the literature | for decades, and also is the Curry-Howard representation of | logical or. It necessarily must be inside a Either-like | wrapper in order to be type safe, String and Int are | ultimately different and so at some point we must | discriminate between them. foo could call further functions | with its argument inside itself accepting Either String | Int, but eventually a destructor will be hit somewhere in | the call stack. | | If String and Int do implement the same behaviour under | some circumstance, then a typeclass should be used to | define that behaviour. Then foo can have type (Bar baz) => | baz -> Int, where String and Int have Bar instances. | mplanchard wrote: | I agree with my sibling comment regarding Haskell, but your | example essentially as written is possible in typescript. | Python's type system also allows it via the Union type, | e.g. Union[str, int] | ohgodplsno wrote: | Dynamic languages don't do away with such types, especially | uint/float, etc. They just hide it away from you. The only | thing it gives you is false confidence, where one day you end | up doing operations on two floats because your untyped function | is supposed to get numbers in, and you're left with | 36.99999999999994$ on your bank account because it wasn't | explicit. | | So, you write unit tests to make sure that you only pass the | rights types when calling it. Doing a very bad job at just | being a bad compiler. | | Now, I'll agree, the languages you mentioned are absolutely | horrible with types. Types in C are basically nothing more than | suggestions because you end up casting const away while | laughing, C++ has std::types<template typed<std::frobnicate<T, | A, R>>>, C# and Java in the past were excessively verbose and | required you to type every single thing. Java/C#/C++ finally | got a bit better with that with auto/var. | | Kotlin types and type inference are absolutely fantastic and | make your code clearer. | | 2/ No, you don't write fooString, fooInteger, etc. You write | foo(value: Int), foo(value: String) and let the type system | resolve and tell you which ones are available, rather than | relying on documentation and runtime type checkings that are | plain bad. And if it makes sense, you can even declare them as | extension functions, Int.foo() | | 3/ Yes you do. Unless you explicitly opt in to null values or | you're using platform types, like calling code from Java with | @Nullable/@NotNull annotation | | 4/ Nobody calls for generalizations all the time. You're not | supposed to write generics for all your methods. | | 5/ Explicit definitions are uglier compared to duck typing and | just going "yolo it has a .frobnicate() method that means I can | call it" ? I agree that over time, they might become unyieldy, | but once again, with a proper typechecker and type inference, | they're a blessing. Using kotlin and writing | when(this) { is Frobnicator -> this::frobnicate | is Zobtrinatex -> this::zobritnex else -> | this@caller::defaultBehavior }.invoke() | | gives you safety, checks that you're not mixing return types. | | 6, 7/ Depends. I've written some true abominations, yes. | Typecript gets ugly when the libraries you're using abuse types | horribly (React used to be absolute trash for that, declaring | props was terrible). But then, you pay the cost once (at | declaration), and get benefits through your entire app. | | Don't write generics for a component, unless it makes sense. If | you're exposing a Container<T> and you can access the data | inside and you require its type, okay, it makes sense. But | don't do a Page<T>, that's dumb. | | Generics are a tool. Like C tells you to not abuse (void*), | don't abuse generics. Use them with parcimony and your code | will be better, give you more guarantess and literally write | itself if you've got a competent IDE. | GlennS wrote: | In Python you'd usually use a `Decimal` for money and it | would give you an error if you tried to mix it with floats, | not silently throw away information (which I certainly agree | would be a dire consequence). | | In JavaScript, good luck for now, but a solution is being | rolled out: [https://caniuse.com/bigint]. | diminish wrote: | Thanks @ohgodplsno for the detailed and thoughtful response. | | One final point is that with statically typed languages you | sometimes fight with the language to deliver. The language | and libraries becomes a maze of problems. Developer | productivity and sense of accomplishment is low. | | I sometimes look at my code when I wrote a matlab clone in | C++, It seems like half of the code is things which I wrote | to overcome or augment the language and the libraries. | AlexSW wrote: | I'm not following your answer to 2, do you have an example? | ohgodplsno wrote: | Say you'd like to have a foo method that returns... a | number, whatever. | | You do not need to have multiple symbols "fooString", | "fooDecimal", "fooString", etc. Simply overloading the | parameters gives you type safety, and keeps a single foo | symbol. fun foo(value: String): Int = | value.length() fun foo(value: Int) = value | fun foo(value: RemoteDatabase) = value.servers.map { | it.connect().executeSql("SELECT 1")[0].toInt() }.sum() | | All these define a foo method, that returns an Int. The | untyped alternative (what javascript, python, etc do in a | naive way, without trying to duck type) is to do this: | fun foo(value: Any) = when (value) { is Int -> | value is String -> value.length() | is RemoteDatabase -> value.servers.map { ... }.sum() | else -> error("Welp, our type system couldn't help us | there.") } | | Additionally, if it really makes sense, you can define foo | as an extension function on the type: fun | Int.foo() = this fun String.foo() = length() | fun RemoteDatabase.foo() = servers.map { ... }.sum() | | You can then use it directly on the type, rather than call | foo(1): val fooResult = 1.foo() val | fooStringResult = "Well hello there".foo() | mplanchard wrote: | > 2. When you want to implement a method which applies to | multiple libraries, you end up writing `fooString`, | `fooInteger`, `fooDatetime` etc (or foo(String), foo(Integer)) | etc. DRYing is too hard. So you end up writing 10x more methods | | This is only a problem in typed languages without generics | (like go) or without sum types (like go). With generics, the | compiler takes care of writing fooInteger, fooDatetime, etc. | With sum types, the compiler verifies that you've handled all | of the cases in your foo function. | mlthoughts2018 wrote: | This is the opposite of my progression. I worked professionally | with Haskell for 6 years and then Scala for 2 years at the early | stages of my career. | | Static typing is a waste of time that does not facilitate better | design, better clarity or safer code. The classes of errors | detectable and preventable with static typing are almost never | very important, and in dynamic languages you can achieve the same | results with lightweight unit tests that are no more effort, and | often much less effort, than creating or maintaining type system | designs. | | Type annotations are code and more code is just more liability | and more complexity. It really is just that simple. You should | strive to write code with the fewest cognitive concepts | necessary. Cognitive concepts are the hugest form of tech debt | you can have in code, much much worse than repeated code, lack of | modularity, lack of test coverage and so forth. Type system | design invites running amok not just with all kinds of new | cognitive concepts to model every domain problem, but also the | code authors are encouraged to use "creativity" when developing | these concepts, which leads to poorly factored messes that | express the limited view of a set of specific people - often with | huge premature abstraction that makes the code excessively | brittle (even when it's claimed to be extensible) and poorly | suited to accommodate use case changes or requirements changes. | | For the past 5 years I've moved on to exclusively using the | Python ecosystem for system designs. It's nice since I work in | machine learning and no ecosystem even comes close to Python in | terms of ML tooling, but even for backend systems and web service | designs around the ML product my team creates, using Python has | been pure joy in terms of easy development cycles, easy ability | to write less code, easy ability to achieve high safety and | reliability, easy ability to trade off safety as a resource to | accommodate sudden business requirement changes. | | I can sincerely say across the board programming large backend | systems in Python has been more pleasant, easier to design, read, | understand and maintain, easier to extend, and led to safer, more | reliable delivered solutions than any of the projects I worked on | using Haskell or Scala (even when working with those languages in | mature organizations that had very seasoned, veteran functional | programming experts establishing in-house development practices). | | Now that I've transitioned through senior & staff engineer and | became an engineering manager, my perspective over the years has | come to endorse dynamically typed languages as vastly superior | tools. | | My experiences with Haskell and Scala just lead me to believe | Python is strictly better for large system design. | dpc_pw wrote: | Let me guess. You probably worked in small teams, mostly with | code you co-wrote your whole carrier. | | You literary compare 3 languages that you happened to use and | think that all there is to know about this argument. | adnzzzzZ wrote: | I don't really see most of my code being improved that much by | types and I also see a lot of extra complexity that people in the | sphere I am (indie games) have to deal with when using typed | languages. It just doesn't seem worth it. When you have to spend | a lot of time fighting your language, and handling numerous extra | concepts that don't exist in an untyped language because they | don't need to, it feels like the people who swear by typed | languages that they simply like creating extra work for | themselves as a way to avoid doing what's actually needed to be | done, just because they want to feel productive. | | I also suspect that a lot of this has to do with people's | personalities around the concept of borders. Some people like | well defined borders in general in everything they do because | they approach life from a more procedural perspective, and for | procedures to work they need things to be in the right boxes and | in the right places. While others prefer borders to be undefined | and more free-flowing because more information can pass through | concepts and that allows for a more unstructured design process. | It only puzzles me that there's so much energy in indie game | development for highly bordered programming environments (i.e. | all the energy being put into gamedev Rust libraries) when indie | developers tend to be people who value borders less, as do all | creative types. But I guess people really like types... | [deleted] | jondubois wrote: | I agree. I think the downside of static typing is that it | encourages developers to pass around complex types between | functions instead of simple types and I think this is a | mistake. | | If you have the option between creating a function which | accepts a string (e.g. ID) as argument or accepts an instance | of type SomeType, it's better to pass a string because simple | types such as strings are pass-by-value so it protects your | code from unpredictable mutations (which is probably the single | biggest, hardest to identify and hardest to fix problem in | software development). I think OOP gets a lot of blame for this | and it's why a lot of people have been promoting functional | programming but this blame is misguided; the problem is complex | function interfaces which encourage pass-by-reference and then | hide mutations which occur inside the blackbox, not mutations | themselves. Mutations within a whitebox (e.g. a for-loop) are | perfectly fine since they're easy to spot and happen in a | single central place. | | If you adopt a philosophy of passing the simplest types | possible, then you will not run into these kinds of mutation | problems which are the biggest source of pain for software | developers. Also you will not run into argument type mismatch | issues because you will be dealing with a very small range of | possible types. | | Note that this problem of trying to pass simple types requires | an architectural solution and well thought-out state management | within components; it cannot be solved through more advanced | tooling. More advanced tooling (and types) just let you get | away with making spaghetti code more manageable; but if what | you have is spaghetti code then problems will rear their ugly | heads again sooner or later. | | For example, a lot of developers in the React community already | kind of figured this out when they started cloning objects | passed to and returned from any function call; returning copies | of some plain objects instead of instances by-reference | provided protection from such unexpected mutations. I'm sure | that's why a lot of people in the React community are still | kind of resistant to TypeScript; they've already figured out | what the real culpit is. Some of them may have switched to TS | out of peer pressure, but I'm sure many have had doubts and | their intuition was right. | nicoburns wrote: | In many langauges it is possible to have complex types that | are pass-by-value. Rust also completely solves the mutation | issues with pass-by-reference by putting the mutability of | references in the function signatures and only allowing one | mutable reference at a time. | jondubois wrote: | But still, I think it does not fully solve the | architectural issue or encourage good architecture (though | it can certainly help reduce bugs)... In this case you may | end up with lots of duplicate instances in different | blackboxes which may not be a good thing either. | | The point of good state management is to ensure that each | instance has a single home. As soon as you start passing | instances between functions/modules/components, you're | leaking abstractions between different components. | Sometimes it is appropriate to do this, but most of the | time it's dangerous. Components should aim to communicate | as little information about their internal state to other | components as possible. | viraptor wrote: | It really depends on the language. Some still help you | use this case in an amazing way. For example rust will | allow you to create an enum which can be a FooId(&str). | (Or add extra 4 lines to get an owned String that's | immutable) | | Now you've got an immutable id string, you can access as | easily as the bare one, but now you can't mix it with | other types of IDs, so you won't pass it to something | expecting BarId by accident. As a result - no black boxes | and a clearer design. | | A variant of this is the cause of many Linux kernel | issues. They basically had to cram it into macros to | prevent passing real/kernel pointers to userspace by | accident, because pointer is a pointer is a pointer. | willtim wrote: | If you use String for all your data types than you are no | better than a dynamic language. There are many string like | things that benefit from their own types, e.g. currency, | identifiers, post codes. Such types should only be created | from a parse of valid strings, i.e. no empty strings, | whitespace, illegal values etc. They do not have to be Alan | Kay "objects", despite what your language or thought | leadership is telling you. They should be values with value- | based equality. A modern statically typed language should let | you define such a type in a few lines. This is all done in | order to make illegal states unrepresentable, which is what | type systems are for. | alkonaut wrote: | The correct thing is imho to pass an immutable SomeType or an | interface that only exposes the parts of SomeType necessary | for the calculation and doesn't allow mutation of the object. | | Of course you don't send around references to mutable objects | and of course you only send to a function just what it needs | - but that's regardless of type system. | jondubois wrote: | Sometimes this will be the best approach possible but | adhering with this principle too strongly can | overcomplicate the general design/architecture - It can | give developers a green light to start passing around | complex types all over the place and harms the separation | of concerns principle. | | In terms of modularity and testability, the ideal | architecture is when components communicate with each other | in the simplest language (interface) possible. Otherwise | you become too reliant on mocks during testing (which add | brittleness and require more frequent test updates). I | think very often, static typing can cause developers to | become distracted from what is truly important; | architecture and design philosophy. I think this is the | core idea that Alan Kay (one of the inventors of OOP) has | been trying to get across. | | 'I'm sorry that I long ago coined the term "objects" for | this topic because it gets many people to focus on the | lesser idea. The big idea is "messaging"' - Alan Kay | | It's very clear from Alan Kay's writings that when he was | talking about 'messaging' he was talking about | communication between components and he did not intend for | objects to be used in the place of messages. | [deleted] | loup-vaillant wrote: | Abandoning a feature just because it enables a misuse is | the wrong way to do it in my opinion. Yes, some | inexperienced, stubborn, stupid, or hurried developers | will pass around complex types when they really | shouldn't. But no, this drawback does not nullify the | _massive_ advantages of (good) static typing. | | Sure, interfaces should be kept small. Let's to just | that, then! Recognise that we want our | classes/functions/modules to be deep (small | interface/implementation ratio), and frown upon shallow | instances in code reviews. | | No need to give up static typing. | marcosdumay wrote: | You seem to have experience only on languages where strings | are immutable, and objects are always mutable. Those | properties are not universal. | | And yet, despite correctly assessing the problem, you insist | on fighting objects instead of mutability. | valuearb wrote: | In Swift Structs are immutable and passed by value, class | objects are mutable and passed by reference. | | Using a struct is infinitely preferable to a string. | sideshowb wrote: | Maybe it's a case of problem complexity. I think of types as a | tool to help cope with certain things. If you're building a | garden wall you probably don't need CAD. For a 747, you | probably do. Though aircraft predate CAD so it's clearly not | impossible to do without. | | I dislike types in 30 lines of python because they're | unnecessary complexity. I like them in 10000 lines of c++ | because they do some of the thinking on my behalf. | i6ruce wrote: | > I dislike types in 30 lines of python They are already | there you just don't want to acknowledge them. You can build | the same prototype in strictly typed language just by | sticking to some primitive types like int/string and type | inference, and the progress toward something more complex as | your prototype grows. I personally prefer to use types right | away, so type system can guide me further and show me when | I'm assuming something in a wrong way. | awild wrote: | What languages have you worked with? As per the OP, there are | certain languages where types are much more expressive and add | something to the programming experience. | | Learning haskell changed a lot about the way I think about | programs and that is even as someone who primarily writes in | java. | samhain wrote: | Aren't you falling into the same trap that the post explains? | That there are nuances around when types are useful and not | useful? An indie game developer might spend a lot of time | designing methods of gameplay and artwork for the game, but | that doesn't mean that types should go out the window for all | levels of programming. | | Wouldn't it be better to approach this by which problem we are | trying to solve? A script that is run during development, where | resources are unconstrained, and stability is not an issue, | should absolutely value the time it takes to develop and | maintain the script. So using a typed language may not be | useful here. A situation where small optimizations make large | improvements might benefit from a typed language, maybe a | physics engine of a game intended for multiple platforms? | | In the OP, the author approaches it from a "systems" | perspective, that when you need either of the 3 scenarios, then | you might consider using types. Type inference, Sum/tagged | union types, and Soundness, which I think could easily apply to | certain areas of game development. Ignoring the nuance around | the issue, and being dogmatic that all scenarios in a given | field do not need types is ignoring that what we're really | doing is writing in languages that need to be interpreted by | both humans and machines. | darkerside wrote: | This seems likely. It took me embarrassingly long to realize | the fact that you can't understand the benefit of a feature | you don't understand. Seems like an obvious tautology, but | it's one I fell for over and over, and one I think | grandparent is guilty of here. | loup-vaillant wrote: | Statically typed languages (or at least the good ones) are | disciplined so the programmer doesn't have to. | | If you can work reliably with dynamic typing, that means you | are very disciplined about giving the right data to the right | function, in exactly the right form. That you are very | disciplined about tests, possibly including fairly stupid- | looking unit tests (which aren't _actually_ stupid, at least in | a dynamic context). Adding static typing on top of that wouldn | 't help much of course. | | When I write something from scratch however, I found that | static typing actually _speeds up_ my development. It 's _less_ | work, not more. Because I don 't have to write as many tests, | or even _worry_ about huge classes of errors -- the compiler | (and if I 'm lucky, my editor/IDE) just checks them for me. | | I don't know the work you do, but I bet that your style could | benefit from some static checks. Perhaps not the mainstream | ones, but your scripts _work_ somehow, don 't they? That mean | they respect a number of invariants, some of which could | certainly be checked at compile time, saving you significant | time on stupid bugs. The result won't be TypeScript or Rust, | but I don't think it would be fully dynamically typed either. | TeMPOraL wrote: | I can second the experience. I write a lot of Common Lisp, | and these days it's typed Common Lisp for me. It adds very | little overhead in terms of code writing speed, but | continuously stops me from making stupid mistakes (like e.g. | forgetting a function I'm calling returns a sequence and | treating it as a scalar value). My comfort of writing is much | better, because I spend less time in interactive debugger | hunting my own typos. | williamdclt wrote: | > I found that static typing actually speeds up my | development. It's less work, not more. | | It's a point that comes back often, and that I totally agree | with so it's worth reiterating. In addition to the improved | dev tooling (autocompletion, hinting, refactoring), being | able to write large swathes of code without actually running | it and being 100% confident that it's all _valid_ (not bug- | free of course) just takes a huge load off my mind. | | Of course, there's huge differences between languages like | Java and languages like Typescript. Talking about "typed | languages" as a homogenous concept often doesn't make a lot | of sense | pansa2 wrote: | > being able to write large swathes of code without | actually running it and being 100% confident that it's all | _valid_ (not bug-free of course) just takes a huge load off | my mind. | | I've heard similar things before, e.g. "static typing | allows you to find bugs in your code without even running | it". | | Perhaps the reason I'm a fan of dynamically-typed languages | is that I don't see the benefit of this. Maybe my workflow | is unusual, but I don't write code without running it - I | run tests every time I add a few lines. | loup-vaillant wrote: | OCaml has a REPL. I use it all the time to check that a | new function I just wrote is correct. Yet I still get | huge benefits from the static typing: many of my errors | are stupid type errors, and having the type checker tell | me about them, rather than a runtime error (or worse, a | wrong result), makes early prototyping much faster. | | Even if I already have a REPL. I believe the main reason | is because the type checker is much closer to the source | of my errors than runtime checks or tests are. | adnzzzzZ wrote: | I think the same. This is especially true for games where | you're absolutely running the game again for everything | you change, and in case anything is wrong it's generally | very obvious visually. | asiachick wrote: | how many times have you not used bash/dos/pwsh scripts | because it's not typed? | | Maybe there's a place for both? | loup-vaillant wrote: | I can say what I _do_ use Bash for: create files & | directories, and simple string replacements in files. | Anything more involved goes into a proper program. Usually | OCaml, though I can fall back to more mainstream languages | (Python, C) if I need a wide audience to be able to read | it, _and_ the program is simple enough that types aren 't | really a problem to begin with. | Aeolun wrote: | Javascript is for scripts that fit in one file. Anything | else is typescripy. | orwin wrote: | Yes. Try prototyping for a quick POC/casual demo with | javascript, then try with typescript. If you get back to your | demo two month later (or have one other person to explain | your code to), typescript is Infinitely superior. | | For quick hacks types are not useful though. | i6ruce wrote: | Unless you're quite experienced with them and use as a | thinking tool and not as burden. | sammorrowdrums wrote: | I think it depends on size or codebase, how many people are | working on things and how long you can afford to develop before | releasing. | | I do mostly work with python and JS, but last Christmas I | learned Rust, and it strongly occurred to me that exhaustive | matching, no nulls, borrow checking and strong type inference | would be a real boost to development given the initial time to | build the codebase up. I'd put money on them removing hours of | hunting subtle bugs, and on missing the ramifications of | refactors. | | I built some small scale game stuff using SDL2 for Advent of | Code and I enjoyed rust for doing that a lot. | | I think also dynamic languages work best when developers | actually are knowledgeable about the underlying types and | effectively write code in a typed manner anyway. It's a much | worse trade-off when function signatures actually avail of | loose typing to do strange things. | cel1ne wrote: | Types are important and necessary. | | Can you skip them in a typed language? Yes, just use any, Object | or whatever the equivalent. | | Can you add them to an untyped language? No. | | They are not needed anywhere. But I argue that especially | JavaScript module-systems would have benefited greatly from them. | | A million lost hours in fixing obscure "undefined is not a | function"-errors from output of highly dynamic pluggable | build/transpiler-systems like webpack, requirejs, babel, buck | etc. could have been avoided. | nlitened wrote: | JavaScript is not untyped, it's dynamically typed. The types | are still there, they are just not enforced at compile time. | Ericson2314 wrote: | No "dynamically typed" is an oxymoron, and untyped or | "unityped" is correct. | | Please read | https://existentialtype.wordpress.com/2011/03/19/dynamic- | lan..., the classic take-down of this mistake. | fmakunbound wrote: | Static types are great for Fungeable Programmer at Big Co. where | you can be dropped into a project and it sort of gives you a way | to figure out what's present in the code. | | However, if you need to get somewhere fast, be it a side project | where you've only got so much time due to other constraints in | life, or it's a product you're trying to get to market first with | a small team, then you're not going to benefit from ossifying | statically typed languages - you're going to reach for something | that's interactive, powerful, and not bogged down in | edit/compile/test cycles. You're going to instead reach for | something like Common Lisp, Ruby, Python etc. | | Once you're making $COMFY_DOLLARS_PER_MONTH you can then pass it | off to some re-write team, though. | hohohmm wrote: | The benefits of types is not something to be discovered, but | something that's taught in school with very convincing arguments, | if not too much zeal. It's interesting to see this kind of post. | Not to be sarcastic about the late discovery of typed goodness, | but the fact that this is not already a concensus in the | engineering world. | christophilus wrote: | It's not a consensus because many of us have found that types | are not the panacea we were taught. Erlang is a fantastic | language, and it's arguable that types would not improve it, | but rather hinder some of its better features. Same is true for | Smalltalk and various lisps. I think there's a place for | various approaches to types. | orwin wrote: | All lisps I used could be statically typed, I think? Does | elisp used typep? | | For small projects, hacks or emacs macros types are not | useful but the possibility of adding types is there. | | [Edit:and it's useful when your finite state machine grows to | much] | aazaa wrote: | > Type inference: because having to write out every type, however | obvious, is an incredible waste of time. Person me = new | Person(); is ridiculous. let me = new Person(); may seem like a | small improvement, but spread over the body of an entire program | and generalized to all sorts of contexts means that type | annotations become a tool you employ because they're useful -- | for communicating to others, or for constraining the program in | particular ways -- rather than merely because the compiler yells | at you about something it should know perfectly well. | | Type inference was a major revelation to me as well in Rust. I | was reluctant to learn the language because of my experience in | Java with its high ceremony everywhere, mostly due to lack of | type inference. | | The _first_ thing I noticed with Rust was type inference. It | gives the entire language a distinctly high-level, almost | scripting language feel - modulo ownership. | jenscow wrote: | Personally, I prefer the type names to be at the start of the | line, so I don't need to scan to the end of the line (or guess | based on a function name). | | But I agree, it's a waste to type it all out. So I use an | intelligent IDE that reduces the repetitive typing: | | So typing `Person.var` gives me `Person person = new Person();` | or typing `Person person = ` will suggest `new Person()` | | Sure, it doesn't look as appealing when creating an new object, | however I get a better information when you've written | something like: Person me = | something.GetOwner(); | | rather than: let me = something.GetOwner(); | mannykannot wrote: | I think you are making a good case for a somewhat different | point: we have long reached the point where the tools we use | for writing and reading programs can give us all sorts of | semantic information on demand, and language syntax design | should take advantage of the fact that everything the | programmer needs to know need not be forced into just one | flat page-of-text view of the code. | | Alan Kay made essentially this suggestion when the | requirements for the language that became Ada were being | debated, but it was not taken up at that time. | jenscow wrote: | But that doesn't mean hide everything from view just | because it can be accessed via a keyboard short-cut or | mouse movement - my eyes are faster and easier to move. | Also, code isn't read exclusively in an IDE, nor the same | IDE that I use. | II2II wrote: | In the case of Java you have the option of using "Person me" | or "let me". Simply stick to the convention of explicitly | stating the type at least once when declaring a variable. I | have also noticed that Python allows developers to note the | type (at least when defining functions). It's not necessary | and I don't think the language uses it, but at least it | offers a standard mechanism to note it in the code. | lights0123 wrote: | All the major Rust IDEs (VS Code, IntelliJ, and probably Vim | + others) show the variable type inline when you don't | provide it explicitly: https://i.imgur.com/96Wfukl.png | | The white text on a gray background is provided by my IDE. | svieira wrote: | Once you get to Java 11+ you get `var`, so you can spell it | `var me = new Person()`. | mgkimsal wrote: | Was recently on a project that just migrated to java 11 (mid | august of this year) from java 8. | | Someone has started to try to introduce 'var' and is getting | push back from others. "it doesn't match current style" and | "could be confusing" were some 'concerns'. | | When you're trying to do | InternalFormatParserCustomForClientABCDEF | customerFormatParser = new | InternalFormatParserCustomForClientABCDEF(param1, param2); | | you really start to hit readability limits, and formatting | things like going over nominal line lengths and such. | var customerFormatParser = new | InternalFormatParserCustomForClientABCDEF(param1, param2); | | is certainly easier on the eyes, and the compiler can still | known and infer what type 'customerFormatParser' is. | aembleton wrote: | You might enjoy Kotlin. Type inference, with full compatibility | with Java libraries and ecosystem. | | Similar to Rust, you can just write `val me = Person()`. No | need for the new keyword. | eru wrote: | Though keep in mind that not all type inference is created | equal. | | Ie what you find in Haskell or OCaml is much more powerful | than what eg Go gives you. (In Go type inference only works | 'forward'.) | | (I don't know enough about Kotlin to know what kind of type | inference it has.) | edem wrote: | I love Kotlin. I also love Typescript. I'm yet to try Rust, | but I'm positive that I'll love it as well. They are all born | to solve the same problem I think, only from a different | perspective. I use Kotlin every day and the more I learn the | happier I am. I'd never go back to Java if I can help it. | shortercode wrote: | Interestingly quite similar to my own progression. I think | working in modern typed languages ( Rust, Swift, Typescript ) is | what primarily changed my viewpoint in a way that C and Java just | couldn't. | | In the last year I've transitioned from full time JS development | to full time TS development and have been pleasantly surprised | how easy the transition was for my personal projects. Previously | I had been quite adamantly against TypeScript; seeing the type | system as an unnecessary level of complexity given I already | structured my code in quite strict ways. In most it helped me | find 1 or 2 small errors, but it certainly helps reduce the | amount of time I spend verifying the behaviour of code. It | definitely has its flaws, but most of them are linked to its | compatibility with JS and I don't think they can be resolved | unfortunately. | nlitened wrote: | It looks to me as if people who started with dynamic types | discover static types after many years, and vice versa. | | There's a zen koan about it, I am sure. | dustingetz wrote: | Hate on types seems to stem from three perspectives I can see: | | 1. 2000 era Java-likes cause superlinear class complexity | explosion | | 2. 2010-era typed functional programming - Scala stdlib type sigs | that didn't fit in a tweet, 7 scala stdlib rewrites and churn | related to really figuring out how to even do functional | programming in applied domains like crud apps | | 3. All along, cutting edge comp sci research being presented as | the "correct" way to program and you're a normie drooler if you | haven't read yesterday's paper and rewritten your stuff onto it | echelon wrote: | I followed a similar trajectory. Types were the bane of my early | career. Hideous, extraneous. | | But really, they're the light at the end of the tunnel once | you've worked your way though the dynamic / weak typing | minefield. It took me a lot of Python, Javascript, and Ruby for | me to get there, but now I'm way more comfortable on the other | side. | | The correct type system is actually way more expressive than not | having strong static types. Sum types let you combine multiple | return types elegantly and not be sloppy. Option types remind you | to check for an absent value. | | Static types let you refactor and jump to definition quickly and | with confidence. | | Your interfaces become concrete and don't erode with the sifting | sands of change. As an added bonus, you don't need to | precondition check your functions for type. | | Types are organizational. Records, transactional details, | context. You can bundle things sensibly rather than put them in a | mysterious grab bag untyped dictionary or map. | | Types help literate programming. You'll find yourself writing | fewer comments as the types naturally help document the code. | They're way more concrete than comments, too. | | With types, bad code often won't compile. Catching bugs early | saves so much time. | | Types are powerful. It's worth the 3% of extra cognitive load and | pays dividends in the long haul. Before long you'll be writing | types with minimal effort. | NicoJuicy wrote: | The incoherent thinking I see with some people even in typed | systems, would make me very scared to let them do the same in | dynamic ones. | ooobit2 wrote: | Nothing good comes easy. The dozens of hours I'd spend | staring at 26 lines in R just trying different ideas to | shorten/optimize/improve clarity, and that wasn't something I | needed to sell that someone else would depend on for business | or personal use. | | But I can relate to the pressure to deliver quick results. I | found myself burnt out when working on a forecast model | around three years ago. The constant "how's it goin'?" tore | my attention away from the work, and I'm still convinced I | could have delivered a better result. | | So, in a way, I agree. In another, I understand the other | side of the issue, and I think there are so many less time- | intensive tasks going on around engineering that there's | often little awareness that something like refactoring a | class for better efficiency pays in smaller but compounding | ways long-term, with most of the time cost and perceived | opportunity cost being immediate and short-term. It's still | worth it if you really do the math on the long-term benefit. | eru wrote: | Sum types even work when you actually have the multiple of the | same return types. (Ie in Haskell `Either String String` works | just as well as `Either String Int`; the types don't have to be | distinctive.) | karmakaze wrote: | That follows from the definition being tagged 'Left'/'Right': | Either a b = Left a | Right b | masklinn wrote: | > But really, they're the light at the end of the tunnel once | you've worked your way though the dynamic / weak typing | minefield. It took me a lot of Python, Javascript, and Ruby for | me to get there, but now I'm way more comfortable on the other | side. | | To me that was not the issue. It was, rather, discovering | languages with powerful and expressive type systems. | | My first job was in Java, most of my career afterwards was in | Python. I've been type-curious for a while because of Haskell | and OCaml and am _very_ fond of Rust, I 'd take a job in any of | those happily. | | Types in Java are still, today, largely verbose, hideous and | extraneous. The cost / benefit is extremely low (or rather | extremely high, you pay a very high cost for limited benefit, | and the cost generally increases faster than the benefits). You | _can_ leverage types heavily, but it creates a codebase which | is ridiculously verbose, inefficient (because every type is | boxed), opaque, and simply doesn 't look like any other Java | codebase so will be very much disliked by most of the people | you're working with. And the benefits from that will still, at | the end of the day, be rather limited. | TeMPOraL wrote: | Same here! I've started with C++ and Java, learned to hate | excessive typing, went through a long period of dynamic typing, | and now I'm at the point you and the the author are. | | I still code a lot of Common Lisp on the side, but my Lisp code | now looks entirely different than it looked just 3 years ago. | The language standard does support optional typing | declarations, and there's an implementation (SBCL) that makes | use of it to both optimize code and provide some static | typechecking at compile time (with type inference). So my Lisp | code now is exploiting this, and is littered with type | declarations. | | However, the CL type system is very much lacking compared to | Rust or Haskell. I'm hoping one day someone will make a | statically, strongly typed Lisp that still doesn't sacrifice | its flexibility and expressive power. I'd jump to that in an | instant. | stevelosh wrote: | > However, the CL type system is very much lacking compared | to Rust or Haskell. I'm hoping one day someone will make a | statically, strongly typed Lisp that still doesn't sacrifice | its flexibility and expressive power. I'd jump to that in an | instant. | | https://github.com/stylewarning/coalton looks promising, and | stylewarning has recently said he's still working on it. | cconroy wrote: | "statically, strongly typed Lisp that still doesn't sacrifice | its flexibility and expressive power" | | SML | TeMPOraL wrote: | ... with sane (i.e. s-expression based) syntax. | | :). | | But I'll check out SML. That's Standard ML, right? | cconroy wrote: | Yeah. | | ML syntax is very pleasant, and roughly, sexprs w/o all | the punctuation noise. | | I don't believe it has a macro capability like lisps | though, but you gain a sophisticated type helper. | | Definitely worth looking into! | Ar-Curunir wrote: | FWIW, SML is an old research-focused language that was | the progenitor of Haskell and Ocaml and Rust, and not | something to program in :) | gryfft wrote: | My experience with strong types is limited-- I'd done the | thing of learning some C, doing professional stuff in Ruby | for several years and then discovering the ridiculous power | strong types can have and doing some professional stuff in | Go. | | Typed Racket [1] was really a revelation to me in that | regard. I'd be curious how developers with more strongly- | typed language experience feel about it. | | [1] https://docs.racket-lang.org/ts-reference/index.html | christophilus wrote: | I was a statically typed fanboy for most of my early career. C# | and later F# were my daily drivers and still hold a fond place in | my heart. More recently, I learned TypeScript and a bit of | Haskell and Rust. | | However, I think dynamic languages have their place. Most of my | server side code involves parsing one string and transforming it | into another (JSON to SQL or the like). Something like Clojure | spec is really, really useful for this, and beyond that the | remaining code doesn't really materially benefit from static | types. | | At any rate for my typical web application, I now prefer dynamic | languages, which is something I never thought I'd say. | | One last thought: soundness is just one variable to optimize for, | and it's not as important as I once thought. For most parts of my | application, rough edges are not a big deal. For the really | important stuff (like payments), I always write tests and also do | a fair amount of manual testing. And for that stuff, the bugs are | almost always logic bugs that types wouldn't have caught. | Ericson2314 wrote: | > Most of my server side code involves parsing one string and | transforming it into another | | But this is the real problem. It's a huge failure that so much | programmer effort goes into writing the same broken marshaling | code over and over again. It's unproductive, boring, and if you | believe in http://langsec.org/ also the major source of | security issues. | edwinyzh wrote: | How about a language that supports both strong typed data type | and dynamic data types? Not sure about other language, but Free | Pascal and Delphi support both of them. | TooCreative wrote: | I code without types. My "proof", that types do not pay for | themselves goes like this: | | Every time I encounter a bug (during coding, testing or in | production) I make a note what type of bug it was and how it | could have been prevented. | | Types are way down on the list of what could have prevented the | bug. Especially for production bugs, which are the most important | of course. It is so rare, that a bug could have prevented by | types that I can say with confidence that they would have been a | net negative. | | Talking about which tool can prevent the most bugs, integration | tests win by a large margin. | | The reason is that most bugs are conceptual. Like "Oh shit! We | have allowed people to tag items as duplicates of other items. | And we have a function that traverses the list up to the original | item. But now this new feature over there had a bug where it | marks the last original as a duplicate of another duplicate and | then when the traversal function in that other module is used, it | ends up in an infinite loop". | | Another example of a popular bug category: The code contains | assumptions about the environment that do not hold true. For | example PHP's mb_strtolower() will not always create the same | string as MySQL's LOWER(). It is very rare and only holds true | for a tiny tiny fraction of the UTF-8 characters. So you might | expect them to behave the same until you one day trip over one of | those few chars. | [deleted] | simion314 wrote: | Maybe your style is not fit for types? | | So I fixed a bug recently where a url validatior code was | falling because the code was old and recently what is allowed | in an url changed. Imagine there was a language where | url/email/file path was a type that could validate itself when | you create it and you don't need to always try to create regex | to validate things. Even more cool would be if string would not | be something we use daily (like we don't use every day bytes) | so we would have always a type for customer name, file name or | file path and you would need to explicitly ask a conversion | from a customer name to a file name etc. The way I think future | languages and types could help is to make it almost impossible | co create invalid data structures or state. | tluyben2 wrote: | I think it takes people who do not start out 'believing in | types' (I was taught by pupils of dijkstra in NL so my belief | in strict-as-possible has always been quite firm) a same kind | of timespan / experience as the OP; experience (very) large | weak/stringy typed projects, experience them for at least a few | years full time and then, when the frustration sets in, try | something which is the complete opposite like Haskell/Rust. | | I think a certain frustration needs to be there to try | something else anyway, when you come against the billionth | cannot call hello() on undefined error in a critical (for the | company), 100k+ LoC, multi-team project, you might wonder if | there is something else. | | You are probably not going to post your 'list' here but proper | types prevent many trivial and non trivial bugs. Many of the | points on your list will fit there, but you wouldn't know it | yet. | | > Talking about which tool can prevent the most bugs, | integration tests win by a large margin. | | Obviously there are ways of catching them without types, but | proper type systems catch them at compile time and also; how | are these mutually exclusive; we use both. We just need less | integration tests. | | > Like "Ooooooh shit! We have allowed people to tag items as | duplicates of other items. And we have a function that | traverses the list up to the original item. But now this new | feature over there had a bug where it marks the last original | as a duplicate of another duplicate and then when the traversal | function in that other module is used, it ends up in an | infinite loop". | | In some of my favorite languages you can catch this in types at | compile time. | | Obviously; do what works best for you and your team; I just | don't buy your overarching statements and 'proofs'. If it works | it works, but it would _probably_ work better with types. | kybernetikos wrote: | Working on a large scala codebase, I quickly learnt that | 'compile time' in one language does not always happen before | 'run time' in another language. | | What matters is not the phase in which the bug is found but | how close in absolute time finding the big is to writing it. | tluyben2 wrote: | Agreed and we have to continue improving in every way; | dynamic/static/hybrid, just saying that I have not seen | this dynamic enlightenment in larger projects. I have only | seen the pain of runtime errors that other (static | language) teams never had. Sure, if you would-have-written | a test for it, you wouldn't have had it either, but types | rather force you to think about it while writing. So sure, | you are 'done faster', but the fall-out, and again ofcourse | YMMV, of having statically preventable bugs popping up in | Sentry at 3 am in the morning with things you would've | prevented (not necessarily directly by the types but you | would've thought about it more because you had to define | the types, which is I think what the parent poster here | misses too; I just tend to think less and try more without | types which, again for me, is a bad state, but ymmv) is not | great. | | But sure, I am biased as my experience with statically | typed langs has been good since I moved from asm/basic in | the 80s to pascal/c (they were an improvement over | asm/basic and my first experience with types, not saying | you should use them now, or not). | kybernetikos wrote: | The view I've heard expressed is that deep thought on a | piece of code reduces bugs. Whether that takes the shape | of religious TDD, rigorous proofs or detailed type design | doesn't make such a lot of difference. | | I used to be fully bought into types, but I've since | realised that they have a number of downsides that in | many cases more than offset their benefits: | | 1. Ergonomic typesystems require a lot of work to happen | at compile time and slow down the iteration time (one of | the more important things for programming in my view). In | my view, saving the source and seeing the result almost | immediately in a browser is one of the big advantages of | web development. | | 2. Types are almost always written in a second, much less | powerful DSL and then sprinkled distractingly through the | code that actually does the work. I prefer the way | Haskell does this- separate the type signature out onto | at least a separate line rather than mashing the two | different languages together. | | 3. Higher levels of abstraction tend to become very hairy | in many type systems (although not all). This ends up | just meaning that people who like types often restrict | themselves (unconciously) to less abstract programming. | They spot the time they're saving by avoiding some kinds | of bugs, but they don't see the time they're wasting by | being unable to talk at a higher level of abstraction. | Another way this shows itself is that types are very | rarely first class objects in strongly typed languages, | making it very difficult to create code that operates on | types, or understands types. | | 4. Type systems open up opportunities for type driven | architecture astronauting, which is just yet another way | you can go down an unproductive rabbit hole. There was an | interesting study done on different teams solving | problems with different languages. The differences of | different teams within the same language was much bigger | than between languages, but the team that made slowest | progress (and without particularly having an unusually | low number of bugs) was the team that leant the hardest | into encoding everything in the type system. | | 5. Type systems encourage code generation build | pipelines, which again slows iteration time and makes | everyones life miserable. | | 6. Type systems reflect a incorrect model of the world - | user input, network input, file system data _is not | typed_. The misery that I 've had with some web server | frameworks that refuse to acknowlege that they don't know | every possible thing that the web client might send them | and are able to slot it into a predefined type. I think | this is the same kind of error that we made with OO | systems - thinking that we could fit the world into a | predefined inheritance hierarchy. | | 7. Type systems encourage a static view of the world. The | types of things can change under you, dynamically, (e.g. | the structure of a table in a database), but in most | typed languages you can't cope with that correctly | without shutting down and deploying entirely new code. | | 8. Related to that, it's hard to imagine using a strongly | typed language with the live image approach of smalltalk | or sometimes used by lisp systems. This means that the | popuarity of strongly typed languages is killing valuable | and interesting approaches to building complex systems | that emphasise observability, interaction and iteration | as a way of understanding them. | | There are genuine advantages to typed languages, but many | of the advantages touted as being unique to typed | languages can be provided by advanced linting and IDEs | (intellij was surprisingly capable on plain JS + jsdoc). | You can also ameliorate some of the disadvantages of | untyped languages while keeping the benefits by | deliberately programming in a fail-fast way. | | I'm sure that type systems have their place. The research | I've come across on empirical studies suggests that while | there may be positive effects they are small, which does | not at all mesh with the extreme partisanship I generally | observe. Yes, type systems gain you something, but there | seems much less awareness of what you lose. | marcosdumay wrote: | There's a lot of things, some about old-school types | (Java, C), other about modern ones. I don't think most | are fundamental, even though some are common experiences | today. | | #1 is fundamental. (Yet people somehow live with the JS | ecosystem that's slower than GHCi.) It's supposed to | evolve into always becoming a smaller problem, since | computers are always getting faster; but I don't think | we've put everything we can into types already, so I | expect it to get worse in the near future. | | #2 and #3 are about old-school types. | | #4 Oh, yeah, they can. But they can also help a lot in | team coordination. Powerful stuff enable you either way, | if you harm yourself or take advantage of it is your | choice. | | #5 Failures in type systems encourage code generation. | Expect that to always improve, but always slowly. | | #6 That's why there's always a parsing stage between | input and processing. You deal with input errors at the | parsing stage, and processing errors at the processing | stage. Most communities of dynamic and old-school | languages make a large disservice to the industry by | mixing those; they explode error handling into something | intractably complex. | | #7 Hum... You are holding it wrong. Do not state variants | into your types. Instead, use the type system to get | every invariant out of the way, so the variants stand | clear. (And yeah, there are plenty of libraries and | frameworks out there that try to encode the environment | into types. That deeply annoys me... But anyway, if you | do that, take the types as requirements upon the | environment, not its description. Those are different in | very subtle ways.) | | #8 This shouldn't be fundamental. AFAIK there are not | many people trying this, and the few there face a | Sisyphean task of keeping their code up to date with the | mainstream changes. I do hope people make progress here, | but I'm not optimistic. | kybernetikos wrote: | > #2 and #3 are about old-school types. | | There absolutely are approaches to this that don't fall | foul of my complaints, but when you say 'old-school | types' I think you're talking about Java and non inferred | types. | | I was including other more modern languages in my | criticism. Scala for example ends up with pretty hairy | types very quickly for higher level code. So much so that | they made the documentation system lie about the types to | make it easier to understand. | | And most currently popular languages don't give you | runtime access to types and allow you to treat them as | first class. | | The languages that allow you to deal with types with the | same language you write code in are not remotely | mainstream. So unless by 'old school' you include all | mainstream languages then I disagree. | marcosdumay wrote: | Yes, I meant types systems like Java's. | | I was thinking about unusable code, cause by the need to | write way more down in brittle types than anything you | save on coding. In fact there are problems with complex | types. | tluyben2 wrote: | > Yet people somehow live with the JS ecosystem that's | slower than GHCi. | | I think a lot of people, including the parent, seem to | equate speed of ecosystem and iteration with web | development and instant reload of web pages. When other | systems allow fast iteration, it goes unnoticed unless | it's for web dev. Luckily, a bunch of those 'impossible' | systems have to now too, like [0]. | | [0] https://ihp.digitallyinduced.com/blog/2020-08-10-ihp- | live-re... | kybernetikos wrote: | Web development is an example. Fast iteration is the | thing that I like. I have so far associated fast | iteration with dynamic languages, and it is certainly the | case that my experience is that most fast iteration | systems are dynamic. | | But maybe that simply reflects a concern of the relevant | communities. If strongly typed language systems start | adding fast iteration approaches to things and are able | to achieve a similar level of quick iteration then that | will definitely address one of the things I dislike about | them. I haven't coded significant amounts of haskell | since 2000, but back then what you could do interactively | was very restricted. | | At the end of the day, the compiler is doing a bunch more | stuff in strongly typed languages. It's like taking a | bunch of your verification infrastructure and saying | 'these must run before you're allowed to see the result | of what you wrote'. It will necessarily be slower, | although with work maybe it won't be so much slower that | it matters. | tluyben2 wrote: | > Fast iteration is the thing that I like | | > haskell since 2000, | | Things changed a lot in 20 years. | | Thanks for noting down something about your age; I have | always been a bit ageist about 'fast iteration' as I | never met someone close to my age (been devving | professionally for 30 years this year) that cares too | much about it. I am not a very good programmer, but a | very experienced one and i'm consistently faster at | delivering than my 'fast iterating younger peers' as I | simply know what i'm going to type beforehand, I don't | need too many iterations to get it right and I have | enough experience to know that i'm close to what we need | after it compiles. The people who just type/run 1000 | times/minute get stuff done, but it's not the way I would | ever like (or liked) to work. | | > It will necessarily be slower, | | GHCi is fast but other avenues can be explored as well, | like creating a real interpreter just for development , | like Miri for Rust. Only for faster iteration of logic, | you forgo some of the type benefits, but when you are | done iterating, you compile and voila. I guess the | merging of incremental compilation, jits, interpreters | etc will evolve in something that might not run optimally | but gives blazingly fast iteration up to perfect | performance after deployment. And anything in between. | tluyben2 wrote: | I am positive about a lot of these points for the future. | Especially the performance points; that's going forward | fast. But yes, that's often pretty slow; not that | bothered by it for my work though. Also, linters work | well for statically typed languages too; I usually don't | have to compile for 100s of lines of code. If the editor | does not complain, it'll probably all work fine. Like I | said; do what works for you , but I think at least a good | mix will get more benefits. | | 6. People mention this more often, but I just don't see | how that works; you cannot program without knowing what | data you are getting. Sure the world is not typed, but at | the moment you are going to use the data, it is typed; be | it in your logic, head or actual types. Any webserver can | go lower level and give you a ByteStream, but when you | finish parsing that, you still have types. You might not | know them upfront, so you use ByteStream for a bit, but | once you know, you bake types and the world is nicer. | Imho :) Not sure why that's a difference? | | 7. This is an issue where? I know it's Erlang domain, but | microservices/docker/k8s/ci/cd/lambda/functions/.../all | modern crap do this (redeploy, killing the previous | instance(s)) with any code, always, including dynamically | typed code. So sounds like a niche? | | 8. Agree with this; we should experiment and research | these things and continue building them. I work with | Lisp/Clojure as well and like it, I just miss types | often. I never suggested it's all crap; I'm just looking | where benefit comes from. | steveklabnik wrote: | A good post on point six: https://lexi- | lambda.github.io/blog/2020/01/19/no-dynamic-typ... | kybernetikos wrote: | > you cannot program without knowing what data you are | getting | | Types almost always overconstrain. Each part of your code | relies on some very specific properties of your data, yet | most type systems end up restricting your function to | only work with data that meets a whole bunch of other | properties that your code doesn't actually care about. | tluyben2 wrote: | > Types almost always overconstrain | | Not in my experience; so many, basically, stringy types. | | > Each part of your code relies on some very specific | properties of your data, | | So then you either have a type that exposes those you | need or you have different types for different functions. | | > yet most type systems end up restricting your function | to only work | | Again, I don't understand this statement; someone | implemented the types to fit the data for some functions | they needed. How does the 'type system restrict' | anything? | NicoJuicy wrote: | I have the feeling you are applying your experience from coding | your own project. | | And not from a project in a team, where you didn't code | everything. | petters wrote: | IMO types in Python mostly pay for themselves when you develop | in a team. | | They greatly improve the developer experience by providing | automatically enforced documentation. Allows IDEs to provide | code completions etc. | [deleted] | viraptor wrote: | Can you share the tally? I wonder about the categories you | used. | AaronFriel wrote: | Given that you don't code with types, how are you certain which | bugs you could have prevented? | | I'd be curious to see this list. Is it anecdote, anecdata, or | is it a spreadsheet you have somewhere? | benjiweber wrote: | I find the value of static typing to be more from increasing | the ease of exploration and understanding of a codebase than | preventing bugs directly. The constraints on how the code | you're reading could be being used making building a mental | model faster. Not to mention the tooling built on top of the | typing that can help with exploration. | tluyben2 wrote: | > increasing the ease of exploration and understanding of a | codebase | | Very true and a good thing to remember when discussing static | typing. | bpicolo wrote: | Can't be beat for refactoring. In languages I can trust the | compiler, I can refactor fearlessly | Twisol wrote: | It's interesting that you talk about bugs, when the OP doesn't | make that argument. The OP is very clear, actually: | | > It didn't mean I was free from logic bugs. (Nothing can do | that in the general case!) It did mean that a program which | type-checked wouldn't blow up in ways the type-checker said it | shouldn't, though. | | Other than soundness (which is not the same as avoiding logic | bugs, anyway), the points the OP raises are about | _expressiveness_. Types help [you / the OP] organize more of | your knowledge in the codebase. That's never going to be the | most obvious preventative measure against bugs, because the | goal is higher-order: structuring your codebase so _you_ can | reason more clearly about the program. | TooCreative wrote: | That might be a matter of personal taste. How ones brain is | wired. For me, types make code less expressive. I can grasp | the structure of a piece of code the easier, the less meta | data there is on top of the algorithmic structure. | tluyben2 wrote: | What are you working on though? What algorithmic structure? | TooCreative wrote: | Compare this: private static function | request(?string $method, ?string $url, array $options): | array { ... } | | To this: function request($method, | $url, $options) { ... } | | I can grasp the latter much better. It immediately forms | a structure in my head that I will remember while I read | other parts of the code. To do the same with the former, | I think my brain uses up twice the energy or more. And | even then, I will not have such a good grasp on it as | with the former. | unrealhoang wrote: | Counter point: with the former, I know exactly how to use | it: for item in request("get", "google.com", []) { .... } | Whereas if there's no example for the latter, I'd have to | read the code of that function (or sometime the code of | the functions it called) to know how to use correctly. | TooCreative wrote: | Wishful thinking. | | You know it returns an array, but you don't know what the | array contains. So you don't know how to use it. | | You still need to look into the code to see what it | returns. Then you will see that it returns an array and | what the array contains. | | And now you had to process the information that it | returns an array twice. Once in the function definition | and once in the function body. | unrealhoang wrote: | Heck, not only I don't have to look at the code, with | proper IDE setup, I even don't have to go to the | documentation page, just `item.` and my IDE/editor will | present me with the list of things that are applicable to | my item. | | And that's only the response part of the usage, there's | absolutely no way for me to know if the method is an | enum/constant or is it a string without looking into the | code for dynamic typing, and again, sometime N-level deep | to get enough information of "what to pass to this | function so it will work". | castwide wrote: | > You know it returns an array, but you don't know what | the array contains. So you don't know how to use it. | | That's a limitation of your specific example, not type | systems in general. | TooCreative wrote: | For the example I took a random line from Symfony and | shortened it a bit: | | https://github.com/symfony/http- | client/blob/master/HttpClien... | | You are free to link to some other example. | mplanchard wrote: | Most type systems also provide information about what's | in the array, like Array<String> or int[] | TooCreative wrote: | That is just another piece of metadata. But it does not | help either. Now you know you get an array of strings. | But you still don't know what is inside of the strings. | | It might be ['headers'=>'...','body'=>'...' ] or | ['status'=>'...','response'=>'...'] or god knows what. | | And you are back to reading the function body. | mplanchard wrote: | Is that like a hashed array you're implying? In that | case, the type wouldn't be string anyway, but something | like HashMap, Object, Dict, etc. More commonly though, | something like HTTP options would be its own type, with | defined properties, which you could browse via | autocomplete or your IDE's hover/peek functionality. That | type would then specify the types of its values, so you | can be certain that options.status is an integer, for | example. | | You'd generally only use an otherwise untyped array of | strings when you can't know beforehand what their values | can be. | tluyben2 wrote: | Got that, but was triggered by 'algorithmic structure'; | that sounds a tad 'over the top' when you then come with | some basic web request. | | However, I would not call your example particularly well | typed. Array is still basically untyped. | | When I talk about types I mean things like; | Either<Error,Group> AddGroup(string GroupName, User[]? | users) | | In this case I know what I am getting and I know the | users are already, when reaching the webserver, | deserialized, validated (or the type would reject them | and produce an error and I know I am getting an actual | Group back. | | in your case that would be; function | AddGroup(GroupName, Users) | | I have no idea how that is better or clearer or less work | to write? No idea what it returns; Users probably is an | array of users, but is it? Or is it a typo? And I need to | validate whatever comes in. | | You can add docs but I do that still with the typed | version (although thats automated mostly unless I need to | explain something). | | Worse as well is that when I have this: | function AddGroup(GroupName, Users) function | AddGroups(GroupNames, Users) | | Now i'm completely lost. I'm not even sure these users as | input are the same things? Both arrays? Both the same | User 'things'? Output? | | But yes, in php I write typeless mostly too (although I | luckily do not have to touch it much anymore) as the | types mostly suck. But in more advanced type systems, the | types will tell you a lot/all about the input/output, so | you can do without a lot of docs and trying things out | can be automated; as we know the precise input so some | generator can generate example input that will | immediately work; like swagger on speed. | | Etc. But agreed, your example does not benefit too much | from typing, however I would define Option[] as something | precise than just a void* and hope for the best. Also | Method and URL so I know invalid input for those very | well defined things cannot be violated. | | So your example: private static | function request(Method $method, URL $url, Option[]? | $options): WebResult | | would clear up a lot for me. Now, for instance, I can | see, and not guess, that you are answering a web request | instead of some request with confusingly similar names to | webrequests. | | But how about some more 'concrete' examples; 'request' is | rather a low level / abstract thing (I hope...). But your | business logic would contain more concrete functions; any | examples from those that are similarly badly geared to | types? | TooCreative wrote: | And now instead of $body = | Http::request('GET', 'https://example.com')['body']; | | You have to do this? $body = | Http::request(new Method('GET'), new | URL('https://example.com'))->body; | | And you have cuttered the global namespace with "Method", | "URL" and "Option"? | tluyben2 wrote: | Why would they be in the global namespace? They would be | in imported namespaces (something\Http for instance). | That's rather normal. | | And yes, now you have to make sure you are passing the | proper and valid types, which is obviously the goal here: | $body = Http::request(Method.GET, | URL.Parse('https://example.com'))->body; | | In other languages you can make it so that the static URL | is validated at compile time even. | jenscow wrote: | A better example for a typed function would be something | like: function Request(string method, | string url, RequestOptions options) : Result {...} | | or Result Request(string method, string | url, RequestOptions options) {...} | | Here, we know `url` is a string (rather than the parsed | object), we know all the supported options (they're | members of the RequestOptions object/enum), and every | value in the return value. Some might suggest even making | `method` an enumeration. | | If the function name was better, you could call that | function right now without needing any more information. | | Also, especially if you're a fan of defensive | programming, most of the basic argument checking is | performed at compile time, leaving the function body | cleaner. | | Based on your example alone, perhaps you don't have | enough experience with typed languages to appreciate the | benefits. I don't mean that in a belittling way, but more | of an invitation to learn more about the craft. | TooCreative wrote: | For the example I simply took to a random, recently | updated file from Symfony: | | https://github.com/symfony/http- | client/blob/master/HttpClien... | | And shortened it a bit. In the real example, as you can | see, there are even more parameters, making it even | harder to read. | jenscow wrote: | Well, for me it's easier - perhaps because I'm used to | reading parameters in type+name pairs. | | The data type is right next to the parameter name - and | it will be in the pop-up provided by my IDE when I use | the function... I don't need to look at the docs or | comment (which may not exist). | viraptor wrote: | You're showing an example of a function signature in a | specific language, not an example of typing though. These | are related but not exactly the same. | | For example here is a Ruby version, very dynamic: | def request(method, url, **options) end | request("get", "http...", foo: "bar") | | And here is a completely statically typed Crystal | version: def request(method, url, | **options) end request("get", "http...", | foo: "bar") | | The argument types are inferred automatically from the | usage and mismatched usage will be caught at compile | time. And in the second case the ide can still tell you | the types in the signature if you want to know them. | | In the third example, here's completely dynamic python: | def request(method: str, url: str, **options): Dict[...] | | No type checking happens here in the language itself. | | So what I'm getting at is - if you don't like languages | with verbose explicit type signatures, it doesn't mean | you don't like static types. | andybak wrote: | One note of caution. There's a pleasure to solving problems that | arise from type systems akin to solving Sodoku puzzles. It's | intellectually satisfying and feels like productive work. | | I sometime worry that a lot of the overhead and baggage that type | systems sneak into coding are hidden because it's fun to resolve | them. | | I'm still on the fence about types. I love them when they help me | but I find myself generally writing more boilerplate and less | elegant code than I can sometimes write in Python. | | My typed language is C# so I do have type inference (although not | the most advanced). I don't have Union types. My IDE does warn me | about potential nulls. | | So I've got 50-75% of the things listed in this article. And I'm | still not 100% sure there's not a hidden cost that's quite hard | to define. | pavel_lishin wrote: | A lot of people in the comments are saying how they started off | in Java, or C, and hated types, but eventually grew to love them. | | I started off in PHP. It was wild. Anything could be anything. | Refactoring was a nightmare. Our codebase was littered with | mystery variables like $hold, $hold1, and $holda, which were re- | used all over the place. (Granted, this two other problems | entirely orthogonal to types.) | | Then I got a job at a Java place. My god, it was beautiful. I | could suddenly _know_ what arguments functions were expecting, | even if I hadn 't seen them before. I could be instantly aware if | I was trying to use the wrong variable somewhere, or pass in | invalid data. | | It was as if someone lifted me out of a dank cellar where I had | previously subsisted on whatever mushrooms the rats didn't deign | to eat, into a brightly lit dining room full of steak and | pastries. | edem wrote: | I this is the "profound enlightenment experience" ESR was | talking about in his seminal paper "How to become a hacker?". | eru wrote: | Apropos PHP: I hate it with such as much passion as the next | guy, but I am quite impressed with what Facebook managed to do | with Hack! (Including adding lots of types.) | temporallobe wrote: | Even though I learned C/C++ in school, I started off my career | with a typeless language, Perl. I loved it for its simplicity | and power to quickly spool up working code, but realized it was | problematic to use for large projects for many of the same | reasons you state. I then switched to a Java project and was | immediately frustrated with types because of how verbose it | was, but after a while I came to appreciate just how beautiful | and pragmatic it was, especially in its ability to help avoid | so many runtime bugs that have been the bane of my existence in | the Javascript, Clojure, and Ruby world. | throwaway894345 wrote: | I've often felt that some people dislike types because they | expect to be able to write code in a certain way that they | know will make some very narrow happy path work _now_ and | they get really frustrated when the compiler tells them that | there are other paths in the code that don 't work. "Why is | this stupid compiler slowing me down?!". This frustration | betrays the programmer's indifference toward the broader | quality of the project and their willingness to trade bugs in | other paths for a feature that appears to be working. The | cognitive mismatch is that it's intended change the way you | think and program so you can move quickly on many paths at | once (not only your very narrow happy path)--with a well- | crafted type system, we can move fast _and_ have quality. | Note also that 'quality' isn't just about bugs, but also | about a code base that is maintainable, similar to the GP's | and the parent's observations about the unmaintainability of | their PHP and Perl code bases. | chrismeller wrote: | I went a similar path, PHP to C#, and never looked back. When I | had to switch to Node for one job I was pulling my hair out | constantly (and quite literally) because of stupid things that | would never have happened in what I called a "real" language | (that being a typed, compiled one). I mean for $deity's sake, | there weren't even any dependency injection options at the time | and many many times it turned out a bug I introduced was | because of simple typo in a camelcase property name. | | I absolutely love Node for the ease of writing something quick | and dirty. No dependency injection, no coding standards, | nothing but a tool to quickly churn through a ton of data or to | perform a single task really well. I also think there are some | frameworks (using Typescript, like NestJS) that do JavaScript | apps really, really well. I will still, never, ever, ever | voluntarily write any kind of "real" application in a language | that is not type safe again. The benefits just aren't worth the | perceived time savings... | withinboredom wrote: | I work on the big data side of Automattic, so I tend to work with | PHP, Typescript/JavaScript, Python, and Scala, but I also work | with C# for fun. When you work with and without types often, you | realize that it doesn't matter. Types are great for expressing | constraints on the code, but you can do the same thing with | conventions in non-typed code. | | IMHO, type-less provides some resiliency. If you write code for a | "string" anything that satisfies "stringiness" will still run | just fine. This lets you build types that "trick" the code you're | calling, which can be useful sometimes. I think I've abused this | capability less than 3 times, but more than once. Mostly it | allowed me to significantly change behavior without rewriting | huge chunks of core code. However, these were all proof of | concepts. Don't get it twisted, working with no types is pretty | annoying sometimes. | | Types, on the other hand, are great for most things, but I do | find they get in the way sometimes. It's annoying when you look | at the code you're calling and see that it only uses `.x` on a | type you pass it, but you have to completely build a type just to | create a `.x` on the type you pass in. I once found a NullPointer | bug in HDFS with a static type (very strange) and had to work | around it doing exactly this. Anyway, I think I'd like Go's | approach to interfaces. | musingsole wrote: | Those who argue strongly for one method over the other are | likely to not understand the one they're against. Which sucks, | because if you do know both, there's rarely a reason to have | strong feelings one way or another. So, once again the naive | but vocal voices get all the decision power. | | /Too many arguments at work are over dogma and I have to spend | a lot of my time reminding people that there are options. | That's all. There are options. Your kneejerk response to a | problem might be fine -- might be right -- but there are always | options. | withinboredom wrote: | To expand on that, the options also have trade-offs. They can | both be just as right or wrong, and the trade-offs be the | deciding factor. | loup-vaillant wrote: | I was never sure what to think of dynamic typing, until I | tried to implement a fairly simple Earley parsing algorithm | in Lua. The thing was 50 lines, and I was _lost_. I only | managed to get runtime errors such as "you can't add | functions, you can't apply a number, this reference is | null... | | That's when I understood where TDD came from: dynamically | typed languages require so many tests to work reliably that | we better write those tests first so we're not tempted to | omit them. | | Anyway, I redid the whole thing in OCaml, and this time got | lots of _compile time_ errors. Which I could correct, and | once the compiler was happy, well... my program was basically | correct. | | --- | | I still don't rule out that other people's brains are wired | fundamentally differently. I don't expect it, it would | surprise me, but I honestly don't know. What I do know is | that dynamic typing is not for me. I'll suffer through it to | get other advantages (Python's comprehensive library for | instance), but that's about it. Dynamic typing and I are | otherwise _done_. | withinboredom wrote: | > The thing was 50 lines, and I was lost. | | The key to writing type-less code is making it so simple | that you could cry. Avoid being clever at all costs. | However, it's almost the exact opposite when you have a | nice compiler checking everything, you can be mighty clever | and know that it will work. | | I'm lucky in that we can solve a problem in a myriad of | languages, depending on if it needs to be "realtime", or | can be precomputed. There are some types of problems I'd | rather solve in Scala, and others, Python or PHP. It just | depends. | rswail wrote: | Using interfaces/traits/protocols don't preclude strong typing. | | If you write code for a type that "has" stringiness, as | expressed by a trait, then anyone can build a variant of their | types that expresses that trait. | withinboredom wrote: | Yeah, for sure. That's certainly possible, but I rarely see | people build for it, at least when it comes to value types | like strings, ints, etc. | | One other thing that I DO like about type-less, is the | ability to introspect, for example, in PHP: | | ``` foreach ((array) $my_class as $property => $value): ``` | | vs. a bunch of boilerplate and making sure types match up in | most strongly-typed languages. | mceldeen wrote: | > IMHO, type-less provides some resiliency. If you write code | for a "string" anything that satisfies "stringiness" will still | run just fine. | | Things like that are actually possible to do in a _light | weight_ way in FP languages and many of them are part of the | stdlib. (PureScript happens to be my favorite example.) Many | times it brings back the same feelings I had working in Ruby | (which I also happen to love), but with the added confidence of | a strong type system. | IshKebab wrote: | I don't understand why he thinks Typescript still isn't worth it, | when all of his points are issues in JavaScript that are solved | by Typescript. | charliesome wrote: | An interesting insight I came across a little while ago is that | for mainstream, industrial languages, this way of thinking about | types is relatively new. It's not that we're seeing the pendulum | swing back to types, it's that we're discovering them for the | first time! | | In earlier typed languages, the types weren't there for reasons | of soundness or productivity at all. The types were there for the | compiler alone, as the compiler needed them to know which machine | instructions to emit for various operations. Types were just a | cost imposed on programmers. | | Once computers became powerful enough that we could afford to | spend cycles and memory making these decisions at runtime, | dynamic languages became viable and we saw industry shift over to | them, except in domains where dynamic languages still weren't | viable, or where existing codebases or ecosystems made it not | economically viable. | | Fast forward to the present and decades worth of type theory | knowledge is finally filtering through to industry in the form of | languages like Rust, TypeScript, Swift, Kotlin, and others. For | the very first time we're embracing types for their soundness and | productivity benefits. This is an exciting new era. | bjz_ wrote: | As a bit of a nit-pick, it's not _that_ new - see languages | like ML, SML, OCaml, Miranda, Haskell, Coq, etc. that combined | the notion of types from programming languages and types from | mathematics. It's more that it's only recently that _industry_ | has been learning about it. | | That said, I definitely think you're right to point that this | is a new thing for industry, and not just a swing back to the | idea of types that were previously mainstream in industry. I'm | excited too! | asiachick wrote: | I feel like new features of popular typed languages have also | helped them catch up to dynamic language it terms of ease of | use. Go back before C++11, without "auto" writing generic code | sucked! Callbacks without lambda closures also sucked. I'm sure | someone will point to some 40 yr old language that had this but | those languages weren't popular for whatever reason. var got | added to C# in 2007 and it took more releases to let it be used | in more places. Apparently added to Java much later. | | I'm sure someone will give me a good example but for example | std::sort in C++ before closures in C++, if you want to sort | one array by another, for example you have an array of indices | and an array of values and you want to sort the indices by the | values, before closures I'd argue this was fairly painful | unless you resorted to global variables or copying all of the | data into some intermediate format. You'd end up having to | write or generate a class with a sort function solely for the | purpose of being able to pass in a member function to sort that | could access the values. Today it's trivial because you can | write a lambda that closes over the values and pass the indices | into sort. | stult wrote: | It seems pretty evident to me that most successful and popular | dynamically and statically typed languages are converging from | different directions on a similar set of solutions. Very much | reflecting the phenomenon you describe. Some simple examples: | C# has moved from very strong typing of the exact sort OP | criticizes (`Person person = new Person();`) to increasingly | permitting looser/more expressive typing with `var`, anonymous | types, pattern matching, etc. From the dynamic side, optional, | loosely enforced typing is starting to grow more common (e.g., | type hinting in Python, TypeScript in JavaScript) and provides | a static but still flexible form of typing. So there's some | happy medium where the language balances the permissiveness of | dynamic typing and the expressiveness of static typing. | chrisoverzero wrote: | >[...] increasingly permitting looser/more expressive typing | with `var`, anonymous types, pattern matching, etc. | | None of these features are related to loosening the type | system. | loup-vaillant wrote: | It can still feel that way. Take C++ for instance: | Foo f = fooFromElsewhere; // explicit typing (old) | auto f = fooFromElsewhere; // type inference (new) | | Now what happens if we change the type of | `fooFromElsewhere` from `Foo` to `Bar`? With the old way, | we need to change the code to: Bar f = | fooFromElsewhere; | | With type inference however, you won't need to change that | line at all. And if the new type has enough in common with | the old type, you may not change the code at all. It's just | as strict as explicit typing, but it's arguably more | flexible, and thus _feels_ looser. | nickmqb wrote: | You're not giving the older generations of programmers enough | credit here. | | While it is true that strong typing is a requirement for the | best performance (and this remains so), the productivity | benefits of strong typing have been known for a long time. | | I mean, just look at languages like C# and Java. These are well | established, extremely popular languages, used mostly in | business software. A domain where performance is rarely | critical. Yet, these languages are very popular. Not in the | least because they make it easier for programmers to understand | and work with other people's code, and because they provide | good tooling, both of which are hugely valuable in a | business/enterprise context. Strong typing plays a major role | in enabling these features. | | Even when C# was still a brand new language, roughly 20 years | ago, Visual Studio already provided features like "go to | definition", "find references" and autocomplete out of the box. | These were a major reason for people to adopt the language. | | It's no surprise that people like Anders Hejlsberg, who created | C#, later went on to create TypeScript. They already understood | the productivity advantages of strong typing and wanted to | bring those to the web. | orobinson wrote: | The heuristic I use for choosing a statically typed language vs a | dynamically typed language for a task is whether or not the code | will sanely fit in a single file. | | If that is the case, then that means I'll probably be able to | keep the structure of the code in my head and therefore I'll be | able to get by with a dynamically typed language. However, once | the code starts to span multiple files, typed method signatures | in a statically typed language are invaluable. It sucks having to | navigate a codebase with tens of thousands of lines of ruby or | python code trying to work out exactly what structure of object | can be passed to the method you're working on. | rswail wrote: | A lot of people are making comments about how they can code | "faster" without types. | | But for the majority of the code we write, inital speed isn't | that important. Understanding the code and maintaining it are | orders of magnitude more important for any non-trivial code. | | Types are not only a way for the compiler to understand your code | and impose constraints. They're also your _API_ to other | programmers. When they see a sum type, they can understand its | possible states. When they see a product type they can understand | its possible values. | | Understanding other people's code is at least half the job of a | programmer, whether it's understanding a library or understanding | code you have to maintain, or understanding your _own_ code that | you wrote 6 months ago. | | Types help you do that. | skohan wrote: | > Understanding other people's code is at least half the job of | a programmer | | This was one of the pain-points when I was working more with | node.js: the function signature told you _nothing_ - like | whether the function would even return or not would sometimes | be a mystery. | | In very, _very_ short scripts you can get away without types | (like in a notebook for example), but once a project starts to | get even medium size the tiny amount of time you put into | writing a type name explicitly here and there is more than made | up for by the degree it helps with the structure and | correctness of your program. | bo1024 wrote: | This kills me about python. So many times I cannot figure out | what exactly a functions expects and what it returns, | sometimes even from reading the documentation! Matplotlib is | especially bad. | dwaltrip wrote: | Off-topic writing feedback: use less italics. Readers are good at | figuring which words are important on their own :) | seanparsons wrote: | Types for checking things are correct is really important which | is what this talks about. | | For me though they really come into their own when those types | then help to eliminate boilerplate. Haskell's foldMap function is | my classic example of handling the accumulation of a result where | it does the hard work based on the types. Idris goes even further | by supporting the ability to infer obvious code for your code | editor like handling each case in a sum type. | TurboHaskal wrote: | Having started out with Pascal and C, I thought I hated | statically typed languages as well until I got exposed to SML and | Miranda. I guess Rust or Swift have the same enlightening impact | to people coming from Java or Go and I'm glad such approach is | finally hitting the spotlight, even if a bit hampered. | | That being said, and as much as I see the appeal on those and | hoped that stuff like ATS or SPARK were more prevalent, for me | they lead to dull, boring code bases. Which is great! But when I | look back on my career, the most fun I had, the craziest | abstractions, cool hacks, the code I'm most proud of, it's the | one written in dynamically-typed on untyped languages. And here | I'm talking about some flavor of Assembly, APL, Lisp, Forth or | Smalltalk. PHP, Python and JavaScript and similar scripting | languages just don't cut it for me. | rwmj wrote: | Yeah the article is just "hey, I discovered SML!". I've been | using SML, Haskell, then OCaml since the early 90s and the | benefits of (proper) types and type inference have always been | obvious. ___________________________________________________________________ (page generated 2020-09-27 16:00 UTC)