[HN Gopher] Why static languages suffer from complexity
       ___________________________________________________________________
        
       Why static languages suffer from complexity
        
       Author : Lapz
       Score  : 117 points
       Date   : 2022-01-19 16:55 UTC (6 hours ago)
        
 (HTM) web link (hirrolot.github.io)
 (TXT) w3m dump (hirrolot.github.io)
        
       | Animats wrote:
       | Fascination with type systems does not seem to be all that useful
       | in practice. Go has a minimal type system, and is able to do much
       | of Google's internal server side work.
       | 
       | Most of the problems that cause non-trivial bugs come from
       | invariant violations. At point A, there's some assumption, and
       | way over there at point B, that assumption is violated. That's an
       | invariant violation.
       | 
       | Type systems prevent some invariant violations. Because that
       | works, there are ongoing attempts to extend type systems to
       | prevent still more invariant violations. That creates another
       | layer of confusing abstraction. Some invariants are not well
       | represented as types, and trying makes for a bad fit. What you're
       | really trying to do is to substitute manual specification of
       | attributes for global analysis.
       | 
       | The Rust borrow checker is an invariant enforcer. It explicitly
       | does automatic global analysis, and reports explicitly that
       | what's going on at point B is inconsistent with what point A
       | needs. This is real progress in programming language design, and
       | is Rust's main contribution.
       | 
       | That's the direction to go. Other things might be dealt with by
       | global analysis, Deadlock detection is a good example. If P is
       | locked before Q on one path, P must be locked before Q on all
       | paths. There must be no path that leads to P being locked twice.
       | That sort of thing. Rust has a related problem with borrows of
       | reference counted items, which are checked at run time and work a
       | lot like locks. Those potentially have a double-borrow problem
       | related to program flow. I've heard that someone is working on
       | that for Rust.
        
         | DonaldPShimoda wrote:
         | > Fascination with type systems does not seem to be all that
         | useful in practice.
         | 
         | > ...
         | 
         | > The Rust borrow checker is an invariant enforcer. [...] This
         | is real progress in programming language design, and is Rust's
         | main contribution.
         | 
         | I'm so confused by your stance here. You essentially say "type
         | systems are not useful" and then "oh but this most recent
         | advance in type systems -- that one is useful." Do you find
         | type systems useful or not?
         | 
         | There are a lot of properties we can analyze statically, and
         | practically all of them essentially amount to extensions of
         | type systems. Any of them increases our ability to rule out
         | undesirable programs from every beginning execution. Some of
         | them have unintuitive syntax, but many of them are no more
         | syntactically burdensome than most other type systems. This is
         | especially true if you consider how far we've come with type
         | inference, so we no longer have to write code with the
         | verbosity of Java just to get some meager guarantees. It's
         | still a very active area of research, but we're clearly making
         | progress in useful ways (which you even highlight), so I don't
         | really know what point it is you've set out to make.
        
           | joe_the_user wrote:
           | _I 'm so confused by your stance here. You essentially say
           | "type systems are not useful" and then "oh but this most
           | recent advance in type systems -- that one is useful." Do you
           | find type systems useful or not?_
           | 
           | Not the original author but it seems like they're saying that
           | type-systems are non-specific invariant enforcers and so have
           | costs without necessarily having benefits whereas a user-
           | specifiable invariant enforcer is more guaranteed to have the
           | benefits.
        
           | preseinger wrote:
           | Type systems are useful, but not nearly as useful as many
           | people believe they are.
        
             | mountainriver wrote:
             | Try working in a big system without them =P I think they
             | are invaluable
        
           | Verdex wrote:
           | I think the key is the following:
           | 
           | > It explicitly does automatic global analysis
           | 
           | They appear to think that the borrow checker isn't achieved
           | with type theory, but with some other technique ("global
           | analysis").
           | 
           | Although, to be fair, my understanding of making a practical
           | affine type checker is that things get kind of wonky if you
           | do it purely logically. So practically you do some data flow
           | analysis. Which is, I believe, what rust is doing. This also
           | explains why MIR was such a big deal for certain issues with
           | borrow checking. They ended up with a format that was easier
           | to run a data flow analysis on, and that allowed the borrow
           | checker to handle things like non-lexical lifetimes, etc.
           | 
           | [I've only read about such things. So I might have mis-
           | remembered some of the details. However, this is my take on
           | why someone might not call rust's advances purely type
           | theoretic (even if they can be handwaved as type theory at a
           | high level).]
        
         | Zababa wrote:
         | > Go has a minimal type system, and is able to do much of
         | Google's internal server side work.
         | 
         | Isn't it stil mostly Java and C++? That's what I hear all the
         | time here.
         | 
         | Also, I'm not sure what point you're trying to make. You start
         | by saying that fascination with types systems is not useful in
         | practice, and end with an example where it is useful (Rust).
         | While Go can stick a GC to avoid most of the issues that Rust
         | is trying to solve, it stil has to ship with a defer mechanism
         | (no linear/affine types/RAII) and a data race detector.
        
           | acchow wrote:
           | > Isn't it stil mostly Java and C++? That's what I hear all
           | the time here.
           | 
           | Go's type system is much weaker and less expressive than
           | either Java's or C++'s. C++ in particular has parametric
           | polymorphism, type constructors, and dependent types. Go has
           | none of those.
        
           | skybrian wrote:
           | It's been a while since I worked there, but the trend at
           | Google at the time was that the amount of code written in
           | each popular language was rapidly growing, and the number of
           | popular languages was also slowly growing. (Despite a lot of
           | resistance to introducing new languages.)
           | 
           | I'm out of touch, but I would expect that there is a lot more
           | Go code by now, and it also didn't catch up with C++ or Java.
        
         | Chyzwar wrote:
         | Rust contribution affect less than 1% of programmers. Most code
         | written today do not require manual memory management or even
         | explicit multithreading.
         | 
         | I think typescript with gradual and structural typing and
         | similar like mypy or sorbet are making real difference.
         | 
         | Type systems provide multiple benefits, performance, self-
         | documentation, better tooling and more explicit data model.
        
         | agentultra wrote:
         | > Fascination with type systems does not seem to be all that
         | useful in practice.
         | 
         | And yet type theory is an excellent way to express all kinds of
         | invariants. The more rich the type system the more you can
         | express. If you get to dependent types you essentially have all
         | of mathematics at your disposal. This is the basis of some of
         | the most advance proof automation available.
         | 
         | What is super cool is that proofs are programs. You can write
         | your programs and use the same language to prove theorems about
         | them.
         | 
         | This is still fairly advanced stuff for most programming
         | activities but the languages have been steadily getting better
         | and the automation faster and I think the worlds will
         | eventually collide in some area of industrial programming.
         | We're already seeing it happen in security, privacy, and
         | networking.
         | 
         | I don't think type systems suffer from complexity. They merely
         | reveal the inherent complexity. You can use languages that hide
         | it from you but you pay a price: errors only become obvious at
         | run time when the program fails. For small programs that's easy
         | enough to tolerate but for large ones? Ones where certain
         | properties cannot fail? Not so much in my experience.
         | 
         |  _update_ : clarified wording of "proofs are programs"
        
           | dmitriid wrote:
           | > The more rich the type system the more you can express. If
           | you get to
           | 
           | Ah yes. And then you end up writing entire prgrams in types.
           | So the next logical setep would be to start unit- and
           | integration tests for these types, and then invent types for
           | those types to more easily check them...
           | 
           | > you essentially have all of mathematics at your disposal.
           | 
           | Most of the stuff we do has nothing to do with mathematics.
        
           | spc476 wrote:
           | > You can write your programs and use the same language to
           | prove theorems about them.
           | 
           | Didn't Kurt Godel and Alan Turing do some work on proving
           | statements within a system?
        
             | BalinKing wrote:
             | AFAIK languages like Idris, Agda, and Coq are not Turing-
             | complete (specifically, they disallow general recursion)
             | for just this reason.
        
               | raphlinus wrote:
               | If my understanding is correct, Agda and Coq disallow
               | general recursion, so all programs terminate by
               | construction, but Idris relaxes this, in a desire to be
               | more pragmatic at the cost of not as clean mathematically
               | (functions have to be modeled as partial in general, to
               | account for the fact they might not terminate).
        
         | acchow wrote:
         | > Go has a minimal type system, and is able to do much of
         | Google's internal server side work.
         | 
         | And yet Go is adding generics in 1.8. And I'm sure its type
         | system in another 5 years will be much more expressive than
         | 1.8's. The community has long been saying that the minimal type
         | system isn't enough.
        
           | benhoyt wrote:
           | Nit: they're adding Generics in 1.18 (not 1.8). Regarding
           | "another 5 years": I'm not so sure. Go is very conservative
           | about language changes. The type system didn't change at all
           | from version 1.0 through version 1.17 (a 12-year period).
        
         | mountainriver wrote:
         | Type systems also allow people to understand your code, this is
         | very important
        
       | guidorice wrote:
       | Best.Programmer.Art.Ever
        
       | dnautics wrote:
       | I didn't see the article touch on the "why" explicitly, but: zig
       | really has the chance to square this circle for low level
       | languages, since there is duck-typed type-inferenced-coercion in
       | places where it makes sense. Completely correct about zig not
       | necessarily being good for higher level stuff, but I think
       | (dynamic) HLLs have been converging on dealing with this using
       | static typechecking, with varying levels of success
        
       | honkycat wrote:
       | Great article, made me think! However, I think it needs to be
       | trimmed down. Making your argument in the final paragraph of the
       | article is not great.
       | 
       | Hoist the "Final Words" section to the top and make it a "tldr"
       | introduction, that way your reader can begin with a high level
       | understanding of your argument, which you can hone and refine as
       | you progress.
        
       | ModernMech wrote:
       | Ugh, I know I'm getting old when I don't understand the memes.
        
       | arc619 wrote:
       | This entire article can be summarised as "compile time stuff
       | should use the same language as run time".
       | 
       | I guess the author just hasn't encountered Nim before, where
       | anything becomes compile time by just assigning to a const, and
       | macros have access to the real AST without substitution. Macros
       | also allow compile time type inspection, as they are a first
       | class citizen rather than tacked on.
       | 
       | The compile time print, AFAICT, already exists in Nim as the `&`
       | macro in strformat. That lets you interpolate what you like at
       | compile time, and supports run time values too.
        
         | foxfluff wrote:
         | > This entire article can be summarised as "compile time stuff
         | should use the same language as run time".
         | 
         | I think the message is more nuanced than that (otherwise
         | wouldn't lisp with its homoiconicity and compile time macros
         | fit the bill perfectly?). Idris uses the same language, but is
         | still too complex. And Zig not general purpose enough. I don't
         | want to put words in the author's mouth but I think the
         | implication is that this is a large space to explore and we
         | don't have a solution yet; there's nothing like "just make your
         | language like this and it'll be good." They're just pointing
         | out the problem they see, and some (non-)solutions to it.
        
           | Hirrolot wrote:
           | +1
        
           | arc776 wrote:
           | > I think the message is more nuanced
           | 
           | I thought it was more nuanced too as they were explaining how
           | integer types can be derived, until I finished the article,
           | and they really did just seem to be complaining that there's
           | a mismatch between compile time and run time.
           | 
           | Dynamic types don't really solve the problems they mention as
           | far as I can tell either (perhaps I am misunderstanding),
           | they just don't provide any guarantees at all and so "work"
           | in the loosest sense.
           | 
           | > otherwise wouldn't lisp with its homoiconicity and compile
           | time macros fit the bill perfectly?
           | 
           | That's a good point, I do wonder why they didn't mention Lisp
           | at all.
           | 
           | > we don't have a solution yet
           | 
           | What they want to do with print can, as far as I can see, be
           | implemented in Nim easily in a standard, imperative form,
           | without any declarative shenanigans. Indeed, it is
           | implemented as part of the stdlib here:
           | https://github.com/nim-
           | lang/Nim/blob/ce44cf03cc4a78741c423b2...
           | 
           | Of course, that implementation is more complex than the one
           | in the article because it handles a lot more (e.g.,
           | formatting and so on).
           | 
           | At the end of the day, it's really a capability mismatch at
           | the language level and the author even states this:
           | 
           | > Programming languages ought to be rethought.
           | 
           | I'd argue that Nim has been 'rethought' specifically to
           | address the issues they mention. The language was built with
           | extension in mind, and whilst the author states that macros
           | are a bad thing, I get the impression this is because most
           | languages implement them as tacked on substitution mechanisms
           | (C/C++/Rust/D), and/or are declarative rather than "simple"
           | imperative processes. IMHO, most people want to write general
           | code for compile time work (like Zig), not learn a new sub-
           | language. The author states this as well.
           | 
           | Nim has a VM for running the language at compile time so you
           | can do whatever you want, including the recursive type
           | decomposition (this lib isn't implementing Peano arithmetic
           | but multiprecision stack based bignums):
           | https://github.com/status-im/nim-stint and specifically here:
           | https://github.com/status-im/nim-
           | stint/blob/ddfa6c608a6c2a84...                   func
           | zero*[bits: static[int]](T: typedesc[Stuint[bits] or
           | Stint[bits]]): T {.inline.} =           ## Returns the zero
           | of the input type           discard              func
           | one*[bits: static[int]](T: typedesc[Stuint[bits]]): T
           | {.inline.} =           ## Returns the one of the input type
           | result.data = one(type result.data)
           | 
           | It also has 'real' macros that aren't substitutions but work
           | on the core AST directly, can inspect types at compile time,
           | and is a system language but also high level. It seems to
           | solve their problems, but of course, they simply might not
           | have used or even heard of it.
        
       | chriswarbo wrote:
       | I think the comparison between printf in Idris and Zig is a
       | little off, since the Idris version defines an intermediate
       | datastructure, and hence requires extra parsing and interpreting
       | functions for it. That's a _nice_ approach, but the Zig version
       | is operating directly on characters, so it 's a bit apples-to-
       | oranges.
       | 
       | We can get a more direct Idris implementation by inlining the
       | parser (toFmt) into the interpreter (PrintfType). That lets us
       | throw away `Fmt`, `toFmt`, etc. to just get:
       | PrintfType : (fmt : List Char) -> Type         PrintfType ('*' ::
       | xs) = ({ty : Type} -> Show ty => (obj : ty) -> PrintfType xs)
       | PrintfType (  x :: xs) = PrintfType xs         PrintfType [] =
       | String              printf : (fmt : String) -> PrintfType (unpack
       | fmt)         printf fmt = printfAux (unpack fmt) [] where
       | printfAux : (fmt : List Char) -> List Char -> PrintfType fmt
       | printfAux ('*' :: fmt) acc = \obj => printfAux fmt (acc ++ unpack
       | (show obj))           printfAux (  c :: fmt) acc = printfAux fmt
       | (acc ++ [c])           printfAux []           acc = pack acc
        
         | dvh wrote:
         | Would printf even exist if C had sane strings?
        
           | msla wrote:
           | Some kind of formatting function would because sometimes, you
           | really do need to print an integer with enough leading zeroes
           | to fit in a five-digit field.
        
           | foxfluff wrote:
           | How is formatted printing related in any way to the internal
           | representation of strings?
           | 
           | printf is what you call when you want to print X in
           | hexadecimal with at least two digits, left justified on an
           | eight-character wide field. I don't see how the sanity of
           | whatever string representation the programming language uses
           | is relevant here.
        
         | goldsteinq wrote:
         | Except this version doesn't compile. I'm not sure that it's
         | possible to get it to compile: type-level Idris is actually a
         | _subset_ of Idris and pattern-matching non-ADTs is half-broken
         | on the type level. You can also observe this problem in this
         | simplified example:                   f : Char -> Type
         | f '0' = Int         f _ = Char              g : (c : Char) ->
         | (f c)         g '0' = 0         g c = c
        
       | lowbloodsugar wrote:
       | "We might want to zip our car with their car..."
       | 
       | We do or we don't. There is no "might". Spending money on "might"
       | has been the death of many projects.
       | 
       | If we didn't, and now we do, we could write a fn to map the car
       | to parts, or we could define the car struct in terms of its
       | parts, or we could just do away with the car altogether.
       | 
       | But far more valuable would be an analysis of what changed about
       | the requirements that the model no longer works.
       | 
       | Now, don't get me wrong: I'd love a better language, and by
       | better I mean "as fast as assembly but 'dynamic'". The problem is
       | that, at the end of the day, all compilers are just "premature
       | optimizations" or perhaps "willing premature optimizations". We
       | could all be happily programming in smalltalk or build a runtime
       | using predicate logic, but a) the number of people who could
       | program in it is vanishingly small and b) it would be fucking
       | slow. These languages don't solve a problem that I have, or
       | rather they don't solve a problem that I don't already have a far
       | better solution for. They solve a problem that academics have.
        
       | [deleted]
        
       | AtNightWeCode wrote:
       | This is some kind of joke, right?
        
       | seanw444 wrote:
       | I really want to know where I can find quality programming memes
       | like these. Not just the generic "haha language ___ is bad, nerd"
       | memes.
        
       | zmmmmm wrote:
       | The problem I find with static typing is that it so easily leads
       | you over-specifying the requirements / constraints. In fact, it
       | makes such a virtue out of that over-specification that many
       | people would consider it a best practice to do so.
       | 
       | For example, perhaps my `calculate_price` function only depends
       | on 2 attributes of the order which has 65 attributes. Am I
       | creating a 2-element data type for that function to process? no!
       | I'm specifying that it processes an Order data type, with all its
       | 65 elements. But implicitly then I'm saying the function has 65
       | input parameters of all these specific types and nobody can call
       | it now without providing them all. What a pain! Huge amount of
       | extra code, refactoring, unit testing, because of this.
       | 
       | So either you end up with a cambrian explosion of micro-types or
       | you have these way overspecified interfaces everywhere.
       | 
       | Compare with dynamic languages (or structural typing, Go etc)
       | that only care that things "quack like a duck". The
       | calculate_price function doesn't care what object you give it, as
       | long as it has the two attributes it needs. Now I can unit test
       | `calculate_price` with a 2-element object rather than needlessly
       | creating the 23 irrelevant required elements of a valid Order.
       | 
       | I think a lot could be solved with culture shift. Where data
       | types are really known and locked in, use the crap out of them.
       | As soon as things get ambiguous or flexible, go right ahead and
       | specify that your function takes a Map<String,Object>. If a
       | useful concrete interface emerges at some point factor it out
       | then. The problem is that this is really frowned upon in a lot of
       | places.
        
         | float4 wrote:
         | https://imgflip.com/i/61wqdd
        
         | brundolf wrote:
         | You're putting structural types in the same boat as dynamic
         | types, which I don't think is fair. Some of the most popular
         | static type systems out there have structural typing, including
         | Go (as you mentioned) and TypeScript. And that's not even
         | getting into languages that do extensive type-inference,
         | including TypeScript Haskell and ReScript (which also saves you
         | from locking into over-broad contracts).
        
           | yen223 wrote:
           | One works with the type system they have, not the one they
           | want. Out of the top 20 most popular languages according to
           | Stack Overflow [0], TypeScript and Go are the only
           | statically-typed languages that also have structural types
           | support.
           | 
           | [0]
           | https://insights.stackoverflow.com/survey/2021#technology-
           | mo...
        
         | bqmjjx0kac wrote:
         | > As soon as things get ambiguous or flexible, go right ahead
         | and specify that your function takes a Map<String,Object>.
         | 
         | Dear god, please don't. Some of the worst spaghetti code I have
         | disentangled used this pattern. Typos in the string literals
         | used as keys, object type mismatches, etc.
         | 
         | If you only want some of the fields from another struct, you
         | have a few options
         | 
         | * Define another struct `FooArgs`. This is easy, and I
         | (respectfully) reject the claim that nobody does it.
         | 
         | * Just define your function to take those two fields directly.
        
         | mdoms wrote:
         | Or you could just take the two parameters you're actually using
         | on your function. No new type, no need to pass your mega-
         | object, just take two nice strongly typed arguments.
        
       | armchairhacker wrote:
       | "Why not add X feature? If people don't want to use X, they just
       | don't, and there are basically 0 downsides."
       | 
       | In theory this is true. If the compiler is decent, compile times
       | and analysis shouldn't really be affected. Maybe libraries will
       | use X but otherwise they would use a manual implementation of X
       | anyways.
       | 
       | But in practice developers misuse features, so adding a feature
       | actually leads to worse code. It also creates a higher learning
       | curve, since you have to decide whether to use a new feature or
       | just re-implement it via old features. See: C++ and over-
       | engineered Haskell. So each feature has a "learnability cost",
       | and only add features which are useful enough to outweigh the
       | cost.
       | 
       |  _But_ most features actually are useful, at least for particular
       | types of programs. It 's much harder to write an asynchronous
       | program without some form of async; it's much harder to write a
       | program like a video game without objects. This may be
       | controversial, but I really don't like Go and Elm (very simple
       | languages) because I feel like have to write so much boilerplate
       | vs. other languages where I could just use an advanced feature.
       | And this boilerplate isn't just hard to create, it's hard to
       | maintain because small changes require rewriting a lot of code.
       | 
       | So ultimately language designers need to balance number of
       | features with expressiveness: the goal is to use as few simple
       | but powerful features to make your language simple but really
       | expressive. And different languages for different people.
       | Personally I like working with Java and Kotlin and Swift (the
       | middle languages in the author's meme) because I can establish
       | coding conventions and stick to them, C++ and Haskell are too
       | complicated and it's harder to figure out and stick to the
       | "ideal" conventions.
        
         | preseinger wrote:
         | Absolutely agree.
         | 
         | All features are useful. That's table stakes. But usefulness is
         | insufficient to warrant inclusion. How does a feature interact
         | with all existing features? Are there ambiguities? Are there
         | conflicts? A language is not a grab-bag of capabilities, it's a
         | single cohesive thing that requires design and thought.
        
         | acchow wrote:
         | > But in practice developers misuse features, so adding a
         | feature actually leads to worse code.
         | 
         | I have found the opposite to be true. Missing features often
         | leads to what one would call "design patterns". When the
         | language adds official support to solve the problem you're
         | trying to solve with that pattern, the code becomes clearer.
        
         | throw10920 wrote:
         | > But in practice developers misuse features, so adding a
         | feature actually leads to worse code.
         | 
         | Is that really a problem on the language's side, though? Devs
         | are capable of mis-using _any_ feature, even extremely basic
         | ones that almost every language has (variable names, for
         | instance (although I 'm laughing in FORTH)). Code standards and
         | code reviews are necessary tools in the first place because it
         | doesn't matter what language you give a programmer - they're
         | perfectly capable of constructing a monstrosity in it.
         | 
         | I argue that preventing programmers from doing dumb things with
         | well-designed language features (so, hygenic Scheme macros, and
         | not raw C pointers) is a social and/or organizational problem,
         | and it's better to solve that at that level than to try to
         | solve it (inadequately) at a technical level.
         | 
         | ("I keep dereferencing null pointers", on the other hand, _is_
         | an example of a technical problem that can be solved on the
         | technical level with better language design)
        
       | erichocean wrote:
       | FWIW, I've been developing code directly in MLIR recently, and in
       | MLIR "Comparing types is cool" is indeed true.
       | 
       | It's amazing what you can do when you have compiler
       | transformations and targets always available.
       | 
       | Suddenly, "little DSLs" (MLIR dialects) don't seem so bad, since
       | they are defined the same way and map in semantically-sound ways
       | to lower-level dialects. You can have dedicated dialects, like
       | Halide, for doing something as concrete as image processing
       | kernels.
       | 
       | Oh, and you can output those kernels to both the CPU and GPU,
       | including automatically introducing async functions, host-side
       | sync barriers, etc. Good luck doing that automatically with a
       | general purpose programming language and a combination of macros,
       | AST manipulations, and derived types! You really need a compiler
       | to stay sane.
       | 
       | > _" Programming languages ought to be rethought."_
       | 
       | Indeed.
        
         | raphlinus wrote:
         | Can I pick your brain on MLIR? It sounds awesome from what you
         | describe, but I want to know more about whether it's
         | specialized to machine learning types of workloads or whether
         | it's good for more general things.
        
       | preseinger wrote:
       | > I cannot imagine a single language without the if operator, but
       | only a few PLs accommodate full-fledged trait bounds, not to
       | mention pattern matching. This is _inconsistency_ . . .
       | 
       | How?
       | 
       | > Sometimes, software engineers find their languages too
       | primitive to express their ideas even in dynamic code. But they
       | do not give up . . .
       | 
       | Is this a failure of the language, or a failure of the engineer?
       | 
       | > If we make our languages fully dynamic, we will win biformity
       | and inconsistency,[^] but will imminently lose the pleasure of
       | compile-time validation and will end up debugging our programs at
       | mid-nights . . . One possible solution I have seen is dependent
       | types. With dependent types, we can parameterise types not only
       | with other types but with values, too.
       | 
       | Types are a productive abstraction/model in programming
       | languages. One of many. Each has its strengths and weaknesses;
       | each is appropriate in some circumstances and not in others.
       | Types are not the solution to all problems, any more than
       | currying or OOP or whatever else is.
        
         | gumby wrote:
         | > > I cannot imagine a single language without the if
         | operator...
         | 
         | Production languages (like prolog or make) don't need an if
         | statement or operator as selection is implicit when a
         | production matches.
        
           | oldsecondhand wrote:
           | In Prolog :- is the if operator.
        
           | Hirrolot wrote:
           | Nice point, didn't know about that. My fail.
        
           | jayd16 wrote:
           | Shader languages are also hellbent on avoiding branches too
           | so if is frowned upon and often not used. I could easily
           | imagine not having it in a shader language.
        
             | raphlinus wrote:
             | This is very much changing. IMHO, doing shader language
             | design today, you should give let the programmer express
             | things in the most natural way possible, and let the
             | compiler figure out whether to generate a branch or
             | branchless code. Yes, often you want the latter, but
             | compilers are pretty good at figuring that out.
        
       | Zababa wrote:
       | In the SML/OCaml world there's something like that: there is a
       | difference between types and modules, and functions (from types
       | to types) and functors (from module to module). Work was done on
       | 1ML to unify everything: https://people.mpi-
       | sws.org/~rossberg/1ml/. An extract:
       | 
       | > In this "1ML", functions, functors, and even type constructors
       | are one and the same construct; likewise, no distinction is made
       | between structures, records, or tuples. Or viewed the other way
       | round, everything is just ("a mode of use of") modules. Yet, 1ML
       | does not require dependent types, and its type structure is
       | expressible in terms of plain System Fo, in a minor variation of
       | our F-ing modules approach.
       | 
       | > An alternative view is that 1ML is a user-friendly surface
       | syntax for System Fo that allows combining term and type
       | abstraction in a more compositional manner than the bare
       | calculus.
       | 
       | On the other hand, from the "engineer" point of view, all
       | abstractions melting into one may not be desirable. It's nice to
       | be able to use weak abstractions for simple stuff and powerful
       | abstractions for more powerful stuff. Being exposed to the full
       | complexity of your language all the time sounds like a recipe for
       | disaster.
        
       | dandotway wrote:
       | So whenever I have to study someone else's 'dynamic' python I
       | encounter this sort of thing:                 def foo(bar, baz):
       | bar(baz)           ...
       | 
       | What the heck is 'bar' and 'baz'? I deduce no more than 'bar' can
       | be called with a single 'baz'. I can't use my editor/IDE to "go
       | to definition" of bar/baz to figure out what is going on because
       | everything is dynamically determined at runtime, and even
       | grep -ri '\(foo\|bar\|baz\)' --include \*.py
       | 
       | Won't tell me much about foo/bar/baz, it will only start a hound
       | dog on a long and windy scent trail.
        
         | lkrubner wrote:
         | In Clojure, I tend to put pre and post assertions on most of my
         | functions, which is useful for checking errors in the schema of
         | runtime data (very useful when dealing with 3rd party APIs) but
         | it also offers the documentation that you are seeking:
         | (defn advisories         [config]         {:pre [
         | (map? config)              (:download-advisories-dir config)
         | ]         :post [                 (map? %)                ]
         | }         (let [             dir (:download-advisories-dir
         | config)             ]              ;; more code here
        
           | anyfoo wrote:
           | And now imagine the compiler would actually enforce that
           | practice, and you have static typing, with less boilerplate.
        
           | maleldil wrote:
           | How is this any better than static types?
        
             | Jtsummers wrote:
             | Pre/post conditions are complementary to a type system.
             | They can ensure logical properties that may not be
             | encodable in your underlying type system (that is,
             | essentially every mainstream statically typed language).
             | Such as the relationship between two values in a
             | collection. Trivial example, if you have a range such as
             | [x,y] where x < y must hold, how would you convey that in
             | any mainstream type system?
        
               | yakshaving_jgt wrote:
               | The Haskell-y way to do this is to use a smart
               | constructor[0].
               | 
               | [0]: https://wiki.haskell.org/Smart_constructors
        
               | Jtsummers wrote:
               | The first part of that page demonstrates what amounts to
               | pre/post conditions, but placed in the constructor. The
               | range is checked dynamically, not statically.
               | 
               | The second part is using Peano numbers to enforce the
               | constraint. I guess you could try and force that into
               | some mainstream languages, probably C++. With its
               | template programming you could get something going in
               | this vein, though I'm not sure how well it would work if
               | the number were calculated at runtime rather than compile
               | time. You'd still end up with a dynamic check somewhere.
        
               | yakshaving_jgt wrote:
               | The way that the value floats through the system is
               | checked statically, and the program can (and should) be
               | designed so that the value with the appropriate type
               | cannot be constructed unsafely.
               | 
               | If you need to statically check the construction of
               | values in Haskell, there are things like refinement
               | types[0].
               | 
               | [0]: http://nikita-volkov.github.io/refined/
        
               | Jtsummers wrote:
               | > The way that the value floats through the system is
               | checked statically, and the program can (and should) be
               | designed so that the value with the appropriate type
               | cannot be constructed unsafely.
               | 
               | Except that in the first example from the first link you
               | sent me, there is no _static_ guarantee that the inputs
               | to the constructor are valid, thus the _error_ branch (it
               | would be unnecessary if static guarantees could be made
               | regarding the use of the constructor). And that was my
               | point, that you still end up with _dynamic_ checks on the
               | values which is where pre /post conditions step in to
               | cover what static typing cannot (or, again, cannot easily
               | in _mainstream_ languages, which would not be Haskell).
        
               | yakshaving_jgt wrote:
               | Is there any reason you didn't address the _second_ link
               | that I shared?
        
               | [deleted]
        
         | foxfluff wrote:
         | Static languages unfortunately don't save you from that. You
         | find automatically inferred types, or types that refer to some
         | abstract interface or template-class-mess but you have no idea
         | where the actual implementation lives until you compile with
         | RTTI and run it under a debugger... and as tfa posits, people
         | working with the limitations of static languages often end up
         | reinventing a dynamic structure.
         | 
         | Is this somehow supposed to relevant to the posted article or
         | did you just want to start a tangentially related dynamic-vs-
         | static flame war here in the comments section?
        
           | Diggsey wrote:
           | > Static languages unfortunately don't save you from that.
           | 
           | While you still _can_ make a static language that is
           | confusing, it 's a lot harder... I challenge you to write a
           | function signature in Rust that is both:
           | 
           | 1) Useful
           | 
           | 2) As opaque as the python signature above.
           | 
           | > You find automatically inferred types
           | 
           | A minority of static languages do type inference in function
           | signatures. I think it's a bad idea for exactly the same
           | reason the python code is bad. On the other hand, _every_
           | dynamic language allows you to omit any information about a
           | type signature.
        
           | jacobn wrote:
           | They usually save you from that particular pitfall, but not
           | always of course.
           | 
           | Static vs dynamic makes for such difference in the detailed
           | workflow, both in terms of changing existing code & in terms
           | of writing new/(more) from scratch code, yet they can both be
           | quite fruitful, and can both be abused in absurdum.
           | 
           | It seems like people naturally fall into one of the two camps
           | (either by personality or by training), and the other side
           | just seems kind of insane: "how can you even work that
           | way!?". Then culture and idioms emerge over time and
           | strengthen the tribalism.
           | 
           | I've gone back and forth between the two over the course of
           | my career, and it's quite a mind-shift when switching, with a
           | fair bit of pain involved ("but it would be so easy to do
           | this in [old language]", or "what the hell is this garbage
           | anyway!?") and then eventually it settles in and it's not all
           | painful, all the time ;)
           | 
           | (Going back and forth between Scala and Python right now, so
           | this hit a bit of a nerve)
        
           | hota_mazi wrote:
           | > Static languages unfortunately don't save you from that.
           | You find automatically inferred types,
           | 
           | Oh yes, they do. Even inferred, the types are there and
           | pretty easy to locate, even if you're not using an IDE.
        
             | foxfluff wrote:
             | The types are there.. but you don't know which one it is
             | that your program is dealing with. You could have dozens of
             | implementations for any given abstract interface. One gets
             | picked up at run time.
        
               | Diggsey wrote:
               | You don't need to know which one, because the abstract
               | interface tells you how to use it...
        
               | foxfluff wrote:
               | That's the theory. Works great when there are no bugs and
               | everything's been designed just right. In that world you
               | could wipe implementations from memory because you won't
               | ever need to dive in..
               | 
               | Very often I'm looking at code and "how to use the
               | interface" is not a question I'm looking to find answers
               | for.
        
               | Diggsey wrote:
               | Some information is a lot better than none. In some cases
               | you might want to know what implements the interface:
               | that information is also statically available. In Rust,
               | you can look at a trait and see what types implement that
               | trait.
               | 
               | If you need to know _exactly which implementation is
               | being used in a particular context_ then maybe you
               | shouldn 't be using an interface, but should be using the
               | concrete type?
        
               | foxfluff wrote:
               | > If you need to know exactly which implementation is
               | being used in a particular context then maybe you
               | shouldn't be using an interface, but should be using the
               | concrete type?
               | 
               | Look again at the original comment by dandotway. It's not
               | a question of "what type should I be using here", nor is
               | it "how should I use this interface", but "what do I need
               | to do to understand this code?". Even if an abstract
               | interface is used correctly and is the right thing to do,
               | you still need to understand the code before you can
               | (debug|rewrite|extend|whatever) it.
               | 
               | And it's a pipe dream to say you can look _just_ at the
               | interface. Something blows up, is it the interface used
               | wrong? Maybe, maybe not? Is it the interface implemented
               | wrong? Maybe, first you need to know what implementation
               | you 're looking at. Subtle interactions between abstract
               | and concrete send you spelunking through the layers when
               | you're debugging or trying to extend the interface to
               | account for something the original author didn't
               | anticipate, and often the devil (in the details) comes
               | from concrete implementations.
        
           | dmitriid wrote:
           | > to some abstract interface or template-class-mess
           | 
           | And traits! "Oh look, this functionality is implemented in a
           | trait implemented by a trait implemented by a trait
           | implemented by what you're looking at. Maybe"
        
         | socialdemocrat wrote:
         | That is why I like dynamic languages like Julia better because
         | they use type annotations more frequently.
        
         | vanusa wrote:
         | _What the heck is 'bar' and 'baz'?_
         | 
         | So there's no docstring? And the actual variables are that
         | random and indecipherable?
         | 
         | Sounds like the problem is that you're tasked with looking at
         | code written by someone who is either inexperienced or
         | fundamentally careless. When dealing with reasonably maintained
         | codebases, this kind of situation would seem pretty rare. In
         | modern python we now have type hints of course, which have
         | helped quite a lot.
        
           | timeon wrote:
           | I do not want to argument against Python. This is more like
           | off topic / side note.
           | 
           | What I like about Rust is that it even checks code in your
           | 'docstring'. So it is easier to keep it maintained.
        
           | maxwell86 wrote:
           | > In modern python we now have type hints of course, which
           | have helped quite a lot.
           | 
           | I had to laugh, hard.
           | 
           | If your Python program uses any library whatsoever, chances
           | are that library won't have types, so you can't really use
           | them.
           | 
           | Even super widely used libraries like numpy don't have good
           | support for types, much less any library that consumes numpy
           | for obvious reasons.
        
             | vanusa wrote:
             | _I had to laugh, hard._
             | 
             | It's fine if you want to insulate yourself. But I don't see
             | that you're making much of a point here.
        
               | arc619 wrote:
               | They're probably laughing because a) you're suggesting
               | manually doing the work static typing does in a dynamic
               | language because its untenable not to for large projects,
               | and b) you can't easily add type hints to other people's
               | libraries.
        
               | vanusa wrote:
               | No - (a) is not what I'm suggesting. And (b) while
               | disappointing, just doesn't slow one's work down very
               | frequently in daily practice.
               | 
               | Look, I just don't buy the suggestion that static typing
               | magically solves a huge set of problems (or that it does
               | so without imposing negative tradeoffs of its own -- the
               | very topic of the original article). Or that dynamic
               | languages are plainly crippled, and that one has to be a
               | kind of a simpleton not to see this obvious fact.
        
               | yakshaving_jgt wrote:
               | > I just don't buy the suggestion that static typing
               | magically solves a huge set of problems
               | // compiles and runs, but does bad things       function
               | foo(x, y) {         someDangerousEffect();         return
               | x + y;       }            -- does not compile; huge sets
               | of problems magically solved       foo :: Int -> Int ->
               | Int       foo x y = someDangerousEffect >> pure $ x + y
        
               | vanusa wrote:
               | A problem but not a huge one in practice.
               | 
               | And you're neglecting the part of my statement you
               | conveniently truncated.
        
               | yakshaving_jgt wrote:
               | > not a huge one in practice.
               | 
               | Our experiences have been wildly different :)
        
               | vanusa wrote:
               | Yeah, life sometimes gets that way.
        
               | arc776 wrote:
               | > just doesn't slow one's work down very frequently in
               | daily practice.
               | 
               | Well, maybe you don't feel it slows you down, but it is
               | manual work you must do to get a reliable product only
               | because of dynamic typing. Not only that, but you have to
               | then refer to these docs to check you're not creating a
               | type calamity at some nebulous point down the run time
               | road. Static languages just won't let you mess that up,
               | and often have utilities to generate this documentation
               | for you at no effort.
               | 
               | > I just don't buy the suggestion that static typing
               | magically solves a huge set of problems
               | 
               | Static typing _really does_ "magically" solve a whole
               | class of problems without any negative tradeoffs,
               | assuming the language has decent type inference.
               | 
               | Not all problems, but a specific class of them that you
               | _should_ do extra work to guard against in dynamic
               | languages. Whether that is extra documentation that has
               | to be reliably updated and checked, or run time code to
               | check the types are what you expect at the point of use.
               | 
               | Take for example JavaScript, where typing is not only
               | dynamic, but weak. Numbers in particular can be quite
               | spicy when mixed with strings as I'm sure you know.
               | Strong, static typing forces you to be explicit in these
               | cases and so removes this problem entirely.
               | 
               | By the way, no one's saying anyone is a simpleton. The
               | reality is our field is wide and varied, and different
               | experiences are valid.
               | 
               | Dynamic languages can do some things that static
               | languages can't. For example, you can return completely
               | different types from different execution paths in the
               | same function.
               | 
               | This has been something that has confused me when reading
               | Python, but it does make it easier for stuff like tree
               | parsing. In a static language you need to specify some
               | variant mechanism that knows all data possibilities ahead
               | of time to allow this. From my perspective the dynamic
               | typing trade off isn't worth these bits of 'free' run
               | time flexibility, but YMMV! It really depends what arena
               | you're working in and what you're doing.
        
           | duped wrote:
           | Coming from any statically typed language to Python or
           | JavaScript codebases this plagues me. Virtually every project
           | I have seen suffers from this.
           | 
           | Function names and doc comments describe behavior, not
           | argument and return types.
        
             | vanusa wrote:
             | _Not argument and return types._
             | 
             | When conventions are followed they do exactly that,
             | actually. And unless the code is a complete trainwreck,
             | it's pretty easy to tell what the return type is, even
             | without explicit annotation in the docstring.
        
               | duped wrote:
               | What conventions are those, besides Hungarian notation
               | (which I don't think I've ever seen in Python)?
        
               | vanusa wrote:
               | PEP 257, and local conventions in certain projects.
               | 
               | What all comes down to is: if you really have people on
               | your project writing code like the foo/bar/baz example
               | way up above - then you have problems way bigger than
               | static type checks can possibly help you with.
        
               | duped wrote:
               | Wouldn't it be something if there existed tooling that
               | enforced this level of discipline and checked its
               | validity before executing any code such that you didn't
               | rely on the entire ecosystem to adhere to the same
               | standards and remove that as a source of ambiguity...
        
           | arc619 wrote:
           | In other words, in Python you have to rely on your colleagues
           | manually writing documentation, and if they don't you're out
           | of luck and they're 'bad developers' and potentially the
           | whole product is affected.
           | 
           | In static languages this simply isn't a problem. Types are
           | checked for consistency at compile time and you don't have to
           | rely on people toiling on this busy work.
           | 
           | Not to say documentation isn't necessary, or good, but isn't
           | something you need to create working programs because
           | otherwise no one knows wtf any variable is without running
           | the program.
        
             | prewett wrote:
             | What I've found is even worse: on medium-size personal
             | projects I inevitably pass the wrong type to a function (or
             | refactor and forget to change a call somewhere). Generally
             | it does not fail until somewhere else, which relies on it
             | being a certain type. So even though I know what my
             | functions should take, I still spend a bunch of time
             | tracking down the problems, which involves a printing out a
             | lot of stuff because the error happened outside the current
             | call stack. This is something a static type system would
             | just prevent for me, and I've basically decided that
             | anything beyond really simple stuff is actually faster for
             | me to write in a statically typed language. (Examples in
             | the past are a parser for a static site generator with a
             | mostly Turing-complete language, and 3D model generators)
        
             | vanusa wrote:
             | No, actually I advocate static typing approaches for
             | precisely the reasons you give. As does Python, since years
             | and years ago.
             | 
             | I'm just saying that core problem it solves -- "bad
             | developers" -- is going bite you no matter what (if not
             | addressed at the source). And that supposed magical
             | efficacy of measures designed to protect against it is
             | somewhat overstated.
        
         | lkuty wrote:
         | Indeed. An interesting reading is https://lexi-
         | lambda.github.io/blog/2020/01/19/no-dynamic-typ...
        
           | yakshaving_jgt wrote:
           | This is one of my favourite blog posts on the Internet and I
           | implore every programmer to read it.
           | 
           | > The claim is simple: in a static type system, you must
           | declare the shape of data ahead of time, but in a dynamic
           | type system, the type can be, well, dynamic! It sounds self-
           | evident, so much so that Rich Hickey has practically built a
           | speaking career upon its emotional appeal. The only problem
           | is it isn't true.
        
         | mountainriver wrote:
         | Yup, I find this completely insane behavior to think that you
         | somehow benefit from types not being there.
         | 
         | You just make it way harder for people to understand your code
         | and contribute to it.
        
           | jan_g wrote:
           | To be fair, in recent years I've worked on a number of
           | Typescript projects and it was common for developers to use
           | `any`, `Object`, `() -> Promise<void>`, etc. Not super
           | helpful.
           | 
           | Though in my experience, sane code structure and informative
           | comments trump everything else when it comes to understanding
           | big and unknown codebase. I still shudder when I think about
           | working years ago on various Java codebases (mostly business
           | IT systems). What a convoluted mess of n-levels deep
           | interface hierarchies. Types? Yeah, but good luck unraveling
           | what exactly is happening in the runtime.
        
           | dgb23 wrote:
           | The example is entirely ridiculous. Types are names too. Does
           | foo(bar: baz) solve the issue? Languages are there to convey
           | meaning.
        
           | commandlinefan wrote:
           | It doesn't just make the code harder to read, it makes it run
           | slower, too. Static typing provides some compile-time
           | guarantees about what's going to go where, so the compiler
           | can make a lot of simplifying assumptions that speed things
           | up.
        
             | [deleted]
        
           | vanusa wrote:
           | Insane, no - it's just a tradeoff.
           | 
           | I agree that on balance type signatures are better -- and
           | that's why modern Python has evolved to incorporate them. But
           | they aren't a magic cure-all, and do they impose a
           | significant tax of their own.
        
           | tus666 wrote:
           | Oh yeah the easiest code in the world to read is some
           | contorted type system and function signatures that look like
           | hieroglyphics that you need a PHd in CS to comprehend.
           | 
           | Python is easy to grok, and if you have programmers writing
           | code like bar(foo,baz) then the problem is not Python. You
           | can write crap in any language.
           | 
           | Unit tests do much of what typing checks anyway ... and
           | here's the thing ... you NEED unit tests no matter what. No
           | typing system can tell you that you wrote > when you should
           | have written <.
        
             | hota_mazi wrote:
             | > Python is easy to grok,
             | 
             | It's not, though, it just gives you that illusion.
             | 
             | The code might be easier to read but it's harder to
             | understand and to modify safely because of the absence of
             | type annotations.
        
             | teakettle42 wrote:
             | > Oh yeah the easiest code in the world to read is some
             | contorted type system and function signatures that look
             | like hieroglyphics that you need a PHd in CS to comprehend.
             | 
             | You can also make a book easier to read by ripping out all
             | its pages.
             | 
             | If you eliminate the content you need to read to understand
             | something, what have you actually made easier?
             | 
             | > No typing system can tell you that you wrote > when you
             | should have written <.
             | 
             | There are many that can; e.g. via SMT-decidable refinement
             | types, or even full undecidable dependent types coupled
             | with automated solvers and manual proofs.
        
             | pchangr wrote:
             | Yeah, I always say that python is an amazing language to
             | prototype and terrible language to scale precisely because
             | it lets people write the usual terrible code and then gives
             | you the freedom to make it even worse.
        
             | laumars wrote:
             | > Python is easy to grok
             | 
             | It's what you're used to. I personally find Python horrible
             | to read because I used to a whole different class of
             | programming languages. But I'm sure some of my code might
             | be hard to read by others who aren't used to that
             | particular programming language too.
             | 
             | > Unit tests do much of what typing checks anyway ... and
             | here's the thing ... you NEED unit tests no matter what.
             | 
             | Some, not all. Strictly typed languages are handy when it
             | comes to refactoring and unit tests can sometimes fail
             | there if the design is being changed enough that the unit
             | tests need rewriting too.
             | 
             | > No typing system can tell you that you wrote > when you
             | should have written <.
             | 
             | Not technically true. Some languages with a richer set of
             | types and operator overloading could have code written to
             | detect that sort of thing. But I do get your point that
             | unit tests are import too.
             | 
             | I've been programming for > 30 years and in dozens of
             | different languages. In that time I've felt strictly typed
             | languages make larger and more mature code based slightly
             | easier to maintain. While loosely typed languages are
             | easier for smaller and/or younger code based. But largely
             | it boils more down to personal preference than anything.
             | 
             | I will caveat that by saying the fact that Python supports
             | type annotations should be telling that even dynamic
             | languages benefit from a stricter approach to typing.
        
             | anyfoo wrote:
             | > Oh yeah the easiest code in the world to read is some
             | contorted type system and function signatures that look
             | like hieroglyphics that you need a PHd in CS to comprehend.
             | 
             | People who just learn programming probably think the same
             | about whatever language they are learning.
             | 
             | > No typing system can tell you that you wrote > when you
             | should have written <.
             | 
             | The more the compiler can figure out for you, the quicker
             | problems can be identified and fixed. I stopped using
             | python altogether, because it was just infuriating to have
             | the tiniest mistakes blowing up in spectacular and
             | inscrutable ways. Mixing up values of complex types often
             | does not fail at the actual site of the error, but much,
             | much later. Sometimes literally later in time, as in hours,
             | days, or months until you get an obscure "FooType does not
             | Bar" error, and how the thing in question ever became a
             | FooType is inscrutable at that point. If the result even is
             | a runtime error at all! (Bonus points if your production
             | database is now full of junk as well.[1])
             | 
             | The unit test did not catch it because it did not test the
             | offending composition of classes and functions. Meanwhile,
             | a compiler would have caught it immediately: "The thing
             | you're doing here leads to your data structures being
             | nested wrong."
             | 
             | When I started using async/await in python, at first it was
             | just over, since in plain python that introduces another
             | layer of typing without any assistance whatsoever. Then I
             | discovered mypy which actually lets me do some amount of
             | static typing in python, and it was very enjoyable and now
             | python is back on the table for smaller projects.
             | 
             | There is a reason Haskell has the reputation of "if it
             | compiles, it works". There is a reason why system
             | programmers that work on critical systems are jealously
             | eyeing Rust if their shop still does C.
             | 
             | By the way, dependent type systems absolutely can tell you
             | if you wrote > instead of <. But since that usually comes
             | at the expense of not being Turing complete anymore, it's
             | more used for very critical systems, or for theorem
             | provers.
             | 
             | [1] Yes sqlite, I'm looking at you. The decision to make
             | database column dynamically typed, and hence have for
             | example an INTEGER column silently accept data that is very
             | much not an INTEGER at all, caused me some grief on a
             | widely deployed system once.
        
             | duped wrote:
             | Typing allows you to specify the expected behavior in terms
             | of input/output structure of algorithms in such a way that
             | they can be statically verified without writing unit tests
             | or manual checking code in the source, allowing your unit
             | tests to check behavior by value rather than by value _and_
             | structure. The equivalent of type checking is not unit
             | testing, but fuzzing.
             | 
             | Python is not easy to grok at all, when you consider you
             | have to grok implementations to understand what they are
             | supposed to do, and require extensive runtime debugging to
             | figure out if it is behaving as expected before you can
             | even write unit tests.
             | 
             | Compare to decent statically typed languages, which have
             | quicker write/debug cycles since checking type definitions
             | is faster than checking code behavior, and the structural
             | unit testing is covered automatically by the compiler.
             | 
             | It's like getting more than 50% of your programs' test
             | coverage, for free!
        
         | [deleted]
        
         | aidos wrote:
         | Well, you could put the types on these days.
         | 
         | But also, there's nothing stopping the code from being much
         | clearer about its intention than this weirdly contrived example
         | (I have a lot of code and most the function names are pretty
         | unique). And surely you want to search for 'foo(' to find
         | invocations.
        
         | rightbyte wrote:
         | My worst developer experience ever was trying to make changes
         | to a custom build system with functions exactly like that.
         | Injector or decorator pattern, or what ever it is called.
         | 
         | I just gave up and introduced globals and used as flags for
         | some places in the code.
         | 
         | To make things even more fun, big parts of the system was
         | written in 2.7 called from runtime generated bat files from 3.4
         | as remnants of a rewrite and the consultant that had his
         | funding cut.
        
         | nicoburns wrote:
         | The real power of dynamic languages is being able to do:
         | const foo = JSON.parse(arbitraryJsonString);
         | 
         | and not having to worry about the structure up front.
        
           | ironman1478 wrote:
           | But the thing is, don't you have to worry about structure?
           | You have to unpack the elements from the JSON, so you will
           | need to encode its structure explicitly, which includes type
           | information. The only reason this would be useful is if
           | you're just shuttling data to another API that expects a dict
           | structure (that will then validate everything) and not JSON
           | and you aren't really doing any real work yourself.
        
           | preseinger wrote:
           | Structure is a virtue, not a vice. By doing this you're
           | subverting your own interests.
        
             | pdpi wrote:
             | Structure is a tool. Like any tool, it can be misused or
             | overused.
             | 
             | For anything even remotely production-y I'll always prefer
             | explicitly parsing JSON into a known structure, but there's
             | a lot of value in in being able to do some exploratory
             | scripting without those constraints.
        
               | preseinger wrote:
               | Yes! Exploratory scripting is a categorically different
               | thing than programming, though, I think.
        
               | convolvatron wrote:
               | not necessarily. there is a bottom up school of thought
               | that encourages people to noodle around and construct
               | primitives by playing in the domain, and then
               | interactively composing those primitives into larger and
               | larger systems.
        
               | preseinger wrote:
               | Yeah, that's true. It's a judgment call for sure, but
               | I've always found that angle on things to be self-
               | subversive. The best programmers I know are all bottom-up
               | learners, not top-down.
        
           | naasking wrote:
           | > The real power of dynamic languages is being able to do:
           | const foo = JSON.parse(arbitraryJsonString); and not having
           | to worry about the structure up front.
           | 
           | That's not power, that's a shotgun aimed at your crotch whose
           | trigger is connected to a cosmic ray detector.
        
             | dandotway wrote:
             | +NaN For comments that make me laugh out loud for duration
             | T>2.0 seconds, I wish HN provided a way to
             | transmute/sacrifice one's past karma points into additional
             | +1 mod points.
        
           | cheriot wrote:
           | There's something to this. I love featurefull type systems,
           | but I've seen engineers try to parse JSON the "right" way in
           | Scala, get frustrated, and blame the entire concept of
           | statically typed languages. Elm manages to make this user
           | friendly so perhaps it's "only" a matter of compiler messages
           | and API design?
        
           | mountainriver wrote:
           | You can do that in static languages too by just parsing into
           | a map
        
             | tus666 wrote:
             | What's the type definition of the map out of interest?
        
               | nyberg wrote:
               | const Json = union(enum) {           null,
               | number: f64,           bool: bool,           string:
               | []const u8,           array: []const Json,
               | object: HashMap([]const u8, Json),       };
               | 
               | Usually it's a tagged union of the base JSON types which
               | can easily be consumed by most statically typed languages
               | or a variant of it.
               | 
               | EDIT: added "tagged"
        
               | pas wrote:
               | In TS JSON is usually Record<string, unknown>.
        
               | tus666 wrote:
               | Well unknown is not a type (by definition), so you have
               | just stepped outside of a type system, which is very
               | common in TS if I understand.
        
               | pas wrote:
               | Unknown is a top type in the TS type system. It serves
               | the very important role of "here you need to apply some
               | pattern matching and validation" and then you can make
               | sure that you can continue working in a type safe
               | environment. TS has a lot of facilities that help you
               | with this (from the usual narrowing things, typeof and
               | instanceof guards, control flow analysis, and at the end
               | of the list there are the big guns like this thing called
               | type predicates which basically allows you to wrap these
               | checks and "casts" in nice reusable functions).
               | 
               | There are also recursive types that help you model JSON,
               | but knowing that it's an arbitrary deep nested map/list
               | of maps/lists and number and bool and string mixed like a
               | Bloody Mary cocktail doesn't really help :)
               | 
               | With NestJS it's very easy to add decorators/annotations
               | to fields of a class, and the framework handles
               | validation (throws HTTP 422 with nice descriptions of
               | what failed) and then in your controller you can again
               | work in a type safe environment.
               | 
               | https://www.typescriptlang.org/docs/handbook/release-
               | notes/t...
               | 
               | https://www.typescriptlang.org/docs/handbook/2/narrowing.
               | htm...
        
               | staticassertion wrote:
               | Json = Map<string, Json>
        
               | steveklabnik wrote:
               | In Rust:
               | https://docs.serde.rs/serde_json/value/enum.Value.html
        
           | HideousKojima wrote:
           | In C# I can just parse it to a Dictionary<string, object>,
           | and I can do that _without_ destroying the usability of the
           | rest of the language.
        
             | tomp wrote:
             | What if the JSON represents a list, or an int?
             | 
             | Also, how do you then access nested objects, like
             | data['key'][0]['attr'] in Python?
        
             | tus666 wrote:
             | Well in Python everything is an object, so that type
             | definition holds true for Python as well :P
        
             | jayd16 wrote:
             | You can parse it to the dynamic type too. Everyone is
             | happy...right?
        
           | yakshaving_jgt wrote:
           | You can trivially do exactly the same thing in Haskell, so I
           | think you're suggesting that dynamic languages have no "real
           | power".
        
           | adwn wrote:
           | Rust:                   let foo: serde_json::Value =
           | serde_json::from_str(arbitraryJsonString)?;
           | 
           | There, just as powerful [1]. But you know what's even more
           | powerful? After you've done your dynamic checks, you can do
           | this on the entire JSON tree, or on a subtree:
           | let bar: MyStaticType = serde_json::from_value(foo)?;
           | 
           | and you get a fully parsed instance of a static type, with
           | all the guarantees and performance benefits that entails.
           | 
           | [1] _Value_ represents a JSON tree:
           | https://docs.serde.rs/serde_json/enum.Value.html
        
           | commandlinefan wrote:
           | I thought the real power of dynamic languages was being able
           | to do things like:                  eval('alert("hello, ' +
           | userInput.name + '!")')
        
           | woodruffw wrote:
           | Every static language that I know of also supports this --
           | you can parse into a sum type of `JsonAny` (or whatever),
           | where `JsonAny` is one of `Null | Number | String | List[Any]
           | | Dict[String, JsonAny]`.
           | 
           | The API then becomes a runtime fallible one, which is
           | perfectly sound.
        
           | laumars wrote:
           | That's not a capability unique to dynamic languages
        
           | sandofsky wrote:
           | When that JSON payload changes (intentionally or not), you
           | will run into a mysterious problem in some unrelated area of
           | your code. It will be significantly more expensive to fix
           | than failing fast at the point of parsing.
        
             | suzzer99 wrote:
             | But depending on the situation, that problem may never
             | happen. I'm not a big fan of introducing complexity to
             | guard against code screwups in the same codebase.
        
               | sandofsky wrote:
               | If you work somewhere APIs never change, and your backend
               | people never ship bugs, that's great, but I feel it's
               | rare.
               | 
               | In a modern language like Swift, this sort of type safety
               | is pretty simple. You declare a struct, and slap a
               | _Codable_ on it.                 struct User:Codable {
               | var id:String         var username:String       }
               | 
               | Now you'll just error out if the data is invalid. And no
               | more forgetting that _id_ is a string, not a number. And
               | as a bonus, your IDE will auto complete properties as you
               | type them.
               | 
               | Even when revving on a pre-production API, I find it an
               | incredible time saver. Decided to change a property name
               | from "username" to "screen_name"? Your IDE will rewrite
               | will gladly rewrite the hundred old references in
               | seconds.
               | 
               | I find this way simpler that passing strings to black
               | boxes.
        
               | cdaringe wrote:
               | The hardest bugs are the rare bugs. Common and frequent
               | problems are easy bugs to fix. These subtle or rare
               | changes are those which should be feared. If you model
               | only that which you support, finding the source of the
               | problem becomes much easier.
        
           | arc776 wrote:
           | In Nim you can even convert JSON to static types!
           | https://nim-
           | lang.org/docs/json.html#to%2CJsonNode%2Ctypedesc...
           | 
           | Now you get type checking on JSON at compile time :)
        
           | valcron1000 wrote:
           | What's 'foo' and what you can do with it?
        
             | carnitine wrote:
             | An arbitrary object? What else would arbitrary JSON parse
             | into? Then you can access its properties, like with any JS
             | object.
        
         | solox3 wrote:
         | > I can't use my editor/IDE to "go to definition" of bar/baz
         | 
         | I use "Find Usages" on foo to see where it is used. Once you
         | see where it is used, you know what the types can be. It's not
         | great, but it's also something that can be progressively
         | remedied.
         | 
         | In the event that the function is not as trivial as your
         | example suggests, the author should have written a docstring to
         | help you understand what it is trying to do, in addition to
         | type annotations that will make it more readable.
         | 
         | In this example, bar can either be a function, a class, or any
         | object with __call__(), so the type information is less
         | important in this case, than actual docstrings that express
         | intent.
        
       | dleslie wrote:
       | I'm unfamiliar with one of the language logos in the meme graph
       | at the bottom: what's the red swooshy thing beside zig?
        
         | andrenth wrote:
         | It's the Idris logo https://www.idris-lang.org/
        
         | [deleted]
        
         | [deleted]
        
         | [deleted]
        
       | devit wrote:
       | Yeah, the current problem is that Idris code is far less
       | efficient than Rust code, because Idris boxes everything and
       | erases all types, and also Idris's support for borrowing seems
       | less powerful than Rust (it lacks first-class mutable borrows as
       | far as I can tell).
       | 
       | It seems that fixing this is a research problem, which would lead
       | to the holy grail of programming languages, i.e. an ultimate
       | language that is as expressive as Idris and as efficient as Rust,
       | and is thus essentially perfect.
        
         | preseinger wrote:
         | Expressiveness is not an unambiguous net good -- more
         | expressiveness is not a priori better. Expressiveness carries
         | costs of comprehension and coherence that need to be
         | appropriately weighed in the contexts where the language will
         | be applied.
         | 
         | Programming languages are not theoretical things. They're
         | concrete, practical tools that _enable_ other stuff.
         | Engineering, not science.
        
           | ImprobableTruth wrote:
           | How would you define expressiveness (as its commonly used, so
           | a definition where Turing complete languages can have
           | different expressiveness) if not as how much something can be
           | simplified and thus aiding comprehension, rather than
           | detracting from it?
           | 
           | >Programming languages are not theoretical things. They're
           | concrete, practical tools that _enable_ other stuff.
           | Engineering, not science.
           | 
           | You can't escape theory, engineering is applied science.
        
             | preseinger wrote:
             | Increasing expressiveness of a language necessarily
             | increases its complexity. Comprehension is important but
             | it's a function of "the whole stack" -- language and
             | program both.
             | 
             | > engineering is applied science.
             | 
             | Absolutely. But the metrics are different.
        
         | siknad wrote:
         | > an ultimate language that is as expressive as Idris and as
         | efficient as Rust, and is thus essentially perfect.
         | 
         | Are both Idris's expressiveness and Rust's efficiency (given
         | stronger guarantees) perfect? Aren't theese languages really
         | complex both to learn and to write? There are poblems without a
         | solution, perfect and unique to all of them.
        
         | dwohnitmok wrote:
         | > Idris's support for borrowing seems less powerful than Rust
         | (it lacks first-class mutable borrows as far as I can tell).
         | 
         | Depends on what you mean. Idris's notion of multiplicities
         | essentially subsumes Rust's borrowing (there's some differences
         | with affine vs linear types), so I can't think off the top of
         | my head of things that you can ensure with Rust that you can't
         | with Idris, but Rust has a lot more quality of life
         | improvements that make things less clunky (also having a GC,
         | Idris can get away with a lot less need for borrowing in the
         | first place).
        
         | zozbot234 wrote:
         | The Prusti effort to endow Rust with proof-carrying code is
         | also worth mentioning. There are some reasons to expect this
         | approach to be more fruitful than an actual extension of
         | dependently-typed languages, since the type system features of
         | Rust itself are hard to integrate with dependent types. (At
         | best, it might be somewhat feasible to use the latter in the
         | `const`, compile-time evaluated subset of the language.)
        
       | preordained wrote:
       | Having used Clojure for a while now, I will say having 90% of
       | things be a primitive, map, or vector goes a long way in and of
       | itself. A lot of types concocted in a more conventional language
       | just don't need to exist, IMO, and they create so much baggage
       | around themselves.
        
         | Zababa wrote:
         | You know what they say about people with hammers.
        
         | zmmmmm wrote:
         | Hmm, how well does this scale though? you are passing around
         | these giant maps of vectors of tuples and then you pass it to
         | someone unfamiliar with the code, how the hell do they know
         | what's in there? Is the order price the first element of the
         | tuple or the second? What happens when I refactor things and
         | now all the tuple elements shift over one? Surely you'll end up
         | writing just as much in documentation as you would have to
         | specify the types?
         | 
         | Currently working my way through some complex Python code
         | written in that style and it's completely impossible to
         | understand it. In fact, the only way I can actually do it is
         | transforming all these ad hoc data structures into proper types
         | so I can make sense of it.
        
       | adamddev1 wrote:
       | I wonder where TypeScript would fall on this language continuum?
        
         | dnautics wrote:
         | My guess: It wouldn't because this is about static languages.
         | Typescript is still a dynamic language with a very smart
         | (probably best-in-class at this point in time) compile-time
         | typechecker/static analysis tool.
        
       | batrachos wrote:
       | I dislike the phrase 'dynamic language' and especially dislike
       | the phrase 'static language'. We should say 'dynamically typed'
       | or 'statically typed', because 'static' languages are the site of
       | major dynamism.
        
         | chriswarbo wrote:
         | I think 'dynamic language' is appropriate here, since it's not
         | _only_ talking about types; it 's largely talking about macros,
         | pre-processors, reflection, etc. too.
         | 
         | Also, the main argument is that separating features into those
         | used at compile-time (AKA static) and run-time (AKA dynamic) is
         | necessarily creating separate _languages_ (i.e. a  "static
         | language", which may involve types, macros, preprocessors,
         | etc.; and a "dynamic language", which may involve memory
         | allocation, branching, I/O, etc.)
        
       | tempodox wrote:
       | This article spends many words to say, "there is no silver
       | bullet".
       | 
       | But dynamically typed languages produce at least the same amount
       | of accidental complexity, just in different ways.
        
         | lambdasquirrel wrote:
         | Indeed, the article has it backwards. The types are always
         | there. Your program will fail at runtime if it's not correct.
         | The type system merely surfaced that.
         | 
         | Complexity in the types happens when the type system isn't
         | expressive enough. Or when you're trying to do something that
         | would make the compiler try to solve the halting problem.
         | 
         | To that last point, this is why the PLT community has pushed in
         | the direction that Agda / Idris has. Kind of like how we
         | realized years (decades?) ago that we didn't need pointer
         | arithmetic, there's been a realization that "total" isn't
         | actually that helpful, and it's okay if we didn't have
         | languages that could express the halting problem.
        
           | skybrian wrote:
           | That's the hope, but saying there's something wrong is
           | insufficient. The compile-time errors need to be
           | understandable, or it's just going to be frustrating.
           | 
           | Maybe we should judge compile-time constraint systems by how
           | easy it is for the library author to add good error messages
           | for misuse?
        
         | Hirrolot wrote:
         | > This article spends many words to say, "there is no silver
         | bullet".
         | 
         | Rather "I believe there is a silver bullet, but I don't know
         | where yet". Probably I am too naive!
        
       ___________________________________________________________________
       (page generated 2022-01-19 23:01 UTC)