[HN Gopher] The type system is a programmer's best friend
       ___________________________________________________________________
        
       The type system is a programmer's best friend
        
       Author : dustedcodes
       Score  : 207 points
       Date   : 2022-11-05 11:49 UTC (11 hours ago)
        
 (HTM) web link (dusted.codes)
 (TXT) w3m dump (dusted.codes)
        
       | Konohamaru wrote:
       | You know those "how to draw Bugs Bunny" art guides they used to
       | include in children's art books? Where they begin with a circle
       | with some guidelines and then do a whole bunch of stuff and the
       | end result is Bugs Bunny? But you have no idea how they went from
       | Point A to Point B? That's the same thing with Type Theory.
       | 
       | PROFESSOR: Well, you see, there are different objects, like
       | strings and numbers, that are shaped differently, so we put them
       | into different categories. Those are types! See how simple this
       | is?
       | 
       | <a whole bunch of stuff later>
       | 
       | PROFESSOR: Now the endofunctor of the covariant types are jointly
       | distributed under the free monoid, provided that the pullback
       | doesn't reverberate the time fibration of the dependent type
       | space.
        
         | xigoi wrote:
         | If you actually pay attention to the "a whole bunch of stuff",
         | maybe you'll understand the latter statement.
        
       | bjourne wrote:
       | I don't understand. Isn't classes what the author asks for?
        
         | tsimionescu wrote:
         | Class as a concept is somewhat orthogonal to type, at least to
         | people into programming language research, who consider "type"
         | to implicitly mean compile-time type. Classes are taken to
         | refer to support for virtual method dispatch, while types refer
         | to compile-time expression type-checking. In languages like
         | Java, C++ or C#, the type of an expression corresponds to the
         | class of the value it will have at runtime. However, in Python
         | for example, compile-time expressions always have the type
         | "any", but can have various classes at runtime.
         | 
         | This difference between class and type can even be seen in some
         | of those languages. For example, the type *X has no
         | corresponding class in C++ for any X. In Java, the type int
         | doesn't have a class, and the types ArrayList<Integer> and
         | ArrayList<Object> have the same class.
        
         | masklinn wrote:
         | Lots of languages reify types as classes, but that's not
         | necessary. You don't need classes to have types.
         | 
         | Using classes also leaves entire segments of type-expression on
         | the table or makes them unwieldy e.g.
         | https://en.wikipedia.org/wiki/Tagged_union
         | 
         | So it's orthogonal, classes are just one possible
         | implementation of types.
        
         | eyelidlessness wrote:
         | Depending on the language yes, or probably, or definitely not.
         | A class is one of several ways to represent a type. Some
         | languages have structural type representations and a
         | class/instance is equivalent to a "plain old ___ object"; some
         | languages have types but no notion of classes at all.
        
           | bjourne wrote:
           | Sure but the requirements given by the author are exactly
           | represented by, for example, Java's class system. So why is
           | the author dreaming about something that has existed for 20+
           | years?
        
       | cardanome wrote:
       | Oh god, please just use primitive types. Don't make assumptions
       | about things.
       | 
       | Everyone thinks they are so smart validating emails, phone
       | numbers, zip codes and all until their great design goes live and
       | they discover that users in the real world do not follow their
       | assumptions.
       | 
       | I have seen that happen again and again. No, if your idea of
       | validating an email is more complicated than "should have an @
       | symbol", I guarantee you, there is counter example that will mess
       | you pretty system up. Have fun scrambling to fix that ticket.
       | 
       | Oh you think you know how an address and zip code should look
       | like? No, you don't.
       | 
       | Please people, just use strings and call it a day. Why do you
       | like to suffer?
       | 
       | I mean, sure, using the type system to protect you from mixing up
       | units can be useful. Everything in moderation. Primitive types
       | are good types.
        
         | RHSeeger wrote:
         | Just because there are cases where over-validation (email
         | address being the primary example) can be a problem doesn't
         | mean that _no_ type validation is useful. There are many, many
         | places where validation-beyond-primitives is useful, or even
         | just types that need actions taken on them before they can be
         | used in place of another type. One simple one is "non-negative
         | integers", not commonly provided as a primitive type but _very_
         | common to need to be able enforce. Complex numbers are another.
         | Normal strings vs HTML strings (need to be properly handled
         | before they can be output to a front end). The list is endless,
         | and custom types can provide a _lot_ of safety.
        
         | blackbrokkoli wrote:
         | Thank you, I feel like I am going insane here.
         | 
         | About 90% of stuff I ever worked does email things here or
         | there, and I _never_ had the desire for some hyper complex
         | abstraction with 10 pages of documentation just to store email.
         | I also never experienced a bug that would have been fixed by
         | this.
         | 
         | I mean if the user is putting in some bullshit there, the basic
         | validation of type="email" or pendant if I am not working in
         | HTML is gonna tell him that and after that he does or does not
         | get his verification email. Boom, problem solved along with
         | other one's.
         | 
         | Need just the domain? Well I'll use a damn 1 liner function
         | with split() or something for this hyper specific use case,
         | don't encumber me with the probably faulty abstraction of email
         | some library designer cooked up 8 years ago just for this?!
         | 
         | I do use types when they are actually convenient, but these
         | types-are-the-best-everywhere-all-the-time articles keep
         | failing to convince me...
        
         | revskill wrote:
         | To me, type is for data abstraction, like we use generic for
         | function abstraction.
         | 
         | Abstraction in this case, mean, for future changes, i just need
         | to change in one place, the Email type abstraction instead of
         | searching and replacing every ussage of primitive email string.
        
         | hgomersall wrote:
         | The point is not about validation, it's about conveying
         | semantic information through types. It's perfectly valid to
         | have an email type that is just a wrapper around a string. The
         | advantage is now you and all your functions unambiguously know
         | that the type represents an email (whatever that means) and not
         | a frobinator.
        
         | chii wrote:
         | > if your idea of validating an email is more complicated than
         | "should have an @ symbol", I guarantee you, there is counter
         | example that will mess you pretty system up.
         | 
         | unless you are writing an email server. Then you would need to
         | do this properly.
         | 
         | In other words, validate the data that is in the domain of your
         | application. If your app simply _sends_ email, then it's not
         | your domain, and don't need to validate, as long as the
         | receiving end of the email (aka, the email server) accepts it.
        
         | AnimalMuppet wrote:
         | Ahem. Not all email addresses have an @ in them. If you're
         | sending an email to another user on the same machine, just
         | their username is enough (at least for some mail
         | implementations).
         | 
         | Note that this makes your overall point stronger, not weaker.
        
           | draw_down wrote:
        
         | hiAndrewQuinn wrote:
         | My favorite "easy win" find from learning Haskell last year was
         | just the `newtype` keyword, which basically just let you alias
         | primitive types with zero runtime impact.
         | 
         | `newtype Email = Email String` and `newtype Username = Username
         | String` are just `String`s with guard rails.
        
           | yakshaving_jgt wrote:
           | You probably want smart constructors, as you probably don't
           | want the possibility for a 10,000 character username to float
           | through your system.
        
           | nerdponx wrote:
           | Python's type annotation system has this feature too:
           | from typing import NewType              UserId =
           | NewType('UserId', str)              user_id: UserId
           | user_id = "abc"          # Type check error         user_id =
           | UserId("abc")  # OK              # At runtime, a NewType is
           | the identity function         s1: str = "abc"         s2:
           | UserId = UserId(s1)         assert s1 is s2
           | 
           | Docs: https://docs.python.org/3/library/typing.html#newtype
           | 
           | Runtime: https://tio.run/##TU49D4JADN37K544CIkLupk4OBoTXXQ2CK
           | dcDHeXts...
           | 
           | Type checking: https://mypy-
           | play.net/?mypy=latest&python=3.10&flags=show-er...
        
         | rowanG077 wrote:
         | You are talking about validation not types.
        
         | kajaktum wrote:
         | What? Would you rather have JSON as a string or as something
         | like Map<string, JsonValue>?
         | 
         | > No, if your idea of validating an email is more complicated
         | than "should have an @ symbol"
         | 
         | Then how is just using a string any better? Would you rather
         | litter the entire codebase with validations that this is indeed
         | a valid email or just do it once at the entry point?
         | 
         | I think excessive type system hacks are bad too but they are
         | more often than not the problem of the language where its
         | unable to express certain concepts naturally (see C++ template
         | hacks).
        
           | jstimpfle wrote:
           | > What? Would you rather have JSON as a string or as
           | something like Map<string, JsonValue>?
           | 
           | This question can't be answered without additional context.
           | What are the requirements?
           | 
           | But as a heads up, Map<string, JsonValue> couldn't be
           | representation of all valid JSON documents. Lists are valid
           | Json as well AFAIK. So I think this is making a point.
        
             | Izkata wrote:
             | Based on the casing JsonValue is an object, not a
             | primitive, so there's no reason that interface wouldn't
             | work. It would just by a different subtype depending on
             | what type the value is, and lists/dicts would also
             | implement the Map<string, JsonValue> interface.
        
               | jstimpfle wrote:
               | As much as I can just happily ignore JSON because I don't
               | do webdev, I think there is a strong case to make that
               | lists should be implemented using a native list, array,
               | or vector class. But I won't argue because it's not
               | necessary. The possibility for argument already proves my
               | point.
               | 
               | My point is to show that "the type system" provides
               | endless rabbit holes, and often is just a waste of time.
               | There are a lot of situations where you want to represent
               | a JSON document as a string. (Trivial example, as an
               | embedding in an HTTP response object).
        
               | Izkata wrote:
               | > The possibility for argument already proves my point.
               | 
               | Was just correcting something wrong:
               | 
               | > But as a heads up, Map<string, JsonValue> couldn't be
               | representation of all valid JSON documents.
               | 
               | So,
               | 
               | > As much as I can just happily ignore JSON because I
               | don't do webdev
               | 
               | I think this is why you're going wrong here. JSON is
               | pretty much a solved problem, and Map<string, JsonValue>
               | is pretty close to how it's used in Java and other
               | languages that don't have native types that match its
               | structure (like javascript and python do).
               | 
               | > (Trivial example, as an embedding in an HTTP response
               | object)
               | 
               | This isn't json, it's what you get when you
               | stringify/dumps/convert the json into a different
               | datatype. When you need to be specific it's often called
               | a "json string".
        
               | jstimpfle wrote:
               | JSON is a data-interchange format, i.e. a specification
               | how to serialize and deserialize a domain of values. So
               | Map<string, JsonValue> "isn't JSON" either.
               | 
               | JSON isn't a solved problem, it's a solution to a problem
               | (and often used as a non-solution to a non-problem).
               | 
               | But none of that was my point.
        
               | afiori wrote:
               | JSON is often interpreted as dictionary but is serialized
               | as key-value pairs, for JSON itself this is not a big
               | problem as everybody agrees not to produce JSON documents
               | like _{ "a":1,"b":2,"a":3}_ so most people do not care
               | about how their parser reads them.
               | 
               | In cases like URL queries or HTTP headers it is not such
               | a clear cut. There it is common both to use duplicated
               | keys and to use JSON-like dictionaries to read them.
               | 
               | Personally I never had bugs due to this: PHP does the
               | "right" thing with duplicated keys and I never
               | encountered it in node, but it bugs me that we use this
               | kind of lossy[0] representations.
               | 
               | [0] in JS in particular the object are not really
               | adequate to be used as dictionaries, inheritance and
               | predefined keys aside there are also special magical
               | attributes that behave in special ways
               | _Object.getPrototypeOf(Object.assign({}, JSON.parse(
               | '{"__proto__":null}')))===null;_
        
           | jstimpfle wrote:
           | > Would you rather litter the entire codebase with
           | validations that this is indeed a valid email
           | 
           | Why would you? Just use it. For most of parts of the program
           | it's not relevant to the computation whether the string is a
           | "valid email", whatever that means. It's a string.
        
             | rowanG077 wrote:
             | "Just use it" is what lead to the big SQL injection fallout
             | and even today we pay the price as not even a year ago
             | thousands of crucial service were vulnerable via log4j
             | because of the "Just Use It" mantra.
        
               | jstimpfle wrote:
               | You don't understand what I said.
               | 
               | I say, don't make assumptions unless you need them.
               | Formatting an email address in an HTML document would
               | work by wrapping it in the appropriate tag. (which
               | implies html-quoting it correctly, but that is unrelated
               | to email syntax).
               | 
               | Sending an email using an API would work by passing the
               | email as a string to the API.
               | 
               | Looking up an email address from an address book using a
               | pattern to match would be implemented with normal text
               | search.
               | 
               | It doesn't matter if the email is "valid" or not. Don't
               | overthink it.
        
               | xigoi wrote:
               | You don't avoid injection attacks by validation, but by
               | escaping.
        
               | rowanG077 wrote:
               | escaping requires validation. You don't know what to
               | escape if you aren't allowed to validate.
        
               | jstimpfle wrote:
               | You don't know what you're talking about. For example,
               | HTML escaping (a.k.a quoting) rules don't care what
               | you're escaping - an email, a street name. It's just
               | text.
               | 
               | And that's the point of it. You quote precisely _because_
               | the container syntax doesn 't know the syntax of what
               | you're embedding. If it knew, there would be no need of
               | the escaping.
               | 
               | It's called abstraction.
        
               | rowanG077 wrote:
               | HTML escaping requires you to look for characters to
               | escape for it to not interfere with HTML. This is quite
               | literally validating symbols in the input. If they fail
               | validation they need to be escaped. It's literally
               | IMPOSSIBLE to do escaping without first validating every
               | single symbol in the input.
        
               | xigoi wrote:
               | You seem to have a weird definition of "validation".
        
               | afiori wrote:
               | SQL injection, XSS, and similar attacks are about
               | incorrectly encoding embedded fragments.
               | 
               | The solution is either not to embed (SQL parameter
               | bindings) or to always escape embedded fragments based on
               | the embedding context
               | 
               | For reference: The Last XSS Defense Talk - Jim Manico -
               | NDC Porto 2022 | https://youtu.be/wRC7jyhTkEM
        
         | tel wrote:
         | There's a sharp distinction between validation and typing. I
         | can cast a string into a domain-specific Email type without
         | validating the string. I can also reject a string due to a
         | validation rule without changing its type.
         | 
         | Opaque type aliases are great because they impose semantics.
         | Even if I don't know how to validate an email, it's nice, at
         | times, to distinguish a string which I suspect to be an email
         | from all other sorts of strings.
        
           | persimmonster wrote:
        
         | noobermin wrote:
         | People are getting lost about your example as they don't see
         | the analogy as it sounds like validation of input. The point is
         | still there if people are willing to look past the confusion,
         | that making a type hierarchy requires you clearly know upfront
         | what different subsets of the data you deal with actually look
         | like and this will change at different stages of the project.
         | This is the reason people ditched c++ and java in 2000 and
         | started hacking in python and js, at least at the beginning
         | although things are shifting.
         | 
         | The thing is a lot of things are reversed here, the reality is
         | the way evolution is more sensible is to "generalize" from the
         | bottom up, unless you are very sure from the start that certain
         | types categories make sense for your problem. That's how things
         | should always be, you can only generalize once you've worked
         | through a problem and realized certain aspects are actually
         | common in someway and can be connected somehow, and thus you
         | can take a subset of data you have in your program and organize
         | it into a type. The thing is I feel like that is even harder
         | than thinking top-down (which I feel like is where the pendulum
         | is swinging now) because that takes time.
         | 
         | The reason top-downers still do their thing is they feel their
         | method is better is selective thinking (sorry, I know this is
         | harsh) because they honestly discount the anger and fury people
         | from the outside have dealing with their code and they ignore
         | the amount of refactoring they need to do when their designs
         | break. In fact, they love the churn really or at least merely
         | accept it as a "part of development."
        
         | dwheeler wrote:
         | Using specialized types can be useful, but if it provide s
         | methods they need to be correct for ALL situations.
         | 
         | Here's a subversion of GitHub's authentication (now fixed)
         | where they assumed that "lowercasing domain name using English
         | case rules is always fine and produces the same result" led to
         | a vulnerability: https://dev.to/jagracey/hacking-github-s-auth-
         | with-unicode-s...
        
           | afiori wrote:
           | TIL that punycode breaks _email.split( '@')_ to get the
           | domain:
           | 
           | Apparently _John@Github.com_ normalizes to _xn--
           | john@gthub-2ub.com_ but _Github.com_ normalizes to _xn--
           | gthub-n4a.com_.
           | 
           | For now I will go back to forgetting that emails not covered
           | by /[A-Za-z0-9.-+_]+@[A-Za-z0-9.-_](.[A-Za-z0-9.-_])*/ exist
           | to preserve my sanity but this does confuse me.
        
         | hansvm wrote:
         | That sounds like an argument against excess validation rather
         | than against types (maybe because of blog-driven development
         | showing minimal examples of the power of types?).
         | 
         | Picking on one of those points, suppose emails are just
         | strings. What then prevents them from getting used as names,
         | identifiers, and other unrelated data? Just your continued
         | vigilance as a programmer and hoping that nobody ever
         | carelessly names the field "address" so that mistakes can slip
         | by over a series of devolution commits.
         | 
         | You might not know much about what an email is, but you know
         | _something_ about how you expect them to behave and be used,
         | and if you like the computer to automate your work it's not
         | totally unreasonable to rebind the string type as an email type
         | and require explicit conversions at the point of use to treat
         | it as anything other than just an email. There's zero runtime
         | cost, it's not much more code, and that class of bugs is
         | greatly reduced except for at the boundaries of the system.
         | 
         | Maybe that effort isn't worth it, or maybe your domain changes
         | fast enough you'd have a lot of churn, or maybe those bugs
         | aren't too important, or whatever. Using types to help you
         | reason about the things you do know and do care about can be a
         | huge productivity boost though, so I wouldn't just write the
         | whole technique off.
        
           | indymike wrote:
           | > That sounds like an argument against excess validation
           | rather than against types (maybe because of blog-driven
           | development showing minimal examples of the power of types?).
           | 
           | A lot of developers confuse types and validation.
        
         | skybrian wrote:
         | Unless you're really going to use textareas with scrollbars for
         | every string and store everything in unlimited length database
         | fields, you probably do want to distinguish between single-line
         | and multiline strings and have some limit on their lengths.
        
         | dividedbyzero wrote:
         | > No, if your idea of validating an email is more complicated
         | than "should have an @ symbol", I guarantee you, there is
         | counter example that will mess you pretty system up.
         | 
         | Requiring b2b users to use their @company.com email is common
         | and some b2b customers actually expect to be able to configure
         | that. Another simple case is stripping out any "+whatever" from
         | gmail addresses and to flag that sort of thing for other
         | eomains so support can verify it's not a case of someone
         | creating 72 trial accounts. Yes, rejecting users due to naive
         | and incorrect validation is bad, but treating emails as
         | entirely opaque strings isn't always an option either.
        
           | xigoi wrote:
           | > Another simple case is stripping out any "+whatever" from
           | gmail addresses and to flag that sort of thing for other
           | eomains so support can verify it's not a case of someone
           | creating 72 trial accounts.
           | 
           | trial1@mypersonaldomain
           | 
           | trial2@mypersonaldomain
           | 
           | trial3@mypersonaldomain
        
       | sanp wrote:
       | Question: isn't a rich type system as described here similar to
       | OOP?
        
         | masklinn wrote:
         | The essay uses a class-oriented type system (they're clearly a
         | .net developer), but the same ideas very much exist in non-OO
         | type systems.
         | 
         | And the richest and most expressive type systems are arguably
         | specifically non-OO. Nor are OO languages necessarily
         | statically typed (Smalltalk, Self, Python, Ruby, Javascript,
         | ...)
        
         | Smaug123 wrote:
         | No no no, entirely not. OOP need not be statically typed at
         | all; see Smalltalk.
        
           | remram wrote:
           | Or Python, or JavaScript.
           | 
           | The reverse is true, you can have static type-checking
           | without OOP, like C, Rust, Haskell.
        
             | tsimionescu wrote:
             | I feel compelled to note that C only has the lightest
             | possible amount of type checking, if even that. For
             | example, the following program compiles:
             | #include <stdio.h>       void foo(double* c) {
             | printf("%g", *c);       }       void bar() {
             | printf("bar");       }       int main() {         int x =
             | 9;         int* y = &x;         foo(x); //warning
             | foo(y); //warning         bar(1.0); //not even a warning
             | }
        
         | miskin wrote:
         | I think it is related to how you organize your data structures
         | and business logic - you may have rich typed model that models
         | specific domain, but OOP would typically call to include all
         | the business logic that modifies attributes of class to be part
         | of that class - eg, you do not externally set specific values
         | to class instance, but instead execute some action on the class
         | instance that may change these attributes. If you use type
         | system just to model data structures and have external business
         | logic to use/change it, it would be called anemic model.
        
       | jmull wrote:
       | Why is it better to have two email types, VerifiedEmail and
       | UnverifiedEmail vs. one Email type with an "isVerified" field?
       | 
       | One type is probably going to align with storage and transport
       | better, and you probably mostly want to treat verified and
       | unverified email addresses the same except for some very specific
       | situations. (E.g., maybe only your EmailBlaster cares, where it's
       | like a privilege: some can send to unverified emails and some
       | can't.)
       | 
       | This seems like a bad for types to me.
       | 
       | (It's all code you write and data you design -- types are just
       | one tool... you need to think about they best tool, not get
       | fixated on one, no matter nice it is.)
        
         | kajaktum wrote:
         | It's so that when you are 50 function calls deep you don't have
         | to remember if you are handling a verified email or not. This
         | is the same problem I have with "sum types" as implemented in
         | languages that don't have algebraic data types. You either have
         | a massive struct that contains mostly nullable values or
         | something like a tagged union.
         | 
         | >verified and unverified email addresses the same except for
         | some very specific situations.
         | 
         | This is when typeclasses are a useful concept (or interfaces).
         | Instead of designing your function around a concrete type, you
         | can codify that the caller needs to provide any types that
         | satisfy some requirements. For example, if the function only
         | cares that the input can be treated as a string, you can ask
         | for something like `As<String>`. Then, the caller can provide
         | literally anything as long it implements `As<String>`.
         | 
         | > types are just one tool
         | 
         | Indeed, types are just a tool. But it is a much better tool.
         | Its an electronic shaver instead of rusty axe. This is my
         | biggest gripe with "simple and small languages". More often
         | than not, you just end up writing more verbose and complicated
         | code just to compensate.
         | 
         | To illustrate, I wanted to implement `INCR KEY VALUE` from
         | Redis.
         | 
         | ```
         | 
         | item, exists := db.keys[key]
         | 
         | if !exists { return }
         | 
         | value, ok := item.Value().( _string)
         | 
         | if !ok { return }
         | 
         | intValue, err := strconv.ParseInt(_value, 10, 64)
         | 
         | if err != nil { return }
         | 
         | intValue++ // <== this is literally the only meaningful work I
         | am doing
         | 
         | db.keys[key] = intValue;
         | 
         | ```
        
         | jmull wrote:
         | (Replying to my own post)
         | 
         | Wow, lots of great, thoughtful replies, thanks!
         | 
         | But I'm not convinced...
         | 
         | Maybe it's just a bad example (this is what the main article is
         | about, though)... Generally speaking, you need to be able to
         | send emails to both verified and unverified emails. The
         | difference is in what the email you are sending is about.
         | That's why VerifiedEmail as a type doesn't make a lot of sense
         | to me.
         | 
         | You'll need sendToUnverifiedEmail(email: UnverifiedEmail) and
         | sendToVerifiedEmail(email: VerifiedEmail), and have code to get
         | the right type to pass to the right function the in the right
         | circumstance...
         | 
         | You've got the same potential for getting this stuff wrong,
         | whether you express it in a type or in imperative code or
         | however you express it.
         | 
         | Replies are generally assuming the type part of the code is
         | bug-free and the imperative part of the code is not, which just
         | isn't reasonable.
         | 
         | Also, the static vs. runtime stuff is irrelevant to this
         | example: the verified status of an email address is a runtime
         | property that _cannot be known at compile time_ (OK, I 'm
         | assuming you don't hard-code verified email addresses). I.e.,
         | due to a bug, a value of type VerifiedEmail could be created
         | for an email address that is not really verified. Then, your
         | static checks for VerifiedEmail don't help you at all.
         | 
         | Further, "verified" vs. "unverified" is really a business
         | concern around when it's OK to send certain kinds of emails to
         | the address. It has a fairly standard definition, but there are
         | qualifiers for email addresses that are at least as important
         | to a business that aren't. E.g, did the owner of the email
         | address opt in to marketing emails? Or opt out? Or did not
         | express a preference (yet)? Are you going to have types for
         | those? You'd have to have 3 X 2 types to encode that... that's
         | (verified, unverified) X (opted-in, opted-out, didn't specify)
         | types. Then your business adds a new kind of email,
         | securityAlerts, so now you've got (verified, unverified) X
         | (opted-in, opted-out, didn't specify) X (gets-security-alerts,
         | doesnt-get-security-alerts). Oh wait, some emails shouldn't go
         | to banned people. So now you've got: (verified, unverified) X
         | (opted-in, opted-out, didn't specify) X (gets-security-alerts,
         | doesnt-get-security-alerts) X (banned, not-banned). And you
         | need a "send" for each type, called at the right spot.
         | 
         | So...
         | 
         | Are the types really helping here?
        
           | hither_shores wrote:
           | > You'll need sendToUnverifiedEmail(email: UnverifiedEmail)
           | and sendToVerifiedEmail(email: VerifiedEmail), and have code
           | to get the right type to pass to the right function the in
           | the right circumstance...
           | 
           | Only if you're using a language with an insufficiently strong
           | type system (e.g. Java, C#)
           | 
           | in typescript:                   type UnverifiedEmail = {
           | address: string, verified; false }         type VerifiedEmail
           | = { address: string, verified; true }         ...
           | type Email = UnverifiedEmail | VerifiedEmail | FooBarBazEmail
           | const sendToEmail = (email: Email): Promise<void> = ...
           | 
           | in Haskell:                   class SendTo t where
           | sendTo :: t -> IO ()              newtype Email = Email
           | string              instance SendTo Email where
           | sendTo (Email address) = ...              newtype
           | VerifiedEmail = VerifiedEmail Email deriving (SendTo)
           | newtype FooBarBazEmail = FooBarBazEmail Email deriving
           | (SendTo)
           | 
           | > I.e., due to a bug, a value of type VerifiedEmail could be
           | created for an email address that is not really verified.
           | Then, your static checks for VerifiedEmail don't help you at
           | all.
           | 
           | Of course they do - they tell you that the bug is in the
           | verification code, and not in any of the thousands of lines
           | of business logic separating it from the place where the
           | error was found.
        
         | blandflakes wrote:
         | I think you understood the use, but value the safety less than
         | I do:
         | 
         | > maybe only your EmailBlaster cares, where it's like a
         | privilege: some can send to unverified emails and some can't
         | 
         | A boolean flag is strictly inferior, because it is a runtime
         | check. You can only ever be sure that you're processing
         | verified emails at runtime, and there's no way to _require_
         | that the code guards against that in all places. If they 're
         | different types, you can't even pass an unverified email to
         | code that needs verified emails, so you eliminate the entire
         | possibility at compile-time.
        
         | tel wrote:
         | It's because you can write these types:
         | recognize : String -> UnverifiedEmail         validate :
         | UnverifiedEmail -> VerifiedEmail         send : (VerifiedEmail,
         | Message) -> ()
         | 
         | You can then use visibility controls to universally guarantee
         | that recognize and validate must be called before send. No test
         | can ensure this is true.
         | 
         | Under the presumption that send should only perform work on
         | verified emails, the alternative is not being able to be
         | confident that emails passed to send are pre-verified. This
         | means that send must check this flag, and therefore have the
         | ability to fail due to non-verification.
         | 
         | This isn't inherently a problem, but it can lead to a failure
         | to separate concerns. If one part of your system is responsible
         | for parsing and validation and a separate part responsible for
         | interacting with the sending machinery, it's unfortunate if the
         | latter part can fail due to a failure to verify the email.
         | These systems have now implicitly shared responsibility.
         | 
         | You can try to guarantee that no email is passed from the first
         | system to the second without being verified, but this can be
         | challenging. It's a universal property. Tests can show the
         | presence but not the absence of bugs.
         | 
         | But those types we showed at the beginning provide exactly that
         | guarantee.
        
         | throway232lasdf wrote:
         | > Why is it better to have two email types, VerifiedEmail and
         | UnverifiedEmail vs. one Email type with an "isVerified" field?
         | 
         | You obviously have no idea what a type is.
         | type VerifiedEmail = { email: string; is_verified: true; };
         | type UnverifiedEmail = { email: string; is_verified: false; };
        
           | jmull wrote:
           | This is expressing the same value in two different ways.
           | That's worse, not better.
        
           | Smaug123 wrote:
           | In fairness this takes an unusually strong type system to
           | express, doesn't it? Typescript can do it, but I don't think
           | e.g. Haskell98 can do it out of the box in an analogous way?
           | (Of course, it's hard to prove a negative and I'm not super
           | familiar with Haskell, but my evidence is that I'm pretty
           | certain F# can't.)
        
             | hither_shores wrote:
             | > but I don't think e.g. Haskell98 can do it out of the box
             | in an analogous way?
             | 
             | This is basically the runtime representation of `data Email
             | = Verified { email :: string } | Unverified { email ::
             | string }`, but promoting `Verified` and `Unverified` to
             | type level requires an extension:                   data
             | Email (verified :: bool) where              Verified ::
             | string -> Email true              Unverified :: string ->
             | Email false
        
         | greymalik wrote:
         | Type correctness can be verified at compile time while Boolean
         | values can't.
        
       | dustedcodes wrote:
       | Hey author here! Sorry I didn't respond to any feedback yet. I've
       | literally posted this before leaving my house and didn't think it
       | would get many upvotes as it didn't get any votes the other day
       | either.
       | 
       | Sorry that the general sentiment is "everything old gets new
       | again". I didn't try to rehash some old news again. I basically
       | blog about things that come up in my daily work life and this
       | topic was something that I felt quite passionately about. From my
       | own experience I felt that type systems, especially in modern
       | languages, are not nearly as well utilised as they could be. Of
       | course there is always a balance to strike, especially with over
       | engineering and needless optimisations, but that is a topic for
       | another blog post another day.
        
         | zbentley wrote:
         | Don't let it get you down. HN sentiment often trends grumpy
         | when someone makes a point that's been made before. That
         | doesn't mean it's not important to restate, extend, elaborate
         | on, modernize, and recontextualize ideas!
         | 
         | There are almost 8 billion people on this planet; most claims
         | echo prior statements to some degree.
         | 
         | I found your article practical, short, and largely accurate;
         | which is to say: I liked it. I think it could be improved with
         | either an edit or followup which links to similarly-inclined
         | articles, papers, or talks that discuss the topic, so folks can
         | deepen their understanding of the role of type systems in day-
         | to-day programming and PLT.
        
       | mikewarot wrote:
       | I once attended a meeting where a Professor from a University
       | somewhere in Chicago gave a brilliant demonstration of using a
       | similar type system for dealing with values in Electrical
       | Engineering. It made quite sure you couldn't do things like add
       | volts and amps.
       | 
       | [Edit] it also handled things like parallel resistances, etc.
       | 
       | It was in C++ if I recall correctly.
       | 
       | This is a great idea, that I've haven't had cause to use yet.
        
         | masklinn wrote:
         | FWIW that's a pretty basic application called "units of
         | measure". Some languages like F# have that natively.
        
           | mrkeen wrote:
           | I wish static typing were as uncontentious as units of
           | measure.
        
             | masklinn wrote:
             | I wouldn't say that UOM are uncontentious, things can get
             | dicey around reference units and precision for instance, or
             | the combinatorial explosion of composite units.
        
               | mrkeen wrote:
               | Right, but if you told your professor you sometimes
               | represent distance in Volts (to make your calculations
               | simpler) you'd get some funny looks.
               | 
               | You could even double-down on it: "Have there been any
               | studies that prove that using units of measure helps you
               | get the right answer?"
        
               | tsimionescu wrote:
               | Funnily enough, there are some applications for
               | converting mechanics problems to analogous electricity
               | problems to leverage circuit simulation software such as
               | PSPICE to help solve things like transcendental
               | equations.
        
               | hither_shores wrote:
               | Yes, but the mapping doesn't change the relationship
               | between the units of measure, which is the actual meaning
               | as far as the type system is concerned. It's just a
               | change of names.
        
               | tsimionescu wrote:
               | Sure, I wasn't meaning to detract from the comment above,
               | just to point out what I thought was an interesting
               | related fact.
        
         | mdm12 wrote:
         | Unit of measures are a great example of what a type system can
         | do, and something not enough languages support. F#[1] and
         | Scala[2] are two that I know of that do support UOMs. Like you,
         | I haven't had the need to use them in the domains I work in,
         | but I imagine that they would be invaluable in certain
         | contexts.
         | 
         | [1] https://learn.microsoft.com/en-us/dotnet/fsharp/language-
         | ref...
         | 
         | [2] https://github.com/typelevel/squants
        
           | docandrew wrote:
           | Packages exist for Ada, too: http://archive.adaic.com/tools/C
           | KWG/Dimension/Physical_units...
        
           | throwawaymaths wrote:
           | It's also something that some languages seriously screw up.
           | Consider multiplying a time (which is typed in go) with a
           | numerical value... suppose what I want is a user to input
           | number of time intervals to wait. So the user wants 5, and
           | the interval is 2500 milliseconds. The way you get 12500
           | milliseconds out of that made me want to throw my computer
           | out the window.
        
             | Chinjut wrote:
             | I don't understand. What should you get instead?
        
               | morelisp wrote:
               | Go does not have operator overloading, and numeric
               | operators must have identical types. So if you have `var
               | x int = 5` and `var t time.Duration = 2500 *
               | time.Millisecond`, you have to `time.Duration(x) * t` or
               | `time.Duration(x * int(t))`.
               | 
               | It's slightly better than languages with no operator
               | overloading nor newtypes at all (well, actually a lot
               | better given other things you can use newtypes for) but
               | without operator overloading using it just for units,
               | with no other API machinery, is usually a bad idea.
        
               | xigoi wrote:
               | The commenter you're replying to expressed it
               | confusingly. The point is that in Go, 5 *
               | time.Milliseconds(2500) is a type error, and instead you
               | need to do time.Nanoseconds(5) * time.Milliseconds(2500).
        
               | morelisp wrote:
               | `5 * time.Milliseconds(2500)` is not a type error, though
               | `int(5) * time.Milliseconds(2500)` is.
               | 
               | (This is especially relevant because you really mean `5 *
               | (2500 * time.Millisecond)` vs. int(5) * (2500 *
               | time.Millisecond)`, as there is no `time.Milliseconds`
               | function.)
        
               | xigoi wrote:
               | Thanks, I don't remember exactly how it worked. It
               | doesn't take away from the stupidity.
        
           | leephillips wrote:
           | Julia has this via a package: Measurements.jl.
        
         | hgomersall wrote:
         | It's possible to go even further than just protecting against
         | the wrong unit in the wrong place. You can generalise units:
         | https://docs.rs/dimensioned/latest/dimensioned/
         | 
         | (Edit, that's not possible in c++)
        
           | jdrek1 wrote:
           | > (Edit, that's not possible in c++)
           | 
           | Mind expanding on that? Because what that readme there shows
           | is absolutely possible in C++, I have used a similar system
           | for dealing with natural units.
        
             | hgomersall wrote:
             | I'm happy to admit I'm wrong on that. My understanding is
             | the bit that makes (kg.m)/s^2 type equivalent to a N,
             | equivalent to J/m is not implementable in the same generic
             | way.
        
               | pencilguin wrote:
               | All unit systems decompose each unit to primitives, that
               | can then be aliased for notational convenience. So, a
               | result of J/m decomposes the same as a N.
        
         | photochemsyn wrote:
         | This approach to typing could have saved the Mars Climate
         | Orbiter, i.e a pounds of force type vs. a newtons type.
         | 
         | > "A NASA review board found that the problem was in the
         | software controlling the orbiter's thrusters. The software
         | calculated the force the thrusters needed to exert in pounds of
         | force. A separate piece of software took in the data assuming
         | it was in the metric unit: newtons.... Propulsion engineers,
         | like those at Lockheed Martin who built the craft, typically
         | express force in pounds, but it was standard practice to
         | convert to newtons for space missions. One pound of force is
         | about 4.45 newtons. Engineers at NASA's Jet Propulsion Lab
         | assumed the conversion had been made, and didn't check."
         | 
         | https://www.wired.com/2010/11/1110mars-climate-observer-repo...
         | 
         | There could be issues with memory use, but it could also be
         | implemented as an API, i.e. ensuring values exported from one
         | software package to another were of the correct type, but then
         | store them internally as simple types... Switching everything
         | to a unified metric system would make more sense in the long
         | run, however.
        
           | est wrote:
           | More like an i18n or l10n issue than types.
        
             | morelisp wrote:
             | I think you've inadvertently stumbled on another great
             | example, distinct types for TranslatedMessage,
             | LocalizedNumber, etc. from ordinary string has been a
             | cornerstone of localization enforcement on at least two
             | large applications I've worked on.
        
         | tsimionescu wrote:
         | Note that most such solutions break (or become much much much
         | more complex) if you want anything more than simple arithmetic
         | from them. For example, matrix multiplication with typed values
         | (where each element of the matrix can have a different
         | type/unit of measure) is extremely ugly code, and basically no
         | such library supports it - even for matrices of fixed size
         | (say, code that could multiply 3x3 matrices with 9 type
         | parameters - which is not unrealistic in physical simulations).
         | 
         | This is a fundamental limitation of types - they tend to scale
         | poorly to very complex non-uniform structures. That's not to
         | say that they shouldn't be used when they do scale nicely,
         | though!
        
           | tremon wrote:
           | _matrix multiplication with typed values (where each element
           | of the matrix can have a different type /unit of measure)_
           | 
           | Is this really a common occurrence? In most situations I've
           | come across, it's the matrix itself (rows/columns) that has a
           | unit of measurement, not the invididual columns. Tensors,
           | rotation matrices, lighting maps: they all use the same units
           | of measurement.
        
             | tsimionescu wrote:
             | Matrix multiplication is often used for solving systems of
             | linear equations, and you often have systems of linear
             | equations involving different physical quantities (such as
             | position, speed, time and mass if solving some classical
             | mechanics equations, or pressure, volume, temperature, and
             | time for thermodynamics etc).
             | 
             | And while it may be relatively common to start out and end
             | up with matrices that have a single unit for each row (but
             | different units on different rows), intermediate results
             | will often end up with different combinations of units in
             | each element.
        
         | wirthjason wrote:
         | I'm not sure the Chicago reference but if you're talking about
         | C++ the Units library is a good option.
         | 
         | https://github.com/mpusz/units
        
       | shaboinkin wrote:
       | I'm working in a codebase that has, at times, 10+ different
       | expressions within a single conditional in many places, and
       | trying to pull out the context of why the conditional exists in
       | the first place make grug brain hurt. At the very least, you
       | could put all of the expressions and assign to a boolean with a
       | variable name saying wtf it is you're conditioning on.
       | 
       | https://grugbrain.dev/#grug-on-expression-complexity
        
       | henrydark wrote:
       | > ... to prevent silly mistakes like multiplying $100 with PS20
       | 
       | Honest question, what should multiplying $100 with $20 give?
        
         | jstimpfle wrote:
         | There isn't a reason why you shouldn't write something like
         | $100 * (PS20 / PS47) as "int dollars = 100 * 20 / 47;". Note
         | that this expression assicates to the left instead of the
         | right, which can be the right thing to do if doing integer
         | arithmetic. But it would not work with a strongly typed setup
         | as in your example.
         | 
         | In my experience trying to prevent accidental mistakes is a
         | waste of time and often makes our lives miserable. Catching the
         | rare bug by doing complicated work in the type system when it
         | would have been easy to find in normal code anyway is not worth
         | it.
        
           | xigoi wrote:
           | But why would you use integer arithmetic when dealing with
           | fractions?
        
             | jstimpfle wrote:
             | It was just the first example from the top of my head. The
             | expression above calculates the right thing, cast to int.
             | In general, prescribing which units we can multiply and
             | which not, is extremely silly if you consider how we learn
             | it in school. You can multiply anything and everything,
             | simply take care of the units. There isn't an obvious
             | reason why we couldn't have 2000 dollar-pounds as a
             | transient value in a longer computation.
             | 
             | The real problem is that most type systems aren't fit to
             | track the units automatically. Solution: Don't beat
             | yourself up, track the units in your mind / in comments /
             | in variable names instead of the type system. And just get
             | it right. It's not that hard - if you mix something up
             | that's usually the type of bug that is immediately noticed
             | and fixed.
        
         | yakshaving_jgt wrote:
         | An error at compile time.
        
         | usea wrote:
         | A type error.
        
         | musingsole wrote:
         | Programmers will have immediate answers for you -- stated
         | confidently as if to imply there is a spec somewhere when in
         | fact no spec exists and the programmer you're talking to is
         | peddling their own bullshit as gold.
        
           | creata wrote:
           | A dollar times a dollar is a dollar squared. You don't need a
           | spec for that!
           | 
           | For example, if you have a random variable that's in dollars,
           | its variance would have units of dollars squared. People
           | consider the variance of dollar estimates all the time.
        
             | henrydark wrote:
             | And dollars times pounds is similarly a unit of covariance
        
         | [deleted]
        
         | zasdffaa wrote:
         | Pounds sterling
        
         | Smaug123 wrote:
         | 2000 square dollars? If I'm choosing between ways to spend
         | capital so as to improve the efficiency of a process, and that
         | process currently produces five widgets per dollar, then the
         | quantity I'm comparing to choose between my courses of action
         | can be measured in widgets per square dollar.
         | 
         | Hiring a better engineer for more money may create an
         | efficiency improvement of 1 widget per dollar, with an outlay
         | of $1k extra for the better engineer, giving a total gain of
         | 0.001 widgets per square dollar; hiring a worse engineer for
         | much cheaper may represent an improvement of 0.1 widgets per
         | dollar, at an outlay of $1, giving 0.1 widgets per square
         | dollar.
         | 
         | Perhaps not the most intuitive unit, but it's not impossible.
         | (Though since you can even measure it in dollar-sterling if you
         | like, I suppose that doesn't make it a counterexample to "stop
         | multiplying dollars by sterling".)
        
           | 323 wrote:
           | 2275 square dollars would be more correct, since GBP/USD is
           | 1.1372 right now.
        
             | blep_ wrote:
             | Look closer at the signs. The person above agreed USD * GBP
             | was weird and wrong, but disputed that USD * USD was any
             | better.
        
           | an1sotropy wrote:
           | Is this getting downvoted? bummer.
           | 
           | You have provided a real example, which I was looking for, of
           | why one might need to express a square dollar; thanks.
           | 
           | I wonder if the people who want to argue "types save you from
           | bugs" see your example as very unwelcome, since they'd want
           | to use "squared dollars" as an example of something
           | nonsensical that should be flagged as a type error. I hope
           | those people can reflect rationally on the limits of type
           | systems in the real world.
        
             | usea wrote:
             | I also enjoyed the square dollars example.
             | 
             | A type system is merely a tool to encode information to
             | help better model things. If you want to prevent
             | multiplying dollars together, types can help you do that.
             | If you want to enable multiplying dollars together, types
             | can help you do that, too.
        
             | Spivak wrote:
             | But it shouldn't be an error in any unit system.
             | # oops my scaler has a unit        x unit * x unit = x
             | unit^2
             | 
             | The value isn't catching _this_ line of code since it's
             | potentially valid. It's catching the line of code where you
             | pass the result to a function that expects unit.
        
               | an1sotropy wrote:
               | I agree, but the original article at dusted.codes hopes
               | types will "prevent silly mistakes like multiplying $100
               | with PS20".
               | 
               | I don't know what that author would think of multiplying
               | $100 with $20, but my point is that this embrace of type
               | systems is apparently not just about function interfaces;
               | it also includes the operands of things like
               | multiplication, and preventing that operation if the
               | types are fishy.
        
               | Izkata wrote:
               | For better or worse hopefully they think the two examples
               | the same.
               | 
               | Using the example above, "multiplying $100 with PS20" can
               | be achieved just by making the better engineer a remote
               | employee paid in pounds. It adds the exchange rate into
               | the mix, so the math will change over time, but
               | conceptually the math is the same as the "dollar *
               | dollar" example.
        
         | masklinn wrote:
         | The same as multiplying 100 radishes by 20 radishes.
        
         | remram wrote:
         | They probably meant _adding_
        
         | Emigre_ wrote:
         | Computer says no
        
         | Ygg2 wrote:
         | If both values are decimals? 2000.
        
       | yellowapple wrote:
       | > I want that data type to have helpful methods such as .Domain()
       | or .NonAliasValue() which would return gmail.com and
       | foo@gmail.com respectively for an input of foo+bar@gmail.com.
       | 
       | No the hell you don't.
       | 
       | Please please _please_ do not attempt to separate the alias from
       | an email address I submit. It 's there for a reason -
       | specifically, to hold you accountable if I experience a sudden
       | influx of spam, and generally to keep things categorized in a
       | world where senders can be sending things from all sorts of
       | domains. Knowing that this is something one would even _remotely
       | consider_ is grounds to never touch anything one has built with a
       | ten-foot pole, and I am now very strongly inclined to look into
       | the author and compulsively scrub any accounts of mine from
       | anything said author might 've touched.
       | 
       | I am not exaggerating. The thing before the @ is meant to be
       | opaque. Deeming otherwise for the sake of something so blatantly
       | user-hostile as removing aliases is plain evil, and I will not
       | sugarcoat my condemnation of such practices.
       | 
       | If you're sufficiently sociopathic to have no regard for the
       | morality argument here, then at the very least take heed of RFC
       | 5322 (https://datatracker.ietf.org/doc/html/rfc5322) and
       | recognize that trying to parse any meaning from an email address'
       | local-part is blatantly ignorant of IETF specifications and
       | almost certainly will create bugs. Just don't do it - if not for
       | your users' sake, then for your own.
        
         | pencilguin wrote:
         | True enough, as far as it goes. But if you are concerned about
         | subscribing to something twice, you may want to try to check
         | delivery uniqueness. They might be _your own_ addresses.
         | 
         | Of more interest to me, omitted from the presentation--as
         | _almost_ always--is anything about what is disliked about a
         | malformed address. You see this when some web form says it
         | doesn 't like your address, but won't say why, leaving you to
         | guess and try things until it is satisfied.
         | 
         | Another example is the password filter that idiotically demands
         | "at least one capital letter, one digit, and one swear
         | character" in your already several-word passphrase, and
         | dislikes your choice of swear characters but won't say so.
        
           | kazinator wrote:
           | > _Another example is the password filter that idiotically
           | demands "at least one capital letter, one digit, and one
           | swear character" in your already several-word passphrase, and
           | dislikes your choice of swear characters but won't say so._
           | 1> (jp-hash "correct-battery-horse-staple")
           | "Pyochu1ponu*fuson"
           | 
           | https://addons.mozilla.org/firefox/addon/jp-hash/
        
           | xigoi wrote:
           | > But if you are concerned about sending an e-mail to the
           | same address twice, you need to check delivery uniqueness.
           | 
           | For one, you shouldn't be concerned about that, and for two,
           | you can't tell delivery uniqueness anyway, since someone can
           | have multiple completely different addresses going to the
           | same inbox.
        
           | yellowapple wrote:
           | > But if you are concerned about subscribing to something
           | twice
           | 
           | I'm concerned about some service collecting my email address
           | and "accidentally" exposing it to spammers.
           | 
           | > Of more interest to me, omitted from the presentation--as
           | almost always--is anything about what is disliked about a
           | malformed address. You see this when some web form says it
           | doesn't like your address, but won't say why, leaving you to
           | guess and try things until it is satisfied.
           | 
           | That is indeed yet another reason why you should _never ever_
           | try to parse meaning from email addresses you do not own.
        
         | nickporter wrote:
         | I bought a domain that forwards *@example.com to my personal
         | email address. Easy to set up on google domains.
         | 
         | This ensures everything before the @ is opaque, i.e.
         | _foo+bar@gmail.com_ is now _bar@foo.com_
         | 
         | Services that block my domain are usually the ones that also
         | block _foo+bar@gmail.com_
        
         | kinkrtyavimoodh wrote:
         | > "recognize that trying to parse any meaning from an email
         | address' local-part is blatantly ignorant of IETF
         | specifications and almost certainly will create bugs"
         | 
         | I am sorry but this makes no sense. You do realize that the
         | only reason you are able to use aliases is because your email
         | provider chooses to parse meaning out of the supposedly
         | "opaque" text right? If your email provider is free to "break"
         | the spec, so are people you give your id to.
        
           | kazinator wrote:
           | > _If your email provider is free to "break" the spec, so are
           | people you give your id to._
           | 
           | There is no reasoning behind this argument; it is purely a
           | verbal construct memetically derived from some inapplicable
           | equality ethic that might make sense in a completely
           | unrelated situation.
           | 
           | The correct application of ethics is that someone agency who
           | is given abc+def@gmail.com, and infers from it that this
           | gives them permission to send email to abc@gmail.com (or,
           | worse, sell that address to harvesters) is behaving
           | unethically.
        
           | [deleted]
        
           | yellowapple wrote:
           | And that is solely the business of myself and my email
           | provider. It's my email address, and therefore I am within my
           | rights to assign whatever internal meaning I so choose. It is
           | absolutely _not_ the business of someone sending an email
           | whether or not that opaque text has further-parseable
           | meaning, and pretending otherwise absolutely _will_ cause
           | bugs (say, when sending emails to mailservers which _don 't_
           | use that alias syntax).
           | 
           | EDIT:
           | 
           | > If your email provider is free to "break" the spec, so are
           | people you give your id to.
           | 
           | Wrong. See above. The email provider is free to "break" the
           | spec because it is the thing in control of that email address
           | and can therefore process it as it sees fit. The people to
           | whom I give an ID are not my email provider, and therefore do
           | not have the same degree of control; consequently, attempting
           | to parse meaning from that opaque string _will cause bugs_ ,
           | and also is a dick move which _will not be tolerated_.
           | 
           | If you're defending this practice because you, too, are
           | parsing the opaque components of email addresses which you do
           | not control, then I will take note to look into your code
           | contributions as well and avoid anything you've touched.
           | 
           | Do. Not. Parse. The. Local-part. For. Aliases. Full stop.
           | It's _my_ email address, not yours. Respect how I enter it,
           | or else remove it from your system entirely. Anything
           | different is asking for bugs and is blatantly disrepsectful
           | to users.
        
       | LeicaLatte wrote:
       | Swift does this exceptionally well and always has your back.
        
       | mhaberl wrote:
       | >A string value is not a great type to convey a user's email
       | address or their country of origin.
       | 
       | So we have a type for "country of origin". And then some country
       | that you have in the records splits up into 2 countries, what do
       | you do then? Do you keep a list of all countries that ever
       | existed and keep it up to date?
       | 
       | This approach works good in some cases, but not always
        
         | tremon wrote:
         | What would be a use case for having a type for "country of
         | origin" rather than a type "country"?
         | 
         |  _Do you keep a list of all countries_
         | 
         | Why would you assume that once you create a type "country", it
         | must explicitly enumerate all possible countries?
        
         | yafbum wrote:
         | This problem has already been solved. Use something like
         | https://en.m.wikipedia.org/wiki/ISO_3166-1_alpha-2 and get an
         | authoritative list of countries / territories, including
         | defunct ones like Yugoslavia and USSR. You still have to define
         | the business logic of whether you want to keep a certain
         | country record aligned with historical borders or with current
         | borders, but that problem exists whether you have primitive
         | type to represent countries or not...
        
         | Ygg2 wrote:
         | That's an unrelated problem, that's outside the scope of model
         | presented. Remember the physicists adage: All models are wrong,
         | some are useful.
         | 
         | You can have your name change as well, or your calendar can
         | change, or etc.
         | 
         | Does that mean we stringly type everything? No. You model
         | changes either via a separate field(s) or some kind of change
         | table.
        
           | mhaberl wrote:
           | > You can have your name change as well, or your calendar can
           | change
           | 
           | What do you mean by that?
           | 
           | Having a type for "country of origin" would mean that the
           | type gives you limits on what values it can hold (any country
           | known to ever exist) so you can not say something like:
           | 
           | Country c = "Foo"
           | 
           | because Foo is not a country.
           | 
           | I can't imagine having a type for a persons name that holds
           | checks anything but perhaps a strings length, certainly not a
           | list of all possible names.
           | 
           | The calendar example I don't get. We already have "date"
           | types in almost all languages so that "works", although it
           | can be used as example of how hard is to implement some
           | types.
           | 
           | -----
           | 
           | So I say: >> This approach works good in some cases, but not
           | always
           | 
           | And you say: > Does that mean we stringly type everything?
           | 
           | Come on now. "Not always" does not mean "Never"
        
             | mostlylurks wrote:
             | Restricting the space of possible values is only one of the
             | possible advantages of declaring a dedicated type for
             | something. Even if it is not possible to restrict the space
             | of possible values (e.g. with names, which can't
             | realistically be restricted to any smaller subset than all
             | possible strings), there are other advantages to having a
             | dedicated type, such as preventing the user of the type
             | from putting a value of that type into somewhere it doesn't
             | belong, which is a very realistic scenario in stringly
             | typed codebases, especially where there are similar but
             | different sets of values, all stringly typed.
        
             | masklinn wrote:
             | > I can't imagine having a type for a persons name that
             | holds checks anything but perhaps a strings length,
             | certainly not a list of all possible names.
             | 
             | As always, that is very domain-specific: there are lots of
             | countries with naming laws, some of which do have lists of
             | legal names.
        
               | blep_ wrote:
               | Legitimate question: what do they do about immigration
               | into those countries from countries without such laws?
        
               | masklinn wrote:
               | Likely nothing: usually they're laws which apply to
               | parents / birth certificates.
               | 
               | I guess it's possible that an immigrant trying to get
               | naturalised would have to adopt a "legal" name for the
               | country, but I'm not aware of any country where that's a
               | rule, aside from the Zairianisation movement of Mobutu.
        
             | Ygg2 wrote:
             | > What do you mean by that?
             | 
             | I assumed the point was old name wasn't tracked and should
             | be.
             | 
             | > Having a type for "country of origin" would mean that the
             | type gives you limits on what values it can hold
             | 
             | No one said your type has to be completely set in stone and
             | contain every valid value in advance.
             | 
             | Have a trusted store of country name and valid until date.
             | So defunct country can't be added.
        
           | musingsole wrote:
           | It's a very related problem.
           | 
           | I agree: All models are wrong; some are useful.
           | 
           | A string is a wrong model for an email address. But it's a
           | pretty useful one.
           | 
           | A custom type sitting lonely in an isolated codebase IS ALSO
           | A WRONG MODEL. Arguably, it might be more a useful one than a
           | string. But that's debatable.
           | 
           | And on that debate, I'll argue a string is a better model
           | because it is a better UNDERSTOOD model by more PEOPLE than
           | whatever MyEmailClassForThisProject you just came up with.
        
             | Ygg2 wrote:
             | > It's a very related problem.
             | 
             | It's not. You have the same problem regardless of type you
             | place there.
             | 
             | > A string is a wrong model for an email address. But it's
             | a pretty useful one.
             | 
             | Depends on use case. Perhaps it's an overkill in this toy
             | example.
             | 
             | I've real life use cases with untrusted user input where
             | having raw string as untrusted and some kind of verified
             | type as trusted would eliminate whole swath of errors.
        
         | Smaug123 wrote:
         | Is the alternative to sweep it under the rug? In a stringly-
         | typed world, what happens - do you just hope for the best? In a
         | typed world, the problem ("the real world has ceased to conform
         | to the model") is at least made plain so that you can _decide_
         | what to do with it, because the model is actually... modelled.
        
           | musingsole wrote:
           | In a stringly-typed world, you still end up with a purpose-
           | tuned model of what an email is, how it's used, and what
           | error cases are -- these just aren't implemented as qualities
           | on a type.
           | 
           | There's a dozen approaches for it, many from the functional
           | programming paradigm. But an example of one approach would be
           | that your consumer functions become responsible for
           | interrogating the data they'll act on -- through assertions
           | or other verification means.
           | 
           | Comparing to the real world: my metal foundry doesn't yell if
           | it gets a non-metal Type of material. But, the logical
           | process it follows (heating to 1000+ *C) takes care of all
           | but a handful of corner cases when you give my foundry the
           | wrong data type.
           | 
           | The choice to wrap all the logic into a "Type" and then get
           | mad when the model logic exists elsewhere is just a choice.
           | And a weird one people get VERY OPINIONATED about.
        
             | Smaug123 wrote:
             | I guess I'm only opinionated about it because I'm 100% not
             | smart enough to get it right unless something stops me
             | getting it wrong. ("It" can be pretty much anything here.)
             | It's why I'm such a terrible Python programmer. The foundry
             | analogy is spot on - there's nothing to stop me throwing my
             | grandma in, so at some point you can bet I accidentally
             | will.
        
               | gilbert_vanova wrote:
               | > I'm 100% not smart enough to get it right unless
               | something stops me getting it wrong
               | 
               | IMO, type systems are harsher on modeling mistakes than
               | something like Python is. Sure, you'll get it wrong the
               | first time (sorry Grandma!). And in Python, you can
               | mutate your system rapidly into a new state that can
               | accommodate the old model's mistaken assumption. If your
               | program starts getting complex enough that the mutation
               | speed is dropping -- deconstruct it into smaller,
               | manageable problems.
               | 
               | Humans are 100% not smart enough to build systems the way
               | a lot of corporate shops keep trying to.
        
       | docandrew wrote:
       | I have to plug Ada's rich type system for explicitly encouraging
       | this kind of design. With things like type predicates [1], you
       | can do run-time enforcement or even prove at compile-time (to
       | optimize away the runtime checks) that type constraints are met.
       | 
       | As an example of this, in a piece of code I'm working on there's
       | a Base64_String type, where only RFC 4648 characters are
       | permitted to be part of the string, the '=' padding character can
       | only appear at the end of the string, and if the second-to-last
       | padding byte is '=' then the last one must be as well. This is
       | all enforced by the type system without having to call
       | "validate()" or something every time its used.
       | 
       | 1. https://learn.adacore.com/courses/intro-to-
       | ada/chapters/cont...
        
         | runeks wrote:
         | That's definitely cool, but for everyday programming I'd
         | consider this a waste of time.
        
           | pyjarrett wrote:
           | I agree that it sounds really stupid up front, but it's done
           | when you're just you modeling the constraints of the problem.
           | I've found that it saves a lot of time in debugging and silly
           | mistakes later.
           | 
           | For types with invariants, you just add the `Invariant`
           | aspect and then the type invariant gets checked automatically
           | when passed as a parameter. Combined with built-in pre/post
           | conditions, I've found that these sort of automatically
           | inserted checks give me a lot of confidence, and allow
           | significant embedding of conceptual and domain knowledge
           | during development.
        
             | zbentley wrote:
             | If you like this style of programming but use Python for
             | your day-to-day, check out typeguard; it provides runtime
             | assertions for parts of the Python type annotation system
             | similar to "Invariant".
             | 
             | As with many tools, there are caveats. It's often
             | surprisingly slow (so avoid using it on hot paths, or only
             | turn it on during your testing/pre-production runs) and
             | can't type-check everything (e.g. callables). But it's
             | still pretty nice and requires minimal effort to use!
             | 
             | https://pypi.org/project/typeguard/
        
         | pyjarrett wrote:
         | Not just that, but it also often does so efficiently and
         | doesn't incur a runtime penalty (for new type and static
         | predicates) and will reuse previous function definitions as
         | well.
         | 
         | These are the sorts of cases with function parameters in
         | various languages other language I've dealt with, in which this
         | would have helped:
         | 
         | - "dt": delta time of what? Seconds, milliseconds,
         | microseconds, nanoseconds, ticks? Usually, I'd expect seconds
         | if it was a float, though I've seen counter-examples, and I
         | usually have to trace back the flow to know for use if it's a
         | 64-bit (u)int.
         | 
         | - "ip_addr" and "port": What's the type of port? If you guessed
         | "int", you'd be right in part of the system. If you guessed
         | "string" you'd be right in a different part of the system.
         | 
         | - "path": Does it matter if this is a relative or absolute
         | path? It often isn't apparent this matters and then you find
         | out this path is passed to a different system in which it does
         | matter.
        
           | skrtskrt wrote:
           | I just got done detangling various ip addrs, ports, and paths
           | being passed from Go to C bindings. Not fun
           | 
           | Rust having builtin IP address types (and libraries actually
           | using them) is long overdue for mainstream programming
           | languages
        
         | allisdust wrote:
         | How does this get enforced for string content changed at
         | runtime? Or does this apply for only to strings initialised in
         | code.
        
           | pyjarrett wrote:
           | You can enforce newtype on strings, or also use a dynamic
           | predicate which checks at runtime.
           | 
           | https://ada-lang.io/docs/arm/AA-3/AA-3.2/#324--subtype-
           | predi...
        
       | activitypea wrote:
       | >4 hours ago
       | 
       | Ah, you reposted this, hoping the weekend crowd might be kinder.
       | Cheeky lad.
        
         | gnabgib wrote:
         | Based on the history of dusted submissions[0]... he's a bit of
         | a serial offender
         | 
         | [0]: https://news.ycombinator.com/from?site=dusted.codes
        
           | zbentley wrote:
           | Perhaps, or perhaps the poster is a beneficiary of the
           | (useful IMO) "do you want to post this again? We felt that
           | it's good but didn't get visibility this time around"
           | moderator outreach tradition.
        
             | yellowapple wrote:
             | In my experience with that tradition, I've found that the
             | moderators will just add it to the second-chance queue
             | instead of asking the poster first.
        
       | Barrin92 wrote:
       | _> A string value is not a great type to convey a user's email
       | address or their country of origin. These values deserve much
       | richer and dedicated types_
       | 
       | this is a classic case of not needing more types but needing
       | _proper names_. Types as concretions, i.e. simply collections of
       | data or functions are a terrible idea because they 're static and
       | don't accrete. Data in the real world always does. This becomes
       | very obvious when you go down a paragraph and you see the
       | conundrum:
       | 
       |  _> For example, let's have a second type called
       | VerifiedEmailAddress. If you wish it can even inherit from an
       | EmailAddress. I don't care, but ensure that there is only one
       | place in the code which can yield a new instance of
       | VerifiedEmailAddress_
       | 
       | okay, and for the next email setup let's have a third type, and a
       | fourth type, and a fifth type, and so on. The end result of this
       | is a zoo of types that help nobody to understand anything. It
       | reminds me of an older Rich Hickey talk. When you program a
       | delivery truck you don't make a type for each different truck
       | because of the contents of the truck, you just take your delivery
       | out of the truck and you don't care about the rest.
        
         | hither_shores wrote:
         | > this is a classic case of not needing more types but needing
         | proper names.
         | 
         | Those are types.
        
           | Barrin92 wrote:
           | no. a type is a description of a set of values and its
           | associated operations. Types impose _global_ meaning on
           | entities in your program. When something belongs to a certain
           | type receivers of arguments of that type lose control over
           | how to interpret them. Thus types introduce coupling.
           | 
           | Names are just labels attached to an entity for the purpose
           | of identification and readability, they don't impose meaning.
        
             | hither_shores wrote:
             | newtype ArbitrarilyLabeled x = ArbitrarilyLabeled x
             | forgetLabel :: ArbitrarilyLabeled x -> x
             | forgetLabel (ArbitrarilyLabeled x) = x
             | 
             | What's the "global meaning" of `ArbitrarilyLabeled`? What
             | control has `forgetLabel` lost?
        
         | nezirus wrote:
         | Dunno, but adding verified attribute with a setter which
         | executes verification step looks simpler to me and covers 80%
         | of the use cases.
        
         | mrkeen wrote:
         | No, there should only be the one EmailAddress type. If it's not
         | valid, it's not an EmailAddress.
         | 
         | Does having an EmailAddress type guarantee you won't
         | accidentally accept crap? No, but when you get it wrong, you
         | edit the validation in one place in the system.
        
           | ReflectedImage wrote:
           | If that place is the EmailAddress type, then you have built
           | your system wrong. You check that stuff when the data enters
           | the system.
        
             | kgeist wrote:
             | > You check that stuff when the data enters the system.
             | 
             | There can be N entrypoints where data enters the system
             | (different controllers, CLI), so you must always remember
             | to validate emails in N places, otherwise broken data could
             | end up being passed to business logic. Data can also be
             | constructed inside the system. It's nice to have one
             | centralized place where email is validated. Placing it in
             | the constructor of a special type and using only that type
             | for email guarantees it's impossible for business logic to
             | receive invalid emails in principle, no matter what you do,
             | because when an exception is thrown from a constructor no
             | object is created at all. No object = no invalid data to
             | deal with. You know that when you see EmailAddress (or any
             | other type where state is validated in the constructor)
             | it's in a valid state, there's no ambiguity, and, in my
             | opinion, it's also more readable than just some string, the
             | intent is clearer.
        
             | mrkeen wrote:
             | If you can construct an EmailAddress, then _you have a
             | valid EmailAddress_. That 's the point.
             | 
             | If an EmailAddress can be a valid or invalid email address,
             | then just leave it as a String (since that can also be a
             | valid or invalid email address).
             | 
             | > You check that stuff when the data enters the system.
             | 
             | Yes
             | 
             | > If that place is the EmailAddress type, then you have
             | built your system wrong.
             | 
             | No
             | 
             | If you validate & construct an EmailAddress from another
             | external class, that means external classes are free to
             | bypass validation and construct an invalid EmailAddress.
             | Putting the validation/construction inside EmailAddress
             | lets you force construction to go via validation.
        
               | Jtsummers wrote:
               | My advice: Relax and don't argue. People who don't
               | understand that constructing an EmailAddress type is also
               | _validating_ the raw email string (in this case) will
               | never understand it. They 'll remain convinced for a very
               | long time, possibly the rest of their lives, that they
               | know better. That passing a string around is fine as long
               | as either you always validate it everywhere (yes, kill
               | your performance, that's smart) or that they validated it
               | once and they pinky swear to never change the value and
               | to always call validate before passing it into the
               | system.
               | 
               | Let them find subtle errors in their programs over time,
               | it's job security for them. They don't want to move on to
               | new and more interesting things they just want to keep
               | fixing the same shit for the rest of their careers.
        
         | mixedCase wrote:
         | > you just take your delivery out of the truck
         | 
         | Sorry, I accidentally took the delivery out of the email. You
         | made them both have a deliver_to(address) method, you spent
         | most of your comment talking about emails and the computer
         | surely didn't stop my underslept human self from confusing an
         | email address from a physical one.
        
           | Barrin92 wrote:
           | then you should complain and check what's in your mail. The
           | fact that a delivery method is generic isn't a problem,
           | delivering things from A to B is a generic task. The
           | recipient of the packet handles the content, the deliverer
           | doesn't care what's in the box. _deliver_to_ ought to be
           | reusable, there shouldn 't be 50 versions of it.
           | 
           | When we send json over the wire do we rewrite methods
           | globally to make sure we're all in sync about the content?
           | No, you as the message recipient make sure that what you got
           | makes sense and how to deal with it.
        
             | mixedCase wrote:
             | > then you should complain and check what's in your mail
             | 
             | Now you've wasted time and resources.
             | 
             | > deliver_to ought to be reusable, there shouldn't be 50
             | versions of it.
             | 
             | Reality almost never pans out that way.
             | 
             | > No, you as the message recipient make sure that what you
             | got makes sense and how to deal with it.
             | 
             | Indeed! May I introduce you to typed parser combinators?
        
       | djha-skin wrote:
       | The most successful languages are typed but weakly so. Just
       | enough type system to avoid the biggest class of bugs, not enough
       | to get in your way all the time. Golang strikes this balance very
       | well. Too little typing, and your Python unit tests get too heavy
       | to run after every commit. Too much, and you have to read a book
       | on category theory before you can figure out how to grab that one
       | field using Lenses in Haskel.
       | 
       | Edit: I should note this is coming from an outside observer as my
       | most favorite languages are dynamically typed like Python and
       | Lisp. But it should also be noted that I like writing in small
       | code bases. Larger ones tend to need typing.
        
         | fulafel wrote:
         | I think ascribing PL popularity to striking the right tradeoff
         | in this respect is leaping a bit far. It's compatibility and
         | familiarity with predecessors, marketing dollars, etc. They
         | tend to have C++ style syntax for example which is a similar
         | path-dependence-formed quirk of history.
        
         | hgomersall wrote:
         | Python is strongly typed. The weakness (such as it is) in
         | python is that in historic idioms the type is ignored in favour
         | of the interface (duck typing).
        
         | pfdietz wrote:
         | I want to point out that in practice, Common Lisp is also to
         | some extent statically typed. The compiler will issue warnings
         | if there are forms that it can determine (at compile time)
         | would cause type errors at runtime. It's common practice to not
         | accept code unless these errors are eliminated (one can even
         | set up your compile system to abort when they are found.)
         | 
         | What it will not do is reject the program unless it can confirm
         | that every expression will not cause a type error.
        
           | Jtsummers wrote:
           | That's dependent on the implementation, SBCL is particularly
           | good at it. Others may just let things pass like:
           | (defun foo () (* 1 "aoeu"))
           | 
           | In SBCL gives me this:                 ; in: DEFUN FOO
           | ;     (* 1 "aoeu")       ;       ; caught WARNING:       ;
           | Constant "aoeu" conflicts with its asserted type NUMBER.
           | ;   See also:       ;     The SBCL Manual, Node "Handling of
           | Types"       ;       ; compilation unit finished       ;
           | caught 1 WARNING condition
           | 
           | But in CCL it gives me no warnings at all and only triggers
           | an error at runtime.
        
       | deltasevennine wrote:
       | Types got a bad wrap because of C++. There was a strange
       | dichotomy between languages like python/javascript and C++. If
       | type systems were so good why was it easier to program with
       | javascript and python then with C++? People got confused and
       | promoted dynamically typed languages as better.
       | 
       | What many people didn't realize was that C++ was hard DESPITE the
       | type system, not because of it. This was soon rectified with type
       | script which eventually caused a complete flip of opinion in the
       | industry once javascript developers realized how much better it
       | is.
       | 
       | The other question to this equation is why was python so easy to
       | program for DESPITE not having a type checker (it has external
       | type checks now, but I'm saying before this)?
       | 
       | The answer is deterministic errors and easy traceability. If you
       | have an error that happens either at runtime or at compile time
       | you want to easily know what the error is, where it came from,
       | and why it occurred. Python makes it VERY easy to do this. Not
       | all type checkers make this easy (see C++).
       | 
       | In actuality type checking is sort of sugar on top of it all imo.
       | Rust is great. But really the key factor to make programming more
       | productive is traceability. Type checking, while good is not the
       | key factor here.
       | 
       | Think about it. Whether the error occurs at runtime or compile
       | time is besides the point. Compile time adds a bit of additional
       | safety, but really if an error exists, it will usually trigger at
       | some point anyways.
       | 
       | The thing that is important is that when this error occurs
       | whether compile time or runtime you need as much information
       | about it as possible. That is the key differentiator.
       | 
       | That is why typeless python and typed rust, despite being
       | opposites, are relatively easy to write complex code for when
       | compared to something like C++.
        
         | kaashif wrote:
         | > Whether the error occurs at runtime or compile time is
         | besides the point. Compile time adds a bit of additional
         | safety, but really if an error exists, it will usually trigger
         | at some point anyways.
         | 
         | Well, if the error is at compile time, there's no chance that
         | code makes it to production and affects customers.
         | 
         | If the error is at runtime, you need to have tested that edge
         | case and if you haven't, there could be customer impact.
         | 
         | I mean, once you see a few TypeErrors in Python code with no
         | type annotations, or a few NullPointerExceptions in Java where
         | there's no compile time null checking by default, I think it
         | becomes very clear that catching things at compile time is much
         | better...
        
         | pencilguin wrote:
         | The above, while carefully tailored to tickle HN biases, has no
         | connection with reality.
         | 
         | Types are exactly equally as "traceable" in C++ as in Rust.
        
       | quechimba wrote:
       | Started a Ruby project using Sorbet and I'm refactoring with
       | confidence now. It's such a huge help. The project is about 9000
       | lines of Ruby by now and I don't think I would have been able to
       | get this far without static type checking
        
         | Jach wrote:
         | The second edition of the book _Refactoring_ was written to use
         | JavaScript instead of Java in part to dispel the myth that you
         | can 't confidently refactor without static types.
        
       | treis wrote:
       | The problem with this is the explosion of types when you start
       | with combinations. Stuff like VerifiedEmailOptedOutOfMarketing.
       | 
       | I can see a type system designed from the ground up to do
       | something like this. But as a bolt on to existing languages it's
       | pretty clunky.
        
         | throway232lasdf wrote:
         | type VerifiedEmailOptedOutOfMarketing = { email: string;
         | is_verified: true; is_opted_out: true; };
        
       | abraxas wrote:
       | I'm learning Python after 35 years of working with statically
       | typed languages (Pascal, C++, Java, a bit of Typescript lately)
       | and by god this is hard. Not because there is anything in the
       | language that I don't understand but the lack of any type info is
       | killing me. I just can't build up a rhythm of coding. I feel like
       | every five lines I have to sprinkle in print() statements to keep
       | track of the data transformations as there is nothing useful that
       | can be captured about it even with these weak ass "type hints". I
       | know that even when I get this crap to work I'll hate going back
       | to that code in a few months as I'll have forgotten what the hell
       | it all did and will have to spike it with print() ad df.shape()
       | again to make sense of it. And naming discipline can only get you
       | so far.
       | 
       | Maybe dynamic typing just isn't my thing and I need a new gig...
        
         | _dain_ wrote:
         | use mypy? it can enforce the type hints.
        
           | maxbond wrote:
           | MyPy is awesome and will make you a more productive
           | programmer and will make your application more robust. But I
           | agree that Python types are "weak ass". It's not a
           | particularly ergonomic type system to use, and it's more
           | difficult to express complex types than is worth it for the
           | sometimes questionable benefit.
           | 
           | 3.10 does add unions using | which is nice, and I expect the
           | type system will get better, but I share these frustrations.
        
         | maxbond wrote:
         | I've moved from Python to a static language and it's made
         | programming enjoyable again. I'd forgotten that that was
         | possible. It's much more productive as well. Using Python as a
         | production application language is like playing operation. For
         | me what I really detest is ambient, untyped (in the sense that
         | they aren't declared in the function definition) exceptions.
         | Exceptions can just happen on any line, and there's no way to
         | know what exceptions a function will raise. So you have to dig
         | into the source code of your dependencies and such, it's a
         | tremendous waste of time and you still get unanticipated
         | exceptions in production.
        
       | gary17the wrote:
       | This is, IMvHO, such old news that it feels... weird to still
       | read about it in a year with the prefix of 20.
       | 
       | Every programmer who has ever single-handedly written a 100,000+
       | LOC software system will tell you the same thing: shift as much
       | responsibility on the compiler as you can and have the compiler
       | check the code you write to any extent technologically possible.
       | 
       | Getting rid of bugs by experiencing, diagnosing and fixing them
       | takes at least ten times more effort than getting rid of bugs by
       | not making them in the first place, through expressing the
       | problem at hand with a strong type system.
       | 
       | When you also consider the never ending necessity to introduce
       | change to an already written software system, thus the necessity
       | to refactor code (in the sense of altering the previously assumed
       | meaning of its idioms), the critical advantage of a strong type
       | system becomes self-evident.
       | 
       | (Yes, Rust 4ev3r! ;))
        
         | benreesman wrote:
         | Based on the author's examples and the JS-ish looking code in
         | the article, maybe PureScript in general and row types in
         | particular, uh, 4ev3r?
        
         | highwaylights wrote:
         | I'd take this further.
         | 
         | I was listening to the John Carmack episode of Lex Fridman from
         | the summer, and he makes a comment about being frustrated that
         | in the Valley there's an almost religious opposition to IDEs,
         | debuggers, and static analysis.
         | 
         | Some of those tools have only become more powerful over time
         | and I'm perplexed as to the mindset that would make a person
         | averse to automating the drudgiest parts of their job in a
         | career that is almost entirely based around automating things.
        
           | posharma wrote:
           | Oh man! why did you bring up IDEs. Now we'll argue ad nauseam
           | about emacs vs vi vs vim vs etc.
        
           | pjmlp wrote:
           | Yeah, the same culture worships using their powerful
           | computers in 2022, the same way I was using Xenix in 1993.
           | 
           | Talk about progress.
        
           | bluGill wrote:
           | I'm adverse to debuggers as i've more than once caught myself
           | following a rabbit hole of steping through code instead of
           | thinking.
           | 
           | IDEs have some use, and static analysis has proven to catch
           | the same mistake over and over, but only as the authors of
           | those tools have discovered that false positives cannot be
           | allowed ever, once there is a false positive the tools is
           | worthless.
        
             | astrobe_ wrote:
             | > I'm adverse to debuggers as i've more than once caught
             | myself following a rabbit hole of steping through code
             | instead of thinking.
             | 
             | Yes. I was kind of forced to think and do printf debugging
             | at the beginning of my career, after having used before
             | that (as a hobbyist) quite good asm debuggers.
             | 
             | Maybe that's just me, but I also was, I believe, a bit
             | over-reliant on the debugger - I would just compile, run,
             | see what happen, and launch the debugger if something
             | didn't work.
             | 
             | Nowadays I could use sometimes a debugger - but the system
             | I work with is sort of soft real-time so stopping at a
             | breakpoint of even slight changes in timings can change the
             | context - in some cases even printf debugging could make a
             | bug vanish.
             | 
             | If nothing else, debugging without a debugger is a good
             | exercise in logically thinking - and it can save time too.
        
               | Gibbon1 wrote:
               | I have a code base that is a mix of hard, soft, and
               | static real time. I have a command line interface built
               | in to it and a lot debug logging that can be re-enabled.
               | 
               | I've also spent some effort into making it tolerate being
               | interrupted. And there is also the good old technique of
               | inserting break points while the code is running.
        
             | icedchai wrote:
             | I find debuggers more valuable with dynamically typed
             | languages, especially Python. It's handy to be able to drop
             | a `breakpoint()` in the middle of a script when you have no
             | idea what a function is actually returning. The happens
             | more often than you might think.
        
               | bornfreddy wrote:
               | Of course, when this happens the bug you are hunting is
               | not your only problem. You really should clean up the
               | code to make it clear what is being returned from the
               | function.
        
             | titzer wrote:
             | Yes yes, indeed. The ability to look at what's happening
             | step by step really hamstrings my imagination. Sorry, no. A
             | debugger is a microscope! It will help you find problems
             | faster and will seed your imagination by filling in what is
             | _really_ going on. It 's an augmentation, like any tool. Or
             | do you prefer to stare into space blindfolded?
        
               | morelisp wrote:
               | > A debugger is a microscope! ... Or do you prefer to
               | stare into space blindfolded?
               | 
               | Perhaps illustrating the original point, microscopes
               | aren't used to stare into space. A debugger is a
               | microscope but the most pernicious bugs don't benefit
               | from such a thing.
        
           | acchow wrote:
           | This must be strictly an old school Valley problem. Up in the
           | city, IDEs are standard. How would you even write Scala at
           | Twitter without an IDE?
        
             | outworlder wrote:
             | How, you ask?
             | 
             | https://scalameta.org/metals/docs/editors/emacs/
        
             | closeparen wrote:
             | There's a pendulum swing back and forth between local
             | compilation being supported or not... currently settling
             | onto VSCode remote, with Jetbrains Gateway trying to catch
             | up in usability.
        
           | noveltyaccount wrote:
           | When I graduated college in the '00s I thought Vim was the
           | most amazing thing I'd ever learned. Then a colleague at my
           | first job showed me what happend when you typed . after a
           | variable name in Visual Studio. Code completion, inline
           | documentation...my mind was blown, and I never looked back.
           | When I meet a young chap extolling the benefits of Vim or
           | Emacs or really anything that doesn't have stepped debugging
           | and code competition...well, there are no bonus points for
           | doing things the hard way.
        
             | sidlls wrote:
             | emacs has plenty of plug-ins that turn it into an IDE. For
             | people who are already comfortable with it, these tools are
             | great. I'm more productive in it than with "modern" tools.
             | For newcomers the learning curve is going to be steeper.
             | I'd recommend learning at least either vim or emacs (or
             | perhaps other text-based editors with similar features, if
             | they exist) though, as it does provide versatility for
             | oneself. Those GUI IDEs aren't available in every
             | environment where one might want to code or debug.
        
             | pcthrowaway wrote:
             | For the people responding to you who are saying you can get
             | all the same things in vim, they're right of course, but a
             | lot of this modern functionality is now built on top of the
             | Language Server Protocol[1], which is an open standard
             | created by microsoft for VS Code.
             | 
             | Kudos on the people who have ported this to Vim[2], but I
             | suspect the support for LSP features will still be better
             | in VS Code
             | 
             | [1] https://microsoft.github.io/language-server-protocol/
             | 
             | [2] https://github.com/prabirshrestha/vim-lsp
        
               | abraxas wrote:
               | Kind of a perpendicular discussion but it amazes me that
               | with this language server thing hipsters turned what was
               | a very mature and proven pattern (a plugin architecture)
               | into a distributed software problem. My god talk about
               | doing shit the hard way...
        
               | quicklime wrote:
               | The plugin architectures that I've seen require plugins
               | to be written in the same language as the IDE. For
               | example, Eclipse plugins need to be written in Java.
               | 
               | Language servers run in a separate process to the editor,
               | and they communicate over JSON-RPC. The nice thing about
               | this is that the language server can be written in any
               | programming language - which usually ends up being the
               | language of the code being edited, rather than the
               | language that the editor was written in.
               | 
               | This makes it a lot easier for language servers to be
               | written and maintained by the people who maintain the
               | compilers for those languages, e.g. gopls is written by
               | the Go developers, clangd is part of LLVM.
        
               | morelisp wrote:
               | LSPs turn a M*N problem into an (ideally) M+N problem.
               | You can't do that with any existing single editor's
               | plugin architecture, basically by definition.
               | 
               | Ctags is a better analogy, and LSP has fairly obvious
               | advantages compared to it.
        
               | jrumbut wrote:
               | Code completion, search, cross referencing, and all sorts
               | of other features in vim, emacs, and all kinds of other
               | editors (including Visual Studio) predate LSP by decades.
               | 
               | LSP is cool though, an advancement certainly, but it is
               | not a completely new thing.
               | 
               | https://en.m.wikipedia.org/wiki/Ctags
        
             | yellowapple wrote:
             | Emacs (with e.g. company) can do much of the same,
             | _without_ my laptop making a fighter jet sound quiet in
             | comparison :)
        
             | outworlder wrote:
             | > When I meet a young chap extolling the benefits of Vim or
             | Emacs or really anything that doesn't have stepped
             | debugging and code competition...well, there are no bonus
             | points for doing things the hard way.
             | 
             | Why do you think they are doing things "the hard way"?
             | Emacs can have all the things you have described. And it
             | can do that for languages that are not part of the .NET
             | Framework, they just have to have a language server
             | implementation.
             | 
             | The problem with "IDEs" was placing all your eggs into a
             | single basket. You are on Visual Studio and then you need
             | to do some Java(or Scala, or whatever). And now you have to
             | get another IDE. Some tried to be one IDE to rule them all
             | (Eclipse, Netbeans), didn't work all that well.
             | 
             | You need a good editor - most IDEs don't have one. You need
             | integration with your language of choice. You need
             | compilers and linters and a bunch of other things. Better
             | to glue components that do one thing and do it well, than
             | having one IDE trying to do everything. And those
             | components can fell just as integrated.
        
               | MrJohz wrote:
               | IntelliJ and VSCode have both worked pretty well with
               | anything I've thrown at them, I'd consider them both
               | fairly successful as IDEs "to rule them all". Obviously
               | they're both very different ends of the IDE spectrum, but
               | they've both had intellisense, debugging, type revealing,
               | and refactoring features for all the mainstream languages
               | I've tried with them. VSCode particularly tends to
               | integrate well with LSPs.
        
               | closeparen wrote:
               | >Why do you think they are doing things "the hard way"?
               | Emacs can have all the things you have described
               | 
               | In the first few years of my career I would try every few
               | months to get these things actually installed and working
               | in emacs. It was definitely the hard way.
               | 
               | >The problem with "IDEs" was placing all your eggs into a
               | single basket
               | 
               |  _Eclipse_ is a uniquely terrible piece of software and I
               | understand why it might polarize people against IDEs for
               | life. The JetBrains products are pretty good and,
               | importantly, modular and consistent enough across
               | languages that I don 't mind.
        
             | titzer wrote:
             | Wait until you learn about these 2002 technologies called
             | "extract method", "inline method", "encapsulate fields",
             | "extract interface", "extract local", "inline local",
             | "rename", plus about 200 code inspections...
        
             | _qua wrote:
             | When I was a kid learning to code, programming books
             | recommended stridently against even using syntax
             | highlighting. It's funny how helpful things get rejected by
             | people who "did it the hard way."
        
             | christophilus wrote:
             | I remember using Visual Studio in college (back in 1999,
             | 2000) and coding circles around folks who were using text
             | editors.
             | 
             | These days, I use Neovim + LSP for a pretty decent
             | approximation of an IDE-- it's quite good. Still not as
             | good as Visual Studio + C#, but I'm on Linux now, and not
             | writing C# anymore, and I definitely prefer an open-source,
             | general purpose, light-weight, customizable editor.
        
             | czx4f4bd wrote:
             | Both Vim and Emacs have plugins for intelligent code
             | completion and viewing inline documentation. I personally
             | prefer to use VS Code or Jetbrains IDEs with Vim emulation,
             | but I've seen setups for both Vim and Emacs that basically
             | made them into full-fledged IDEs.
        
               | SAI_Peregrinus wrote:
               | Full-fledged Plugin-based development environments. An
               | IDE is an _Integrated_ Development Environment; it comes
               | with the features necessary for efficient development
               | built-in.
        
             | tsimionescu wrote:
             | Note that Emacs had these features and more for Lisp since
             | about the 1990s or even earlier, while being free.
             | 
             | Now, for C or Java or most other popular languages, you're
             | absolutely right.
        
             | maxbond wrote:
             | Just so you know, you can do those things in vim now (I
             | do), and the whippersnappers may well be.
        
             | constantcrying wrote:
             | Vim is not _really_ an editor. It is a way to edit text,
             | you can use vim in almost any environment. You are also
             | totally wrong about vim or emacs not having code
             | completion, that is just nonsense.
             | 
             | I use (a version of) visual studio, the first thing that I
             | did was installing a vim extension.
             | 
             | Also vim itself already supports almost any of these
             | features with some basic plugins for the completion logic.
             | If I type "." in my vim I get the same thing I would in
             | visual studio. If you are saying "vim is bad because it
             | lacks X IDE feature" you are missing the point.
        
               | [deleted]
        
               | Our_Benefactors wrote:
               | Vim: I can do that too, I swear! Just configure some
               | plugins, can't tell you what they might be though. But
               | I'm turing complete, and I'm the best!
               | 
               | VScode: Of course I can do autocomplete bud. Here, search
               | my package repo, I'll tell you which plug-ins are the
               | most popular and handle the entire download and install
               | process for you.
               | 
               | There's no comparison.
        
               | constantcrying wrote:
               | Why do you even reply to a post that you didn't bother to
               | read? VScode is a neovim frontend.
               | 
               | >There's no comparison.
               | 
               | Read the first line of my post. There literally is no
               | comparison, because there is a category error.
        
               | idontpost wrote:
        
               | Our_Benefactors wrote:
               | > Why do you even reply to a post that you didn't bother
               | to read? VScode is a neovim frontend.
               | 
               | I did read your post Mr. snark.
               | 
               | > There literally is no comparison, because there is a
               | category error.
               | 
               | Pure pedantry. VScode takes far less effort to get a high
               | quality feature set when compared to vim. That's the only
               | point of debate that matters for most people.
        
               | constantcrying wrote:
        
               | za3faran wrote:
               | I think OP wanted to make a categorical difference
               | between vim-the-editor vs vim-motions. The latter of
               | which are supported in all major IDEs as
               | plugins/extensions.
        
               | yellowapple wrote:
               | Visual Studio: Of course I can autocomplete! Hold my beer
               | while I bring your machine with 16 cores and 64GB of RAM
               | to its knees for multiple minutes ;)
        
               | Our_Benefactors wrote:
               | This is just false. Does it have a higher memory/cpu
               | footprint than vim? Sure. But VScode is plenty
               | performant.
               | 
               | Edit: vscode != visual studio. I'll leave my comment.
        
               | yellowapple wrote:
               | VSCode != Visual Studio
               | 
               | EDIT: I'd also hardly call VSCode "performant", either,
               | at least compared to the multitudes of editors that _don
               | 't_ pull in a full-on browser engine for basic text
               | rendering... but yes, it is indeed "performant" relative
               | to Visual Studio.
        
               | nmarinov wrote:
               | Have you tried the latest couple of releases?
               | 
               | I used to have that issue 5 years ago but since then it
               | opens under 5 seconds and loads the solutions I need for
               | not much more. Currently using it on a cheapish recent
               | windows laptop but even on a 8gb x220 it works fine, just
               | loads for 30 sec in the beginning and then it's smooth.
               | 
               | Granted I'm only opening .net solutions with under 100
               | projects each but anything above that is unnecessarily
               | more difficult to navigate with just vim.
               | 
               | And in my experience VSCode is many times slower than
               | Visual Studio on the same projects. None of them are as
               | fast as barebones sublime or vim on an m1 mac but when I
               | load the latter with plugins it's not a huge difference.
        
             | jlarocco wrote:
             | > When I meet a young chap extolling the benefits of Vim or
             | Emacs or really anything that doesn't have stepped
             | debugging and code competition...well, there are no bonus
             | points for doing things the hard way.
             | 
             | I can't speak for Vim, but Emacs has stepped debugging,
             | code completion, etc.
             | 
             | Part of the reason I use Emacs is that I get those features
             | and (and a ton more) with the same light weight interface
             | across multiple languages and platforms. At the same time,
             | it's usually very easy to make Emacs work with third-party
             | tools. On Linux I can step through code using GDB through
             | Emacs, but at work I spend most of my time in Emacs, but
             | have it bring up the MSVC++ debugger when I need it. It's
             | the best of everything.
             | 
             | Meanwhile, I get to listen to my coworkers celebrate new
             | features in VSCode that I've used in Emacs for years...
             | 
             | There are no bonus points for learning a new IDE for every
             | project, either.
        
         | deltasevennine wrote:
         | But there's a paradox.
         | 
         | Why does 100,000 lines of code of python tend to be safer and
         | more manageable then 100,000 lines of C++ despite the fact that
         | python has no type checker and C++ has a relatively advanced
         | type checker?
         | 
         | Why do startups choose a python web stack over a C++ web stack?
         | 
         | I don't think it's "self-evident." I think there's something
         | more nuanced going on here. Hear me out. I think type systems
         | are GREAT. I think python type hints and typescripts are the
         | way forward. HOWEVER, the paradox is real.
         | 
         | Think about it this way. If you have errors in your program,
         | does it matter that much if those errors are caught during
         | runtime or compile time? An error in compile time is caught
         | sooner rather then later but either way it's caught. YOU are
         | protected regardless.
         | 
         | So basically compile time type checking just makes some of the
         | errors get caught earlier which is a slight benefit but not a
         | KEY differentiator. I mean we all run our code and test it
         | anyways despite whether the system is typed or not so the
         | programmer usually finds most of these errors anyways.
         | 
         | So what was it that makes python easier to use then C++?
         | 
         | Traceability and determinism. Errors are easily reproduced,
         | languages that always display the same symptoms from certain
         | errors and in turn deliver error messages that are clear and
         | are readable. These are really the key factors. C++ on top of
         | non-deterministic segfaults, astonishingly even has compile
         | time messages that can confuse users even further.
        
           | xigoi wrote:
           | > Why does 100,000 lines of code of python tend to be safer
           | and more manageable then 100,000 lines of C++ despite the
           | fact that python has no type checker and C++ has a relatively
           | advanced type checker?
           | 
           | Because C++ sucks, but static types are not to blame for
           | that.
        
           | jjav wrote:
           | > If you have errors in your program, does it matter that
           | much if those errors are caught during runtime or compile
           | time?
           | 
           | Of course it matters. If an error can be caught by the
           | compiler, it will never get to production. Big win.
           | 
           | With typeless languages like python the code will get to
           | production unless you have 100% perfect test coverage
           | (corollary: nobody has 100% perfect test coverage) and then
           | some unexpected moment it'll blow up there causing an outage.
           | 
           | This happens with metronomic regularity at my current startup
           | (python codebase), at least once a month. It is so
           | frustrating that in this day and age we are still making such
           | basic mistakes when superior technology exists and the
           | benefits are well understood.
        
             | deltasevennine wrote:
             | That's fine. A type checker won't catch everything. Run
             | time errors happen regardless. I find it unlikely that all
             | the errors your code base is experiencing is the result of
             | type errors.
             | 
             | Something like c++. You get a runtime errors. You have no
             | idea where it lives or what caused it.
             | 
             | Your python code base delivers an error but a patch should
             | trivial because python tells you what happened. Over time
             | these errors should become much less.
        
               | jjav wrote:
               | > A type checker won't catch everything.
               | 
               | That's a strawman, nobody has claimed a statically typed
               | language will catch all possible errors.
               | 
               | It will however catch an important category of common
               | errors at compile-time, thus preventing them from
               | reaching production and blowing up there. Other types of
               | logic error of course exist, in all languages.
               | 
               | > Something like c++. You get a runtime errors. You have
               | no idea where it lives or what caused it.
               | 
               | I don't know what this means? You seem to be suggesting
               | that code in a statically typed language cannot be
               | debugged? Clearly that's not true. Debugging is in fact
               | usually easier because you can rule out the type errors
               | that can't happen.
        
           | gary17the wrote:
           | > So basically compile time type checking just makes some of
           | the errors get caught earlier which is a slight benefit but
           | not a KEY differentiator.
           | 
           | Unfortunately, I have to completely disagree here, at least
           | based on my experience. Shifting software error detection
           | from runtime to compile time is absolutely paramount and, in
           | the long run, worth any additional effort required to take
           | advantage of a strong type system.
           | 
           | Firstly, writing unit tests that examine all the possible
           | combinations and edge cases of software component input and
           | state is... an art that requires enormous effort. (If you
           | don't believe me, talk to the SQLite guys and gals, whose
           | codebase is 5% product code and 95% unit test code.)
           | 
           | Secondly, writing automated UI tests that examine all the
           | possible combinations and edge cases of UI event processing
           | and UI state is... next to impossible. (If you don't believe
           | me, talk to all the iOS XCUI guys and gals who had to invent
           | entire dedicated Functional Reactive paradigms such as
           | Combine and SwiftUI. ;) J/K)
           | 
           | Thirdly, I don't even want to get into the topic of writing
           | tests for detecting advanced software problems such as memory
           | corruption or multi-threaded race conditions. Almost nobody
           | really seems to know how to write those truly effectively.
           | 
           | > So what was it that makes python easier to use then C++?
           | 
           | The Garbage Collector, which is side-stepping all the
           | possible memory management problems possible with careless
           | C++. However, a GC programming language probably cannot be
           | the tool of choice for all the possible problem domains
           | (e.g., resource-constrained environments such as embedded and
           | serverless; high-performance environments such as operating
           | systems, database internals, financial trading systems, etc.)
        
             | deltasevennine wrote:
             | Your argument makes no sense. I say the type checker is not
             | the key differentiator then you say for python the key
             | differentiator is the garbage collector.
             | 
             | So that makes your statement contradictory. You think type
             | checkers are important but you think python works because
             | of garbage collection.
             | 
             | Either way I'm not talking about the implementation of the
             | language. I'm talking about the user interface. Why is one
             | user interface better than the other?
             | 
             | I bet you if c++ has sane error messages and was able to
             | deliver the exact location of seg faults nobody would be
             | complaining about it as much. (There's an implementation
             | cost to this but I am not talking about this)
             | 
             | Even an ugly ass language like golang is loved simply
             | because the user interface is straight forward. You don't
             | get non deterministic errors or unclear messages.
        
               | za3faran wrote:
               | GC was one of the most important and relevant features
               | (if not _the most_ important) that allowed Java to
               | penetrate, and eventually dominate the space where C++
               | used to be relevant in terms of middleware /business type
               | applications. This detail matters a lot in this
               | discussion. Then once that is taken as a given, you can
               | compare different GC enabled languages based on other
               | factors, such as type safety (or lack thereof in the case
               | of python).
        
               | gary17the wrote:
               | No contradiction, really, it's just that we are talking
               | about two different programming goals: I emphasize the
               | goal of producing well-behaved software (especially when
               | it comes to large software systems), while you emphasize
               | the goal of producing software in an easier (more
               | productive) manner. For my goal, a strong type system is
               | a key differentiator. For your goal, a garbage collector
               | is a key differentiator. The discussion probably comes to
               | down to the question of whether garbage-collected,
               | weakly-typed Python is as "bug-prone" as memory-managed,
               | strongly-typed C++. I have no significant experience with
               | Python, so I cannot answer authoritatively, but I suspect
               | your assumption that "100,000 lines of code of python
               | tend to be safer and more manageable then 100,000 lines
               | of C++" might be wrong. In a large codebase, there will
               | probably be many more dynamic-typing error opportunities
               | (after all, the correct type has to be used for every
               | operation, every function call, every calculation, every
               | concatenation, etc.) than memory-management error
               | opportunities (the correct alloc/dealloc/size has to be
               | used for every pointer to a memory chunk; but only if C++
               | smart pointers are not used).
        
           | icedchai wrote:
           | I think it is simpler than that: C++ is an incredibly complex
           | and verbose language. Most of web development is working with
           | strings, and C++ kinda sucks there. There is also a
           | compilation/build step, so overall productivity is lower.
           | Python is "easier" all the way around (we'll ignore the
           | dependency management/packaging debates.)
           | 
           | It depends on how you define "safer." Run-time errors with
           | Python happen frequently in large programs due to poor type
           | checking _all the time._ Often internal code is not well
           | documented (or documented incorrectly) so you may get back a
           | surprise under certain conditions. Unless you 've have very
           | strict tooling, like mypy, very high test coverage, etc.
           | there is less determinism with Python.
           | 
           | Also, this may come as a surprise, but many people do not run
           | or test their code. I've seen Python code committed that was
           | copy-pasta'd from elsewhere and has missing imports, for
           | example. Generally this is in some unhappy path that handles
           | an error condition, which was obviously never tested or run.
        
             | deltasevennine wrote:
             | I know it happens "all the time" but these runtime errors
             | happen fast and quick. You catch most of these issues while
             | testing your program.
             | 
             | Statistically more errors are caught by python runtime then
             | an equivalent type checked c++ program simply because the
             | python user interface fails hard and fast with a clear
             | error message. C++ on the other doesn't do this at all. The
             | symptoms of the error are often not related to the cause.
             | Python is safer then C++. And this dichotomy causes insight
             | to emerge. Why did python beat c++?
             | 
             | In this case the type checker is irrelevant. Python is
             | better because of clear and deterministic errors and hard
             | and fast failures. If this is exemplary of the dichotomy
             | between c++ and python and if type checkers are irrelevant
             | in this dichotomy it points to the possibility that type
             | checking isn't truly what makes a language easier to use
             | and safer.
             | 
             | The current paradigm is rust and Haskell are great because
             | of type checking. This is an illusion. I initially thought
             | this was well.
             | 
             | Imagine a type checker that worked like c++. Non
             | deterministic errors and obscure error messages. Sure your
             | program can't compile but you are suffering from much of
             | the same problems, it's just everything is moved to compile
             | time.
             | 
             | It's not about type checking. It's all about traceability.
             | This is the key.
             | 
             | >there is less determinism with Python
             | 
             | You don't understand the meaning of the word determinism.
             | Python is almost 100 percent deterministic. The same
             | program run anywhere with an error will produce the same
             | error message at the same location all the time. That is
             | determinism. Type checking and unit testing does not
             | correlate with this at all.
             | 
             | This is not the case with c++.
        
               | icedchai wrote:
               | I think it's better to catch errors sooner than later.
               | This is where type checking helps. I've seen plenty of
               | Python code that takes a poorly named argument (say
               | "data").. is it a dict? list? something from a third
               | party library like boto3? If it's a dict, what's in the
               | dict? What if someone suddenly starts passing in 'None'
               | values for the dict? Does the function still work? Almost
               | nobody documents this stuff. Unless you read the code,
               | you have no idea. "Determinism" of code is determined
               | based on inputs. Type checking helps constrain those
               | inputs.
               | 
               | As for C++ "non-determinism": If you write buggy code
               | that overwrites memory, then of course you're going to
               | get segfaults. This isn't C++'s fault.
               | 
               | I've seen plenty of code in all languages (including
               | Python) that appears to exhibit chaotic run time
               | behavior. At a previous company, we had apps that Python
               | would bloat to gigabytes in size and eventually OOM. Is
               | this "non-determinism"? No, it's buggy code or
               | dependencies.
        
           | tikhonj wrote:
           | There is no "paradox". C++ is dangerous because of memory
           | management and awful semantics (undefined behavior/etc), both
           | of which are orthogonal to static typing.
           | 
           | It's a bit like saying that there's a paradox: everyone says
           | that flying is safer than driving, but experimental test
           | pilots die at a much higher rate than school bus drivers!
        
             | deltasevennine wrote:
             | Paradoxes don't exist in reality. It's a figure of speech
             | based on something that was perceived as a paradox. This
             | much is obvious.
             | 
             | Much of the fervor around dynamically typed languages in
             | the past was driven largely by the dichotomy between c++
             | and other dynamically typed languages.
             | 
             | Nowadays it's more obvious what the differentiator was. But
             | the point im making here is that type checking is NOT the
             | key differentiator here.
        
           | mamcx wrote:
           | > and C++ has a relatively advanced type checker?
           | 
           | But why it NEEDS that?
           | 
           | Because C++ is FAR MORE DANGEROUS.
           | 
           | And worse language, in so many aspects, that you _need_
           | everything to tame it.
           | 
           | In contrast, other langs like python have the luxury of see
           | what C/C++ do _wrong_ and improve over it.
           | 
           | Just having a `String` type, for example, is a massive boost.
           | 
           | So for them, the _type system_ already have improved the
           | experience!
           | 
           | ---
           | 
           | So this is key: Langs like python have a type system (and
           | that includes the whole space from syntax to ergonomics -
           | like `for i in x`, to semantics) and the impact of adding a
           | "static type system checker analysis" is reduced thank to
           | that.
           | 
           | And considering that if you benchmark for a "static type
           | system checker analysis" is what C++/C#/Java (at the start?)
           | is then the value is not much.
           | 
           | Is only when you go for ML type systems where the value of a
           | static checker become much more profitable.
        
             | deltasevennine wrote:
             | Hindley mindler allows for flexibility in your types and
             | this high abstraction and usability in code. The full
             | abstraction of categories allows for beautiful and
             | efficient use of logic and code but it's not safety per se.
             | 
             | Simple type systems can also offer equivalent safety with
             | less flexibility. What make Haskell seem more safe is more
             | the functional part combined with type safety. Functional
             | programming eliminates out of order errors where imperative
             | procedure were done in the wrong order.
        
           | za3faran wrote:
           | You shouldn't compare a Python web stack with a C++ web
           | stack, as C++ and Python target very different use cases.
           | 
           | You can compare however with a Java or C# web stack, both of
           | which offer a superior developer experience, as well as a
           | superior production experience (monitoring, performance,
           | package management, etc.).
        
           | jejones3141 wrote:
           | > If you have errors in your program, does it matter that
           | much if those errors are caught during runtime or compile
           | time?
           | 
           | The passengers on the fly-by-wire jet running the program
           | might well say that it matters.
        
         | c54 wrote:
         | I agree with this, yet look at some of the extremely salty
         | comments in this thread. People are upset that something might
         | be useful and that they might benefit from learning it or
         | changing their ways.
        
           | suzzer99 wrote:
           | It's weird to me how scanning the comments all seem to refer
           | to systems with 100k-ish LoC and dozens of contributors.
           | 
           | A big chunk of my job is writing node microservices in AWS
           | Lambda. I do everything I can to avoid shared library code,
           | since past experience tells me there be lots of dragons
           | (mainly in when and how to push or pull lib updates to
           | components). I have a very tiny shared lib that I try to
           | never touch and definitely never introduce breaking changes.
           | 
           | Unit tests are a breeze since I never have to cast objects or
           | worry about generics, etc.
           | 
           | Typescript would slow me down so much and add absolutely no
           | benefit. Maybe I'm misinterpreting though and no one is
           | claiming Typescript would benefit here.
           | 
           | We also have some C# lamdbas and I find writing unit tests
           | for those so much more of a pain - since the shared libs have
           | generics and I'm always casting things. But admittedly I
           | don't know all the tricks.
        
             | hither_shores wrote:
             | > since the shared libs have generics and I'm always
             | casting things.
             | 
             | This indicates to me that you're trying to write code that
             | isn't correct (not _doesn 't work_, but rather only works
             | because of implicit couplings between components) and/or
             | doing exotic lisp-style metaprogramming.
             | 
             | In the latter case, yeah, C#'s type system isn't powerful
             | enough. Others are (to an extent: arbitrary code execution
             | at compile time is never going to be completely safe).
             | 
             | In the former case ... that _should_ be difficult. Forcing
             | you to be explicit is half the point of a type system.
        
               | noduerme wrote:
               | Casting is often necessary for parsing inbound data from
               | certain mysql libraries or CSV or JSON depending on how
               | it's written. I would guess that might be what the parent
               | is talking about. That said, if you don't cast or
               | parseFloat or whatever in JS you're going to have a lot
               | of trouble. And if you're doing that, why not do it in
               | Typescript where you'll know that the data you're
               | accessing has been safely cast based on its type.
        
               | xigoi wrote:
               | > Casting is often necessary for parsing inbound data
               | from certain mysql libraries or CSV or JSON depending on
               | how it's written.
               | 
               | No, that's what sum types are for.
        
             | marcosdumay wrote:
             | > Unit tests are a breeze since I never have to cast
             | objects or worry about generics
             | 
             | Generics _reduce_ the amount of things you must care about
             | on your tests.
             | 
             | And you shouldn't cast objects in almost no code ever. Most
             | 100k LoC programs won't need it even once, your
             | microservices should need it proportionally less.
             | 
             | That's the thing. The gains grow superlineraly with the
             | amount of code. They make it just a bit easier to write
             | some trivial 100's LoC programs, and they make it possible
             | at all to have a working 100k LoC system. But if you don't
             | learn them, you won't know where the break-even point is
             | for you.
        
               | veidelis wrote:
               | "And you shouldn't cast objects in almost no code ever."
               | - I have a question about tests. Imagine I want to test a
               | function that operates on quite large application state
               | but not all app state is necessary for that function.
               | Options:
               | 
               | - Define all app state as a snapshot. Problem: snapshot
               | can become stale, so more infra might be necessary to
               | make sure that snapshot is up to date;
               | 
               | - Pass only the necessary state and construct as
               | necessary. Problem: hard to define whole state precisely
               | and ensure that it conforms to runtime state of a healthy
               | app;
               | 
               | - Pass a subset of necessary state for some execution
               | branch and cast the type. Problem: casting may result in
               | test failures during runtime and potentially other issues
               | such as modify-run-fail debug loop;
               | 
               | - Mock return values of functions called within the
               | function being tested and use any combination of "state
               | passing options above".
               | 
               | In a lot of places I use such approach with custom type
               | helpers and transitive types, and passing in only the
               | necessary subset for smaller functions or mocking return
               | values for bigger ones. What do you think? I know that
               | the AppState can be defined as a union of possible states
               | and together with type guards can address those issues
               | better. I just wanted to hear your opinion on how you
               | would address such problems. I hope I explained it well
               | enough.                 export type Fn = (...params: any)
               | => any;              type UnionToIntersection<U> = (U
               | extends any ? (k: U) => void : never) extends ((k: infer
               | I) => void) ? I : never;              export type
               | FirstParamType<G> = G extends Fn[]           ?
               | UnionToIntersection<Parameters<G[number]>[0]>           :
               | G extends Fn               ? Parameters<G>[0]
               | : never;              export interface AppState {
               | first: {               a: number;               b:
               | number[];           };           second: {
               | c: string;               d: string[];           }       }
               | type DeepPick<A, B extends keyof A, C extends keyof A[B]>
               | = { [BK in B]: Pick<A[B], C> };              function
               | calculateUsingFirstB(state: DeepPick<AppState, "first",
               | "b">): number[] {           return state.first.b; // some
               | calculation       }              function
               | calculateUsingSecondC(state: DeepPick<AppState, "second",
               | "c">): string {           return state.second.c; //
               | another calculation       }              // function
               | which takes complex state parameter and calculates the
               | result based on results of other functions       function
               | calculateMore(state: FirstParamType<[typeof
               | calculateUsingFirstB, typeof calculateUsingSecondC]> &
               | DeepPick<AppState, "first", "a">): string | number[] {
               | if (state.first.a > 10) {               return
               | calculateUsingFirstB(state);           }           return
               | calculateUsingSecondC(state);       }
        
               | gary17the wrote:
               | > The gains [introduced by a strong type system] grow
               | superlineraly with the amount of code.
               | 
               | That's a good way to put it.
        
             | icedchai wrote:
             | I've seen plenty of Lambda code developed in a "copy-and-
             | paste" style, with little to no code sharing, similar to
             | early CGI scripts from 25+ years ago. It makes
             | maintainability incredibly difficult. The more shared code
             | the better, in my opinion.
        
           | tyingq wrote:
           | Some of the salt might come from experiences using
           | dynamically typed languages that later had some amount of
           | stronger typing added on.
           | 
           | No matter how well that's done, it creates friction somewhere
           | in the process interacting with existing code.
           | 
           | That is, I can agree that an inherently strongly typed
           | language has benefits, while also being skeptical about
           | bolted on additions.
        
             | c54 wrote:
             | Makes sense. People have been burned. Probably there are
             | lots of people who think about typescript environment setup
             | and source maps when they think about typing, or who think
             | about python's "isinstance(str, foo)". Or who think that
             | it's overly complicated arcane nonsense with weird
             | terminology (lookin at haskell). Or that types specifically
             | refer to borrow checker woes in rust.
        
         | tialaramex wrote:
         | > (Yes, Rust 4ev3r! ;))
         | 
         | Rust has a perfectly nice type system by modern standards, but
         | it's nowhere close to showing you just how deep the rabbit hole
         | goes when it comes to avoiding bugs at runtime by having
         | stronger type systems.
         | 
         | For example suppose my Rust function takes a slice of clowns
         | (named unimaginatively "clowns") and also a usize integer k.
         | Can we write clowns[k] ? Rust says sure, it will emit a
         | _runtime_ bounds check to confirm that k is inside the bounds
         | of the slice. If there are sixteen clowns, and we ask for k =
         | 20, this Rust code will panic at runtime.
         | 
         | But we can do better, if we are willing to pay for it.
         | Dependent Types. In a language with dependent types and enough
         | inference our type inference system will conclude that k can be
         | 20 here, thus clowns must be a slice of at least 21 clowns, but
         | this slice has only sixteen clowns - type error during
         | compilation, either k or clowns are wrong.
         | 
         | Now, for cases where bounds checking would be the reasonable
         | thing to do, Dependent Types just result in you writing bounds
         | checks, ie in this case checking k < 16, and so it's possible
         | you will just end up doing more work to result in a program
         | that still just says, at runtime, "Nope, not enough clowns" or
         | whatever like in Rust. The type system will require you to
         | write correct bounds checks, but the Rust bounds checks are
         | auto-generated, so they're correct too.
         | 
         | But in cases where bounds checks were not the only sensible
         | approach, or maybe you didn't even realise a bounds check would
         | be emitted because you assumed it was statically correct - this
         | can catch some bugs at compile time which would otherwise
         | survive into a running program, "Shifting left" is I believe
         | the usual phrase to describe this improvement.
         | 
         | If you thought the function is obviously correct, "Of course
         | there are more than k clowns" but it isn't, the type error may
         | cause you to take that extra moment to think about it. "Wait,
         | why can there be fewer clowns than... oh, I didn't mean clowns
         | here, this should say circus_performers. I'm not even using the
         | right slice!".
        
           | creata wrote:
           | I don't know about this. Even some of the people who use
           | dependently typed proof assistants seem to doubt that they
           | should be used much in the programming part (as opposed to
           | the proving part). Also, some of the examples you give might
           | be addressed well enough by Rust's const generics.
           | 
           | https://xenaproject.wordpress.com/2020/07/05/division-by-
           | zer...
           | 
           | https://www.cs.ox.ac.uk/ralf.hinze/WG2.8/26/slides/xavier.pd.
           | ..
        
         | acchow wrote:
         | You say "prefix 20" but there was this weird trend in the
         | early-mid 2000's where Ruby evangelists really believed that
         | TDD is just as good as static types - even better because
         | you're forced to test actual business logic! And they even
         | managed to convince masses of programmers that this is true!
         | 
         | Glad that's over.
        
           | idontpost wrote:
        
         | emodendroket wrote:
         | I don't disagree that it makes things much more pleasant, but I
         | started doing this around 2013, which, while old, was still a
         | year beginning with 20, and consensus was trending the opposite
         | way and people were bullish about stuff like Ruby. The pendulum
         | has really swung in the other direction.
        
           | waprin wrote:
           | I noticed this too and I have a simple explanation.
           | 
           | Ruby and Python overtook Java and C++ in the early 2010s in
           | _spite_ of their lack of a good typing system, not because of
           | it. On the whole, they are much more productive languages.
           | 
           | Now we're seeing languages that have Ruby / Python
           | productivity but also have much better ways of static typing
           | such as Typescript and Swift. And the Ruby / Python community
           | is more open to static types as well.
           | 
           | The problems of ~2010 Java and C++ were mistakenly pinned on
           | static types and the framing of "static vs dynamic languages"
           | was always a red herring. Java and C++ were just crappy
           | languages (at least in 2010, not sure about modern
           | incarnations).
           | 
           | It really is a shame that Swift is so confined to the iOS
           | world because it's such a great example of how you can have a
           | language that feels like a scripting language but with much
           | more advanced type safety.
        
             | grumpyprole wrote:
             | > [Swift] it's such a great example of how you can have a
             | language that feels like a scripting language but with much
             | more advanced type safety.
             | 
             | There are great examples far older than Swift, for example
             | ML that dates back to the 1970s, or OCaml that's as old as
             | Java.
        
           | noobermin wrote:
           | No, there really is no new insight OP or anyone else has.
           | 
           | At the end of the day, the only thing that matters is writing
           | something that works and works well. Hackers go through too
           | many moodswings to be worth paying attention to when they
           | start telling you how you should code.
        
             | zbentley wrote:
             | > Hackers go through too many moodswings
             | 
             | True! But _computer scientists_ (who are sometimes also
             | hackers, sometimes not) apply research methods to existing
             | codebases and the practice of coding, and have repeatedly
             | presented findings that indicate that, mood swings
             | /fads/hype cycles aside, some techniques really do deliver
             | better software quicker. The VPRI STEPS work is an
             | interesting example of this.
             | 
             | That's not to say that every CS methodology paper should be
             | taken as gospel; we have problems just like other
             | disciplines, sometimes more. But it's a far cry from post-
             | hoc rationalization and hacker mood swings.
        
             | emodendroket wrote:
             | Doesn't really follow that because you ended up with a
             | successful product that means it was the best way you could
             | have possibly done it. I don't feel like I'd learned
             | everything I know today the first time I delivered a
             | successful product, and I doubt I know everything I'll know
             | in the future either.
        
         | david422 wrote:
         | > shift as much responsibility on the compiler as you can and
         | have the compiler check the code you write to any extent
         | technologically possible.
         | 
         | I think that people first starting out or people who have never
         | worked on large/new code bases with a diverse range of authors
         | don't seem to appreciate this concept.
        
         | kodah wrote:
         | My hypothesis is that it's so old to you because that discovery
         | and the spreading revelation was first order to you. To people
         | learning programming today, they end up having to somewhat
         | rewind the timeline and learn everything new at 2x speed.
         | 
         | That's to say, having old conversations with new engineers is a
         | really refreshing exercise and I'd encourage the world to
         | continue doing it.
        
       | MagicMoonlight wrote:
       | And that's why go is a meme language.
       | 
       | It replaces clear statements like "int x = 0;"
       | 
       | With "var x = 0;" as if that's somehow better. So instead of
       | having clear blocks of types that you can visibly read, it's
       | concealed. And the type could change each time you run the
       | compiler.
        
       | whiddershins wrote:
       | At the opening of the article, as the author describes a type, it
       | ends up sounding like a class to me.
        
       | graypegg wrote:
       | I've always grokked primitive types as mapping to different
       | concepts used when storing values in memory.
       | 
       | A string is some bytes in a line with a terminator at the end.
       | 
       | An integer is a group of signed or unsigned bytes.
       | 
       | An enum value points at another value with a pointer.
       | 
       | Etc.
       | 
       | What I think this describes is some validation classes, which
       | don't need to be built into a language's runtime. Primitive types
       | have a real reason for existing when compiling these apps, a
       | validation class doesn't. It can just be a library, in which case
       | this is a nice API for validation!
        
         | tsimionescu wrote:
         | This doesn't make too much sense. For example, (on 64-bit
         | Linux) long, unsigned long, long long, unsigned long long,
         | double, void*, char*, int(*)(int), []int are all stored in
         | exactly the same way: they are a 64-bit value somewhere in
         | memory. You could argue that double is different since it's
         | just packing a mantissa and exponent, and that signed is
         | actually a sign bit + a 63-byte number, but that still leaves
         | long*, int(*)(int) and long being the same thing: a 64-byte
         | number. Not to mention that struct X { long X } has the same
         | representation as well most likely.
         | 
         | Instead, it's more normal to think of types (primitive or not)
         | as descriptions of what can be done with a particular kind of
         | value - from this point of view, it's obvious why long* is a
         | different type than long (you can dereference it) or
         | int(*)(int) (you can't call it). With this new definition, we
         | can also see why we may want to distinguish EmailAddress from
         | String - you can send an email to an EmailAddress, but you
         | can't send an email to a String; conversely, you can sort the
         | characters of a String, but you can't sort the characters of an
         | EmailAddress.
        
       | pipeline_peak wrote:
       | Type systems cause programmers to write 300 classes no one
       | references and many duplicates of each other.
       | 
       | Dynamic typing allows you to focus on what really matters, not
       | trivial business logic OOP hierarchies that get inevitably
       | ignored.
       | 
       | I feel like in app development, there's something honest about
       | dynamic typing. You're focusing on the instance rather than the
       | unnecessary model definition that again, nobody uses and
       | redefined elsewhere anyway.
       | 
       | Tbh, I've never used a language like JS professionally. I'm sure
       | a lot of code bases are copied and pasted messes with state
       | dependencies and things that make it so you HAVE to focus on the
       | type. It's an app language, you're just not gonna find elegance
       | lol.
       | 
       | I've been a C# dev for a while now. I've just found that outside
       | of libraries/frameworks/etc how rarely object model code actually
       | gets reused. And I appreciate the cut to the chase aspect of
       | that.
       | 
       | Insert quote about gorilla with banana in forest.
        
         | dgan wrote:
         | Seems like you mix up classes, and types ... like many others
         | in this thread
        
           | hither_shores wrote:
           | Java and its consequences have been a disaster for the human
           | race
        
           | pipeline_peak wrote:
           | I don't understand, classes are user defined types.
        
             | dgan wrote:
             | that's the issue: there is no other way to create new types
             | aside from creating a new class in mainstream languages.
             | Those two concepts are separate, and should be treated as
             | such. Types are not Classes, the last is just a lousy
             | "embodiment" of the first
        
       | PainfullyNormal wrote:
       | Articles like this bug me. You've given me a list of why types
       | are awesome. Great. Now, tell me what the tradeoff is. Nothing is
       | free in engineering. To get something, you have to give up
       | something else. Even grug[0] understands this.
       | 
       | [0]: https://grugbrain.dev/#grug-on-type-systems
        
         | maxbond wrote:
         | Ever so slightly longer compile times. It's pretty close to a
         | free lunch.
         | 
         | There are only tradeoffs when we are at the frontier of what's
         | possible with a set of technologies, and so must trade off on
         | something in order to move along that frontier[1]. Many
         | languages aren't operating at that frontier, and adding static
         | typing is free (in the marginal case, ignoring the substantial
         | effort to implement the type system). If you start a greenfield
         | Python project, and you start typing right away and incorporate
         | MyPy into your CI and IDE - it's as close to free as you can
         | get, and the benefit is _substantial_.
         | 
         | [1] Eg, like in this diagram,
         | http://image1.slideserve.com/2488675/production-possibilitie...
         | - we only need to trade off on guns & butter if we're along the
         | frontier (the blue line), if we find ourselves somewhere in the
         | middle we can just make more stuff until we reach the frontier.
        
           | closeparen wrote:
           | People using dynamic languages deeply, especially library and
           | framework authors, regularly write abstract/generic code for
           | which a suitable type declaration would be mind-bendingly
           | difficult in a very sophisticated type system and impossible
           | in a weak one. You can argue that this is ill-advised! But
           | static typing with normally-powered type systems leads to
           | more voluminous and more purpose-specific code. Very powerful
           | type systems are possible, but treated as academic and too
           | difficult to use in the real world.
           | 
           | A way this often gets worked around is code generation.
           | Anywhere you have or reach for codegen in a static language,
           | you probably could have used a plain old function in a
           | dynamic language.
        
         | marcus_cemes wrote:
         | This is brilliant. Just made my day
        
         | bigyikes wrote:
         | grug miss big brain benefit for types. Grug says the main
         | benefit is auto completion, I think the real benefit is to
         | making code changes.
         | 
         | If I update a type, the compiler will tell me every single
         | location where I need to make a corresponding code change. For
         | grug: change type give red squiggle, make change code good
         | 
         | Also, grug makes a good point about the temptations of
         | generics, but I think they're exaggerating the impact to the
         | speed of development.
        
         | SaltyBackendGuy wrote:
         | > big brain type system shaman often say type correctness main
         | point type system, but grug note some big brain type system
         | shaman not often ship code. grug suppose code never shipped is
         | correct, in some sense, but not really what grug mean when say
         | correct
         | 
         | I forgot about this, thanks for the morning laugh. No such
         | thing as a free lunch.
        
         | ironSkillet wrote:
         | That grugbrain post was so funny and enjoyable to read, thanks
         | for sharing.
        
         | thefourthchime wrote:
         | How have I not seen Grug before, wonderful! Thank you!
        
         | hardwaregeek wrote:
         | Sure there are tradeoffs, but I disagree that it's always so
         | balanced. When people moved from assembly to high level
         | languages presumably there were tradeoffs but in retrospect
         | it's a pretty clear cut choice. I'm not saying typed languages
         | are as big of a shift as high level languages but it's possible
         | they are the unequivocal right choice.
        
           | [deleted]
        
           | c7b wrote:
           | Does that also hold for people doing data science in Jupyter
           | notebooks? They're also programmers, arguably.
           | 
           | At this point in the trajectory of software engineering, it's
           | fair to assume that most of the low-hanging fruits have been
           | picked, and solutions that are unequivocally better would
           | have to bring something fundamentally new to the table (which
           | types are not at all). Most solutions will be picking a
           | particular point on a trade-off isocurve.
           | 
           | Apart from that, it's always fair to ask someone who's
           | strongly proposing something what the downsides are.
        
             | jolux wrote:
             | > Does that also hold for people doing data science in
             | Jupyter notebooks? They're also programmers, arguably.
             | 
             | Yeah they're definitely programmers, but anyone who's had
             | to maintain and deploy what a data scientist came up with
             | in a notebook will question whether they should really be
             | using types.
        
             | hardwaregeek wrote:
             | Why are you sure that all of the low hanging fruits have
             | been picked? The origins of software development are pretty
             | much still within living memory. We're still extremely new
             | to programming. I wouldn't be surprised if the field looks
             | completely different in 50 years.
             | 
             | As for types, I'm not saying they're flawless. I'm pointing
             | out that in the transition from assembly to high level
             | languages there were flaws and criticisms that came out.
             | But looking back fifty years, were these flaws and
             | criticisms genuine tradeoffs that kept assembly as a
             | reasonable option for most developers? No, they were not.
             | Now we look back on programmers who insisted on writing
             | assembly as oddities, as niche figures. I cannot say if
             | this will be the case for types, but I won't rule it out.
        
         | tinyspacewizard wrote:
         | If you are happy to use "obj", "System.Object", "Any" etc. in
         | any place where the type-system breaks down then the downsides
         | are very few.
        
           | [deleted]
        
         | arwhatever wrote:
         | Understanding existing code is a big benefit of static types as
         | well.
         | 
         | I'm sure one could argue that member names should obviate the
         | need for type annotations.
         | 
         | There's also the distinct possibility that my preceding ~15
         | years of statically-typed software development have affected
         | how I think about software development in some way. (wink)
         | 
         | But I am finding type annotations internet useful while working
         | on a huge application that is about 2 years into adding a
         | gradual typing system, enough so that I usually take time
         | whenever I enter a new code area to add annotations to
         | everything, just to understand what's going on.
         | 
         | My perception is that I invest time to build understanding of
         | the types, and then document what I've learned in the form of
         | these type annotations so that future maintainers then gain a
         | quicker understanding without having to do the initial
         | research.
         | 
         | At least my non-statistically-significantly-sized team agrees.
        
           | Izkata wrote:
           | > Understanding existing code is a big benefit of static
           | types as well.
           | 
           | At a syntax level perhaps, but not necessarily at a semantic
           | level. This won't apply to everyone, but I've noticed that
           | the more types are relied on, the less my co-workers really
           | understand the code. They're relying on the compiler so much
           | they don't slow down and think through the changes they're
           | making. In one extreme case I saw a guess-change-compile
           | workflow that relied entirely on the compiler doing the work
           | for them.
        
         | rowanG077 wrote:
         | Of course there are thing that are free. Assembly is free over
         | machine code which is free over punch cards.
        
         | AnimalMuppet wrote:
         | The tradeoff is that I have to explicitly say (and know) what
         | type I'm dealing with at every point in the program.
         | 
         | But I'm not sure that's much of a tradeoff. If I don't know
         | what type this thing is, how do I know what operations I can
         | safely do on it? How do I know that I can make it do what I'm
         | trying to do? Or will it blow up at runtime when I do that?
         | 
         | I consider "I coded it, it's done, but it might blow up at
         | runtime" to be highly unprofessional. "We covered that with
         | unit tests" is theoretically OK, _if_ you 've got 100% test
         | coverage. But you don't, and you never will.
         | 
         | Having to _say_ everywhere what the type is gets tedious.
         | Autocomplete (and  "auto", for those languages that have it)
         | help a bit here, but only a bit.
        
           | pca006132 wrote:
           | Most modern languages can do type inference and doesn't
           | require explicit type annotation for variables. Hack, even
           | C++ gots auto! For declarations, I think it does make sense
           | to ask for the annotation because it can also serve as
           | documentation. Have you ever tried scala, rust, haskell or
           | typescript?
        
         | samatman wrote:
         | I found this blog post on HN some time this year, and refer to
         | it frequently:
         | 
         | https://hirrolot.github.io/posts/why-static-languages-suffer...
         | 
         | The key thing the author identifies is the two languages
         | problem. Static types are a second program _about the program_
         | , and that's great when the second program is simple
         | declarations that keep you from passing one struct when another
         | is expected.
         | 
         | But a flexible language needs more than that, and generics end
         | up either Turing Complete or bad in some other way.
         | 
         | I'll be mulling this one over in years to come.
        
         | plasticeagle wrote:
         | For a while in the codebase I was working on, we had a set of
         | distinct types for different units. You know, a type for
         | meters, another for centimetres, etc etc. We had types for
         | radians, types for degrees.
         | 
         | We had conversion functions between them, and type inference
         | when you performed certain operations.
         | 
         | The result was a disaster. Not an enormous disaster, but enough
         | of a problem to rip the entire thing out and replace it with
         | plain double-prevision floating points, and sensible variable
         | names, everywhere.
         | 
         | Why was this? It was a combination of there being nothing
         | sensible to infer when you, say, multiply an angle and a
         | distance (which happens when you're doing algebra), and
         | everything in the whole codebase needing to be aware of these
         | types.
         | 
         | The downside of all these brilliant ideas is dependency. If you
         | define an "EmailAddress" type, as the article suggests, you've
         | got to write the code for it somewhere. Now all your projects
         | are dependent on this library, with all the pain and anguish
         | that versioning and linking/including/whatever the library
         | brings.
         | 
         | Before, you depended on nothing but the String type, which is
         | very likely built into your language. When all your code needed
         | to do was pull that email address out of some persistent store
         | (say), and send to some other piece of code, your dependency
         | list was just the Persistence library. But with your fancy
         | EmailAddress type, your dependencies are now much worse.
         | 
         | Keep things as simple as they can be. An EmailAddress type is
         | not useful.
        
           | _dain_ wrote:
           | >The result was a disaster.
           | 
           | wtf? metres and centimetres are not different types! they are
           | just different ways of writing same type: Length. radians and
           | degrees are just ways of writing a dimensionless Angle
           | quantity. you made the absolutely elementary mistake of
           | conflating a physical quantity with the unit used to measure
           | it, _of course_ it was a disaster.
           | 
           | >nothing sensible to infer when you, say, multiply an angle
           | and a distance
           | 
           | angles are dimensionless so they should just be a distinct
           | type of float. there is literally no problem here.
        
             | sixstringtheory wrote:
             | I can see how it could cause problems if they weren't using
             | the type system correctly.                   typedef float
             | cm;         typedef float meter:         cm a = 1;
             | meter b = 1;         if (a == b) {             // launch
             | rockets         }
             | 
             | I've done something similar (not including rockets, don't
             | worry!) in Swift with its typealias feature. Thankfully
             | there is a way to actually force compiler errors in such
             | situations with something like
             | https://github.com/pointfreeco/swift-tagged
        
           | za3faran wrote:
           | Didn't NASA lose a space orbiter due to mixing up units in
           | the code? A properly written library should not have the
           | issues you're mentioning.
        
           | sixstringtheory wrote:
           | I would rather have dependency problems-which are able to be
           | automated with sufficient tooling-than working in a codebase
           | written by someone that thought you could just use floats and
           | strings for everything in an extremely overloaded fashion.
           | Doubly so if they don't believe in documenting all the
           | separate use cases and just keep all that knowledge in their
           | head.
        
         | dkarl wrote:
         | I don't think every article has to "teach the controversy."
         | This is an article for programmers who don't know the upsides
         | of types.
         | 
         | What's more, for a programmer who doesn't get the value of
         | types, the major downsides are already apparent, at least at a
         | basic level. Doesn't this make my code more verbose? Doesn't
         | this get super confusing sometimes?
         | 
         | It's an article design to help certain programmers learn a
         | particular thing, not an article meant to satisfy more
         | experienced programmers' desire to see all sides of an argument
         | acknowledged.
        
           | PainfullyNormal wrote:
           | > What's more, for a programmer who doesn't get the value of
           | types, the major downsides are already apparent, at least at
           | a basic level.
           | 
           | How could the downsides possibly be apparent if the upsides
           | are so mysterious they need an article to spell them out?
           | 
           | > It's an article design to help certain programmers learn a
           | particular thing
           | 
           | Have they actually learned that particular thing if they
           | don't know the tradeoffs they're making? I would argue they
           | haven't. You need to know what you're getting and what you're
           | giving up before you can decide whether something is worth
           | using at all. There are too many articles hyping the upsides
           | of technology X, but nobody asking what the downsides are.
        
             | saagarjha wrote:
             | How can the downsides of having to wear a seatbelt possibly
             | be apparent if the upsides are so mysterious they need an
             | article to spell them out? People have a quick aversion to
             | things all the time. Sometimes the actual benefits need to
             | be carefully explained. ("You are statically likely to be
             | in a car crash. Wearing a seatbelt multiplies your chance
             | of living through it.")
        
               | lapcat wrote:
               | I think almost everyone understands the benefits of both
               | types and seat belts. The fact that a seat belt keeps you
               | restrained during a crash is pretty intuitively obvious.
               | The idiots who don't wear seat belts either (1) believe
               | they'll beat the statistics, and thus no statistical
               | argument will convince them or (2) value their "freedom"
               | a lot more than they value their own lives.
               | 
               | In any case, though, wearing a seat belt or not is a
               | choice one can make independently of all other factors.
               | The cars come with seat belts, it's the same car either
               | way. You click or not, nothing else changes about the
               | car.
               | 
               | With types, however, that's not how it works. The
               | tradeoff is... you may have to change your entire
               | programming language, change your IDE, change your
               | frameworks, rewrite existing code, etc. It's not
               | analogous to seat belts at all. Do programmers want the
               | compiler to catch mistakes? Of course they do, in an
               | ideal world. Why wouldn't they? But there are a lot of
               | tradeoffs here that don't exist in the case of seat
               | belts.
        
             | dkarl wrote:
             | They aren't going to learn that in a day or a week or a
             | month. Maybe a year if they're extremely bright or are in a
             | perfect environment for figuring it out, but most people
             | take years. This article is a few minutes out of that
             | hypothetical best-case year. If it gives them food for
             | thought for a week, it'll be time better spent than 99% of
             | what they could read, certainly better than if they read a
             | comprehensive article that went 98% over their heads and
             | got them hung up on things that they weren't yet able to
             | experience and understand.
             | 
             | Besides, they aren't going print this article out and take
             | it to a cave in the mountains to learn about type systems
             | for a year. They're going to read other stuff along the
             | way.
        
             | howenterprisey wrote:
             | I don't know what to tell you. The downsides are apparent.
             | There is no logic theorem that says the upsides and
             | downsides need to be equally as apparent.
             | 
             | The trade-off is obvious: you gain confidence about your
             | program but you need to Learn More Stuff. Nobody's talking
             | about the downsides of type systems except to the extent
             | that they're worth talking about: see the comments here
             | every time someone compares the type systems of Python and
             | Rust.
        
               | kaashif wrote:
               | > The trade-off is obvious: you gain confidence about
               | your program but you need to Learn More Stuff.
               | 
               | You still do need to know that stuff if you have no types
               | in your language, but the compiler won't help you.
        
               | capableweb wrote:
               | > The trade-off is obvious: you gain confidence about
               | your program but you need to Learn More Stuff
               | 
               | This is why engineering/software articles in general
               | (this one included) needs to bring up tradeoffs more
               | often. No, "learning more stuff" is not a downside or a
               | tradeoff, it's just a fact of learning anything.
               | 
               | That you introduce more coupling is a tradeoff. That the
               | program (sometimes) gets harder to change is a tradeoff.
               | That is becomes easier to write large, messy programs
               | because programmers feel more safe in the future to
               | refactor, is a tradeoff. Trying to fix each one of those
               | tradeoffs also come with their own tradeoffs, and so on.
               | 
               | These are "apparent" for me, when talking about languages
               | using static types vs dynamic languages, but it is not
               | apparent for everyone. So when bringing up these
               | "obvious" upsides, also bring up the "obvious" downsides,
               | as it seems quite a lot of people don't see it as
               | "obvious" as we do.
        
               | jjav wrote:
               | > That you introduce more coupling is a tradeoff.
               | 
               | No, the coupling (i.e. assumptions about what type this
               | thing can be) is implicitly there in a typeless language.
               | If you pass in the wrong thing it'll blow up. But it'll
               | happen in production.
               | 
               | The coupling is always there, it's just a matter whether
               | you make it explicit (this allowing errors to be caught
               | early) or you pretend it's not there and let things crash
               | in production.
        
               | jolux wrote:
               | > That you introduce more coupling is a tradeoff.
               | 
               | That's not inherent in static types.
        
               | hither_shores wrote:
               | > That you introduce more coupling is a tradeoff. That
               | the program (sometimes) gets harder to change is a
               | tradeoff.
               | 
               | You don't introduce more coupling, you document the
               | coupling that already exists. If your program is hard to
               | change with types, it would be hard to change without
               | types - but easier to change incorrectly.
               | 
               | > That is becomes easier to write large, messy programs
               | because programmers feel more safe in the future to
               | refactor, is a tradeoff
               | 
               | Sure, I guess this is true in principle - but you could
               | say the same about IDEs, or version control, or grep. The
               | effect size is small.
        
               | germanjoey wrote:
               | > You don't introduce more coupling, you don't the
               | coupling that already exists.
               | 
               | This is true at the code level. But at the system-design
               | level, this documentation _is_ the extra coupling.
               | 
               | I feel like it's important to understand this. I agree
               | with the original commenter; in engineering, nothing is
               | truly free. In many cases, this extra coupling helps keep
               | a system strong and stable, like extra nails holding
               | planks of wood together. In other cases, you may find
               | that part of a system's spec actually missed the mark and
               | now needs to be ripped up and redone. That extra coupling
               | might now work against you!
               | 
               | Again, that doesn't mean that it wasn't worth having it.
               | It is just important to understand tradeoffs in
               | engineering.
        
         | BiteCode_dev wrote:
         | I must be grug. Just forgot I wrote because dumb brain.
        
         | floppydisc wrote:
         | I usually run into issues at the boundaries in the system.
         | 
         | Usually moving from primitives into complex types does not
         | account for serialization and deserialization between db and
         | the client. This can be very annoying to work with in something
         | like C#.
         | 
         | Usually it ends up resulting in alot more types and a lot more
         | mapping between types.
         | 
         | However this has its own benefits, but is very boilerplate-y
         | and is sluggish to work with when your domain changes.
         | 
         | Luckily, for C#, https://github.com/SteveDunn/Vogen now exists
         | thanks to source code generators which soothes some of the
         | issues.
        
         | zbentley wrote:
         | > Nothing is free in engineering. To get something, you have to
         | give up something
         | 
         | I think this is a dangerous position to take to extremes/as an
         | axiom.
         | 
         | Not talking about type systems at all here. The assumption
         | that, given two tools/techniques for accomplishing the same
         | goal, there are always _equivalent_ tradeoffs simply isn 't
         | true.
         | 
         | Some tools are better than others.
         | 
         | That statement usually provokes misinterpretation. It should
         | _not_ be taken to mean:
         | 
         | - That some tools are _always_ better than others, in every
         | context. There are situations in 2022 where COBOL is the best
         | choice for new code, and other situations where rewrite-it-in-
         | Rust is the best choice. Problems occur when  "tradeoffs of
         | tool A (even if we don't know what they are yet) make it
         | equivalent to tool B" is a core tenet of decision making.
         | 
         | - That some tools _always have been_ and /or _always will be_
         | better than others. Context, expectations, tool capabilities,
         | and available programmer talent pools all change massively over
         | time.
         | 
         | - That _one_ tool is better than all the others. Plenty of
         | times there are multiple ways to deliver optimal-given-
         | constraints outcomes, and it comes down to a matter of taste or
         | "just pick something, anything, and let us get to work".
         | 
         | Chasing hype and cargo culting leads to poor outcomes; "we
         | should build our two-core app on Kubernetes/write our 2TPS app
         | in Rust" are often justified with "because it's the future" or
         | "because the cool kids are doing it". That's a major bummer.
         | 
         | But _the opposite extreme is just as bad_ : assuming that all
         | choices are fundamentally a wash because "to get something, you
         | have to give up something else" is just as methodologically
         | irresponsible as following the hype cycle. Programming isn't
         | alchemy. This kind of bad decisionmaking can lead to dependence
         | on obsolete (unsupported/insecure) tools, difficulty hiring,
         | and, at worst, a culture of "don't talk about Python to me; if
         | you can't freehand it in C you just need to get gud"
         | gatekeeping cruelty.
         | 
         | Everything has tradeoffs. That doesn't mean they're equivalent.
        
       | musingsole wrote:
       | >A string value is not a great type to convey a user's email
       | address or their country of origin.
       | 
       | I can argue whatever type you use in place of a string will
       | similarly be "not a great type". This can be argued in perpetuity
       | because no type/map actually matches the reality it's
       | encapsulating.
       | 
       | Type systems require you to build a Pretty Good Theory of your
       | problem space so that your types can overlap with reality/actual
       | usage as much as possible.
       | 
       | The problem is at planning/design time, you'll have a Pretty
       | Crappy Theory of your problem space and can only get a Pretty
       | Good one after having wrestled with it for a while.
       | 
       | A dynamic, more forgiving language, allows you to build what you
       | can today with the theory you have, and then change it in the
       | future when your theory gets disproven.
        
         | Ygg2 wrote:
         | > I can argue whatever type you use in place of a string will
         | similarly be "not a great type".
         | 
         | All Types are wrong, but some are useful.
         | 
         | Will Money type solve all problems? Is it better than decimal,
         | from POV of prevention of mistakes - yes.
        
           | Jach wrote:
           | You have Money types? Lucky, I still see people using
           | floats...
        
       | [deleted]
        
       | kazinator wrote:
       | > _A string value is not a great type to convey a user 's email
       | address or their country of origin. These values deserve much
       | richer and dedicated types. I want a data type called
       | EmailAddress which cannot be null._
       | 
       | Sure, I'm on board: I also want an e-mail address type. Just not
       | in your shit language in which something can be of type String,
       | yet be null reference.
        
         | sixstringtheory wrote:
         | Are there any widely used statically typed languages where
         | there is no such thing as null? Wish I could find a job using
         | one of those!
        
           | kazinator wrote:
           | Hmm, oh! C++ comes to mind. Of course, there is such a thing
           | as null, but in:                  void fun(string x)  //
           | std::string        {        }
           | 
           | x cannot be null. The reference semantics (like a copy of a
           | string sharing the data with the original) is an
           | implementation detail/optimization encapsulated inside what
           | appears to be a value type.
           | 
           | The tools are there in C++ to create ideal types for your
           | problem domain which just look like value types that have no
           | null domain value (or any other value you don't want), and
           | standard strings are like this.
        
             | sixstringtheory wrote:
             | You've moved the goal posts, but seem to have forgotten
             | that you can create pointers to C++ strings, which then can
             | of course be null.
        
               | kazinator wrote:
               | Pointers to std::string are almost never _required_ ,
               | though. A pointer to std::string is not something you
               | have to use to write a string handling C++ program or
               | module; and such a pointer _p_ is itself not a string, *
               | _p_ is (if _p_ is non-null and valid).
               | 
               | About the only time you would need a pointer to
               | std::string when calling some C API that takes a callback
               | function with a void * context, and you'd like that
               | context to be a std::string. Then you might take the
               | address of string object to pass to that API. (That
               | pointer would likely never be null, but could go bad due
               | to lifetime mismanagement.)
               | 
               | Most other uses of such a thing would be unidiomatic.
               | Whereas, in some languages, string references that can be
               | null are foisted on programmers as the standard,
               | idiomatic string representation. That's a big difference.
        
       | croo wrote:
       | There is an important difference between types and objects: types
       | are basic building blocks. An email is not a basic building block
       | nor is money. In the mature ecosystem of Java there are libs and
       | standard libraries that handles these problems some are even in
       | the standard library (timestamp with timezone, url...)
       | 
       | It would be nice to have stuff for these in the standard
       | libraries but currencies are a moving target and needs constant
       | update to handle the quirks of the real world. For the same
       | reason it's extremely hard to create an "Address" class that
       | handles every possible scenario.
       | 
       | In my book the string is a great way to store a country of origin
       | because everything else depends on the usage of that information.
        
         | grumpyprole wrote:
         | > types are basic building blocks
         | 
         | Yes but they should also be compositional, one should build
         | larger types from smaller types. This is how we scale to
         | solving difficult problems.
         | 
         | > but currencies are a moving target and needs constant update
         | to handle the quirks of the real world.
         | 
         | This is a separate issue orthogonal to types. The problem of
         | modelling the real world and what models might be more
         | generally useful. No model is perfect, all have trade-offs.
         | 
         | > In my book the string is a great way to store a country of
         | origin
         | 
         | As an implementation using a string sounds pragmatic, but that
         | should not be its type! You should still create a currency type
         | and parse into it. Then you won't accidentally pass an
         | arbitrary string to a parameter that is supposed to be a
         | currency.
        
         | mejutoco wrote:
         | IMO the difference between types and objects is that objects
         | have state and behaviour, whereas types only have state.
        
           | grumpyprole wrote:
           | To compare them this way is likely to cause confusion.
           | 
           | A type (of a term) represents the set of all possible values
           | for a particular term. An "object" in OOP, does not have any
           | formal definition, but is typically a first-class module with
           | mutable state. As such, they can be represented by a term and
           | therefore can have a type.
        
             | mejutoco wrote:
             | > they can be represented by a term and therefore can have
             | a type.
             | 
             | It is informative, I see what you mean. Let me try again
             | following your terms: An object is a type plus behaviour
             | (mutable state).
        
               | grumpyprole wrote:
               | I am saying that an object has a type, rather than is a
               | type or some augmentation of it.
               | 
               | An object is a term-level construction and therefore is
               | not really comparable to a type. Types can be given to
               | both state and behaviour. For example, a function type
               | describes pure behaviour.
               | 
               | Note that statically-typed OOP languages have a name for
               | the nominal types of objects: "classes". One could say
               | that a class is a type representing both state and
               | behaviour.
        
       | recursivedoubts wrote:
       | _> grug very like type systems make programming easier. for grug,
       | type systems most value when grug hit dot on keyboard and list of
       | things grug can do pop up magic. this 90% of value of type system
       | or more to grug_
       | 
       |  _> danger abstraction too high, big brain type system code
       | become astral projection of platonic generic turing model of
       | computation into code base. grug confused and agree some level
       | very elegant but also very hard do anything like record number of
       | club inventory for Grug Inc. task at hand_
       | 
       | https://grugbrain.dev/#grug-on-type-systems
        
         | majjgepolja wrote:
         | I am grug. More benefit type when there's red line in editor.
         | More benefit type system when F2 rename everywhere.
        
       | abraae wrote:
       | This "everything old is new again" stuff gets a bit much
       | sometimes.
       | 
       | Of course having ability to do strict typing is a good way to
       | avoid bugs and write reliable code. This is not news.
        
       | snickerer wrote:
       | I summarize the article as "use OOP and design your classes
       | well".
        
         | pencilguin wrote:
         | Has absolutely nothing whatever to do with OOP.
         | 
         | OOP, where it means anything at all, involves runtime selection
         | of operations according to types organized in hierarchies.
         | 
         | TFA is about _compile-time_ type compatibility enforcement.
        
           | GnarfGnarf wrote:
           | _Au contraire_ , types can be implemented with OO classes and
           | operator overloading. Errors caught at compile time.
        
             | pencilguin wrote:
             | Classes can be OO. Or not OO. Makes no difference, at
             | compile time.
        
       | GnarfGnarf wrote:
       | 100% agree. I love C++ for allowing me to do things like:
       | CInches in1, in2, in3;   // basically just floating points
       | CCm cm1;   // basically just floating points         in1 = in2 +
       | in3;   // no compilation error         in1 = in2 + cm1;   //
       | compilation error         in1 = in2 * in3;   // compilation error
       | in1 = 2. * in2;    // no compilation error              float
       | foo(CInches *);         foo(cm1);   // compilation error
       | 
       | Obviously there's a lot of code behind "CInches", but it catches
       | so many errors. Which other languages also support this?
        
         | xigoi wrote:
         | > Which other languages also support this?
         | 
         | Haskell and related languages have distinct types; so does Nim.
         | But even if there's no direct language support, you can always
         | emulate them by having a compound type (record, struct, class,
         | whatever you call it) with one field.
        
       | barbariangrunge wrote:
       | The main benefit of static types is great refactoring tools
        
         | Jach wrote:
         | It's amusing that the first language with great refactoring
         | tools had dynamic types (Smalltalk). But I think it's an
         | underappreciated note that static type languages often create a
         | bias for earlier coupling, less system-independent modularity,
         | and a lot of unnecessary data copying from structure to
         | structure, creating in turn a stronger need for refactoring
         | tools since what should be small changes end up becoming larger
         | ones affecting more places. Even though they can't catch
         | everything (thanks to reflection) I'm pretty happy that such
         | tools exist when doing Java development. I've sometimes missed
         | them in less tooling-mature dynamic langs, but also have found
         | them less necessary. (Though I'm sure part of that is due to
         | other feature-factors, like closures, that historically have
         | been a long time coming (if ever) to the most popular static
         | langs.)
        
       ___________________________________________________________________
       (page generated 2022-11-05 23:02 UTC)