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