[HN Gopher] Why Static Languages Suffer from Complexity (2022)
       ___________________________________________________________________
        
       Why Static Languages Suffer from Complexity (2022)
        
       Author : mpweiher
       Score  : 73 points
       Date   : 2023-08-09 14:57 UTC (8 hours ago)
        
 (HTM) web link (hirrolot.github.io)
 (TXT) w3m dump (hirrolot.github.io)
        
       | recursivedoubts wrote:
       | i have to be totally honest that I understand about 5% of this
       | post
       | 
       | nonetheless, I will offer my obviously uninformed opinion on the
       | topic (hey, it's hacker news):
       | 
       | a big problem w/ static languages is that the developers of these
       | languages are not willing to compromise type-safety for
       | simplicity: there are very few 80/20 type systems out there that
       | just say "Well, that's too hard to deal w/ you can bail out to
       | unsafe code" when the implementation gets too crazy.
       | 
       | My experience here is with going through the java generics
       | cluster and helping write a statically typed scripting language
       | that integrates w/ it effectively.
       | 
       | As a positive example of what I'm talking about with respect to
       | an 80/20 type system, consider a type system that supported
       | covariant generics + function types that were contravariant wrt
       | their argument types and covariant wrt their return types.
       | 
       | This, I believe, would capture most of the correctness value of
       | types and certainly provide good infrastructure for code
       | completion and tooling, and it would be very simple to implement.
       | It would not, however, be sound, and, thus, would be laughed out
       | of any serious statically typed language discussions.
        
         | xwowsersx wrote:
         | Interesting. I think that many statically typed languages bail
         | out too early! That's obviously a subjective view, but I
         | suspect there's a large enough group of people who feel this
         | way which is why much stricter and more unforgiving static type
         | systems are developed and popularized.
        
         | ragnese wrote:
         | > a big problem w/ static languages is that the developers of
         | these languages are not willing to compromise type-safety for
         | simplicity: there are very few 80/20 type systems out there
         | that just say "Well, that's too hard to deal w/ you can bail
         | out to unsafe code" when the implementation gets too crazy.
         | 
         | Well, this is the internet, so there will always be someone to
         | disagree with you. In this case, I get to be one of those
         | people for you! Yay, you. :)
         | 
         | I find that most statically typed languages _do_ bail out on
         | type safety for simplicity for very many cases.
         | 
         | Take arithmetic. Just about every statically typed language
         | allows you to add two `int32`s together to get another `int32`
         | with no acknowledgement in the type system that this operation
         | my fail (overflow/underflow). Some languages will wrap the
         | value around, and some will actually abort, but those are
         | "runtime" decisions and not in the static type systems.
         | 
         | Similarly, most languages allow us to compare IEEE float values
         | for equality with no complaints from the type checker.
         | 
         | Most languages allow us to index an array and assume that we
         | will always receive a value.
         | 
         | It's funny that you mention Java's generics, because they are
         | kind of implemented in the exact 80/20 way you describe. Java's
         | generics are type-erased at compile time, so all of these
         | generic values and functions actually end up working in terms
         | of `Object` (the base/root reference type) and doing runtime
         | casts where needed. The only reason that's possible is because
         | of the JVM's type system allowing for all of that loosey-goosey
         | stuff.
        
           | alcover wrote:
           | > Some languages will wrap the value around, and some will
           | actually abort
           | 
           | What if a language had clamped _int_ and modular _mod_ types
           | ? Overflow on _mod_ would work as advertised and, if you
           | disabled _abort_ , overflow on _int_ would be closer to its
           | virtual value than if wrapped.                 mod32 m =
           | UINT_MAX+1       //-> 0       int32 i = INT_MAX+1       //->
           | INT_MAX
        
             | Jtsummers wrote:
             | Ada offers modular types (ranging from 0 to modulus-1 as
             | expected) with the overflow and underflow behavior you
             | want, but not a clamped type as you describe it. So you can
             | get the first but not the second behavior.
        
               | alcover wrote:
               | So, Ada modular types are just like C _uint_ ?
        
               | Jtsummers wrote:
               | Except that it allows for an arbitrary modulus. For
               | instance, you can have:                 type
               | Whatever_Name is mod 10;
               | 
               | Removing restrictions is nice, lets the language and its
               | type system actually express parts of the program logic
               | unlike the inexpressive type system C provides.
        
               | alcover wrote:
               | Ok that's nice. Would one call that a _dependent type_ ?
               | I guess I would naively implement this by appending _%10_
               | to all assignments to a _Whatever_Name_.
        
               | Jtsummers wrote:
               | No. Dependent types go further and allow you to do things
               | like express that the length of an output vector/array is
               | the same as some input number. That is, depends on some
               | value (as in a runtime value). This mod type is fixed at
               | compile time.
        
             | nuancebydefault wrote:
             | I don't know of any language that has these types, i
             | personally find them not so handy. What would e.g. happen
             | if you mix both types? If you want mod or clip
             | functionality for a certain part of an algo, it is easy
             | enough to make a function that does it, you can overload
             | the + operator if you want the algo code to read or write
             | more easily. Also, in C it is simply modulo (straight what
             | the CPU does) and in python there's simply no overflow,
             | every int is an object that can be as big as needed. Both
             | takes are consistent and rather straightforward to
             | understand.
        
               | alcover wrote:
               | > What would e.g. happen if you mix both types?
               | 
               | Like in                 int32 y=INT_MAX       mod32 x=1
               | y+=x
               | 
               | ? You'd get _y==INT_MAX_. Or did you mean something more
               | involved ?
               | 
               | > you can overload the + operator
               | 
               | I fail to see the need. (Sorry I'm a bit tired). In this
               | thread context of static languages, your compiler knows
               | the target type of an assignement (clip/mod) and so
               | applies the right correction.
               | 
               | > in python there's simply no overflow, every int is an
               | object that can be as big as needed
               | 
               | I guess there's a huge speed penalty in wrapping values
               | into objects.
        
               | touisteur wrote:
               | As for saturated arithmetic you can (as in many
               | languages) overload operators (with added post condition
               | that you can check at runtime, or prove statically, if
               | you kept the SPARK provable subset of Ada).
        
         | dinosaurdynasty wrote:
         | TypeScript is like this (can just give up and `as any`
         | anything).
        
           | BenoitP wrote:
           | An actual positive in my book. If you consider back
           | propagation of constraining types wrt the derivative of
           | developer effort you get smooth gradients.
           | 
           | Program works and is correct, but developer gets yelled at by
           | its IDE (IntelliJ does) until there's a proof of it to some
           | degree.
           | 
           | I wish that types and proofs were more progressive. For
           | example in Java, why can't we have the compiler tell us
           | what's missing for proving a variable does not escape and we
           | have to wait runtime to see if we had the optimization?
           | 
           | Sometimes we'd like to guarantee it, and not just get it
           | opportunistically.
        
           | the_gipsy wrote:
           | I find that 99% of my `as any` are due to some type
           | definition limitation. The remaining 1% is due to something
           | not expressable in typescript (or not worth it).
        
         | Someone wrote:
         | > a big problem w/ static languages is that the developers of
         | these languages are not willing to compromise type-safety for
         | simplicity: there are very few 80/20 type systems out there
         | that just say "Well, that's too hard to deal w/ you can bail
         | out to unsafe code" when the implementation gets too crazy.
         | 
         | Very few? Many languages allow you to cast references to other
         | types at will.
         | 
         | C# does one better with its 'dynamic' keyword, which makes it
         | much clearer where you are using runtime method lookup.
         | https://learn.microsoft.com/en-us/dotnet/csharp/advanced-
         | top...:
         | 
         |  _"The dynamic type is a static type, but an object of type
         | dynamic bypasses static type checking. In most cases, it
         | functions like it has type object. The compiler assumes a
         | dynamic element supports any operation"_
         | 
         | I think Swift copied that. https://docs.swift.org/swift-
         | book/documentation/the-swift-pr...:
         | 
         |  _"When you mark a member declaration with the dynamic
         | modifier, access to that member is always dynamically
         | dispatched using the Objective-C runtime."_
        
           | brabel wrote:
           | Add Dart to your list.
        
           | Akronymus wrote:
           | F# goes even further than c# and allows you to embed IL.
           | Which allows you to do really fun things.
        
           | KaiserPro wrote:
           | I do like c#'s dynamic and unsafe bits.
           | 
           | it clearly telegraphs that someone is doing something either
           | clever, stupid, desperate or all three.
        
         | ramesh31 wrote:
         | >there are very few 80/20 type systems out there that just say
         | "Well, that's too hard to deal w/ you can bail out to unsafe
         | code" when the implementation gets too crazy.
         | 
         | This is what I love so much about Objective-C (and superset
         | languages in general, including Typescript). You can play
         | around all you want in ARC world, but drop straight down into C
         | whenever it gets too bloated.
        
         | FrustratedMonky wrote:
         | Like auto type conversions. Instead of barfing when assigning a
         | Float to an Int, just auto round? Or but then if assigning a
         | string to a float, then barf. Does this get 80%? I think
         | anything beyond the simple cases, static enforcement is better,
         | or you could be just getting garbage.
        
           | Someone wrote:
           | > Instead of barfing when assigning a Float to an Int, just
           | auto round?
           | 
           | Because you can't really round many floats to Int.
           | 
           | A float can be larger than 10^38. If you 'round' that to a
           | 64-bit signed integer, you have to return something that's
           | smaller than 10^19.
           | 
           | For doubles, it's even worse. They can be larger than 10^308.
           | 
           | I think that indicates that "just auto round" isn't an
           | option.
           | 
           | The option "if a reasonable conversion exists, make it,
           | otherwise bail out" IMO is a lot worse than having a way to
           | try that conversion and somehow indicate to the programmer
           | when it failed.
        
           | JohnFen wrote:
           | > Instead of barfing when assigning a Float to an Int, just
           | auto round?
           | 
           | Rounding involves a loss of data. If an operation would
           | result in data loss, that operation should not be automatic.
           | It should only be possible by the dev intentionally choosing
           | it.
        
           | junon wrote:
           | Auto round is rarely the operation you want.
        
           | recursivedoubts wrote:
           | no, i don't think that's where the type system implementation
           | complexity usually explodes, in fact that's a more
           | complicated type system than requiring explicit type
           | conversions (which I am usually in favor of, except in cases
           | of toString)
        
         | bedobi wrote:
         | Not sure what you're getting at? Even in Haskell you can easily
         | pass data around as untyped maps of maps if you want. But
         | usually you don't. But maybe you're talking more about when
         | generics and obsession with not duplicating any code ever makes
         | that part of the type system needlessly complex
        
           | recursivedoubts wrote:
           | i'm talking about the type system itself: keeping the type
           | system implementation itself simple rather than just offering
           | an unsafe work-around
           | 
           | this means foregoing system-enforceable type safety and
           | features in some cases as the language level, in order to
           | keep the type system fast and understandable to normies.
        
             | ses1984 wrote:
             | The type system can be understandable to normies while the
             | implementation is not. I would rather have a type system
             | that is correct and consistent, with the complexity hidden
             | away, than to have to know where the boundary is in the
             | 80/20 system, which seems like the perfect place for bugs
             | to enter your code.
        
               | ragnese wrote:
               | Agreed. Consistent is WAY better for me than
               | inconsistent. One of the reasons I hate TypeScript so
               | much is that it provides type safety except when it
               | doesn't, and it takes a lot of experience to actually
               | track down the MANY (non-obvious) places where it
               | doesn't.
        
             | mrkeen wrote:
             | Agreed. Leave out the inheritance, covariance,
             | contravariance, and nullable types.
             | 
             | Leave the type system simple enough that you can take a
             | rest and let the computer do the typing work for you.
        
         | seanmcdirmid wrote:
         | TypeScript is obviously one of those 80/20 languages, and I
         | like programming in it accordingly. But fighting the type
         | system is one of the easier tasks a programmer could spend
         | their time on if reliability was significantly improved via
         | type safety. For example, null pointer static checking is a bit
         | of a pain but has a significant pay off that probably makes it
         | worth it.
        
         | dkarl wrote:
         | > there are very few 80/20 type systems out there that just say
         | "Well, that's too hard to deal w/ you can bail out to unsafe
         | code" when the implementation gets too crazy
         | 
         | Scala is built on an OO/imperative functional foundation, with
         | powerful enough types to build pure FP constructs on top. The
         | problem with tradeoffs is not that the language won't let you,
         | but in the culture: it's hard to get different Scala
         | programmers to agree on tradeoffs when weighing type safety and
         | FP purity against code complexity.
        
         | munificent wrote:
         | _> As a positive example of what I 'm talking about with
         | respect to an 80/20 type system, consider a type system that
         | supported covariant generics + function types that were
         | contravariant wrt their argument types and covariant wrt their
         | return types._
         | 
         | Java and C# are that with respect to array types. Arrays in
         | both are covariant , which is unsound since arrays are mutable,
         | and erroneous uses are checked at compile time.
         | 
         | Dart is exactly the language you describe: All generic type
         | parameters are treated covariantly and we use runtime checks to
         | preserve soundness. It's... OK. It works out mostly OK in
         | practice because the generic types that users happen to use
         | variantly are conveniently used in a safely covariant way:
         | Reading stuff out of Iterables and Maps.
         | 
         | But there is a significant performance cost to the runtime
         | checks needed to preserve soundness. And when you using a type
         | in a covariant way incorrectly, it is _deeply_ confusing to
         | users.
         | 
         | I think the right 80/20 approach is to do what C# 2.0 and just
         | make all generics invariant.
        
       | kazinator wrote:
       | > _Dynamic languages, on the other hand, suffer from these
       | drawbacks to a lesser extent, but they lack compile-time checks._
       | 
       | I.e. "The two or three popular dynamic programing languages I
       | know don't have any compile-time checks".
       | 
       | Even my modest dialect (utterly not focused on type at all) Lisp
       | can do a thing or two:                 1> (compile-toplevel '(let
       | (x) (cons a)))       ** expr-1:1: warning: unbound variable a
       | ** expr-1:1: warning: cons: too few arguments: needs 2, given 1
       | ** expr-1:1: warning: let: variable x unused       #<sys:vm-desc:
       | 8da2ac0>       2> (compile-toplevel '(awk ((let (x y) (rng x y))
       | (prn))))       ** expr-2:1: warning: rng: form x
       | is moved out of the apparent scope
       | and thus cannot refer to variables (x)       ** expr-2:1:
       | warning: rng: form y                                  is moved
       | out of the apparent scope                                  and
       | thus cannot refer to variables (y)       ** expr-2:1: warning:
       | unbound variable x       ** expr-2:1: warning: unbound variable y
       | ** expr-2:1: warning: let: variable y unused       ** expr-2:1:
       | warning: let: variable x unused       #<sys:vm-desc: 8e6f3c0>
       | 3> (compile-toplevel 'foo.bar)       ** expr-3:1: warning: qref:
       | bar isn't the name of a struct slot       ** expr-3:1: warning:
       | unbound variable foo       #<sys:vm-desc: 8e8dc80>
       | 
       | Common Lisp implementations like SBCL have sophisticated type
       | inference.
       | 
       | Dynamic programs can do things wrong in ways that are statically
       | obvious.
        
       | SkyMarshal wrote:
       | _> People in the programming language design community strive to
       | make their languages more expressive, with a strong type system,
       | mainly to increase ergonomics by avoiding code duplication in
       | final software;_
       | 
       | I've never heard this, thought the reason was to eliminate
       | classes of errors at compile time and to make large-scale
       | refactoring easier, among other things. Or did I just miss this
       | rationale?
       | 
       | Here's the lede:
       | 
       |  _> Let us think a little bit about how to workaround the issue.
       | If we make our languages fully dynamic, we will win biformity and
       | inconsistency 13, but will imminently lose the pleasure of
       | compile-time validation and will end up debugging our programs at
       | mid-nights. The misery of dynamic type systems is widely known._
       | 
       |  _The only way to approach the problem is to make a language
       | whose features are both static and dynamic and not to split the
       | same feature into two parts. Thus, the ideal linguistic
       | abstraction is both static and dynamic; however, it is still a
       | single concept and not two logically similar concepts but with
       | different interfaces 14. A perfect example is CTFE, colloquially
       | known as constexpr: same code can be executed at compile-time
       | under a static context and at run-time under a dynamic context
       | (e.g., when requesting a user input from stdin.); thus, we do not
       | have to write different code for compile-time (statics) and run-
       | time (dynamics), instead we use the same representation._
       | 
       |  _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. In a dependently typed language
       | Idris, there is a type called Type - it stands for the "type of
       | all types", thereby weakening the dichotomy between type-level
       | and value-level. Having such a powerful thing at our disposal, we
       | can express typed abstractions that are usually either built into
       | a language compiler /environment or done via macros. Perhaps the
       | most common and descriptive example is a type-safe printf that
       | calculates types of its arguments on the fly, so let give us the
       | pleasure of mastering it in Idris 15!_
       | 
       | ...
       | 
       |  _Overall, the design of Zig's type system seems reasonable:
       | there is a type of all types called type, and using comptime, we
       | can compute types at compile-time via regular variables, loops,
       | procedures, etc. We can even perform type reflection through the
       | @typeInfo, @typeName, and @TypeOf built-ins! Yes, we can no
       | longer depend on run-time values, but if you do not need a
       | theorem prover, probably full-blown dependent types are a bit of
       | overkill._
       | 
       | ...
       | 
       |  _Everything is good except that Zig is a systems language. On
       | their official website, Zig is described as a "general-purpose
       | programming language", but I can hardly agree with this
       | statement. Yes, you can write virtually any software in Zig, but
       | should you? My experience in maintaining high-level code in Rust
       | and C99 says NO._
       | 
       | ...
       | 
       |  _Zig can still be used in large systems projects like web
       | browsers, interpreters, and operating system kernels - nobody
       | wants these things to freeze unexpectedly. Zig's low-level
       | programming features would facilitate convenient operation with
       | memory and hardware devices, while its sane approach to
       | metaprogramming (in the right hands) would cultivate
       | understandable code structure. Bringing it to high-level code
       | would just increase the mental burden without considerable
       | benefits._
       | 
       | ...
       | 
       |  _Final Words
       | 
       | Static languages enforce compile-time checks; this is good. But
       | they suffer from feature biformity and inconsistency - this is
       | bad. Dynamic languages, on the other hand, suffer from these
       | drawbacks to a lesser extent, but they lack compile-time checks.
       | A hypothetical solution should take the best from the both
       | worlds.
       | 
       | Programming languages ought to be rethought._
        
         | Ygg2 wrote:
         | Interesting article but I disagree with conclusion.
         | 
         | Namely these two do not compute:
         | 
         | > if you choose the C-way manual memory management, you will
         | make programmers debugging their code for long hours with the
         | hope that -fsanitize=address would show something meaningful
         | 
         | > Zig can still be used in large systems projects like web
         | browsers, interpreters, and operating system kernels - nobody
         | wants these things to freeze unexpectedly.
         | 
         | But Zig isn't meaningfully safer than C. So you still get to
         | debug code for long hours hoping you'll spot the error.
        
         | brabel wrote:
         | > Or did I just miss this rationale?
         | 
         | Nope, you're spot on: better refactoring and increased
         | correctness are the biggest motivators for type systems to
         | exist... I don't even know what the author means by suggesting
         | that a strong type system avoids code duplication (as someone
         | who has used type systems for decades)!! Can someone
         | illuminate?
        
       | Pet_Ant wrote:
       | I always assumed that complex type systems were driven by
       | enabling better optimization through program analysis since there
       | is more information about guarantees to work with.
        
         | feoren wrote:
         | That's basically wrong. Type systems are driven by something
         | that is much harder and much more valuable than optimization:
         | writing correct programs. Optimization is "strictly" less
         | important than correctness, which means that there is no amount
         | of improvement to optimization that is worth _any_ loss of
         | correctness.
         | 
         | Okay, okay, there are a million caveats and side-notes to this,
         | as there always are. This topic could (and does) fill
         | textbooks. Let's get just a few out of the way:
         | 
         | * Accepting lower precision in results is, of course, often
         | done in the name of optimization. In these cases, there is a
         | narrow band of "acceptable" outcomes; a small level of
         | acceptable error that is still called "correct enough", so I'd
         | argue correctness is still massively at a premium. In 3D games,
         | very minor graphic artifacts on the level of 1% loss of
         | "quality" are acceptable if it means a 5x speedup; 2% is
         | probably not. So accuracy is "merely" 5,000 times more
         | important. Getting within a factor of 10^-6 of the "true"
         | result is acceptable in numerical analysis if it results in a
         | 10x speedup, so accuracy is "merely" 10,000,000 times more
         | important.
         | 
         | * New graduates and entrants into software development
         | _massively_ over-prioritize optimization and performance,
         | largely because the techniques to accomplish it are a lot more
         | generic (and therefore teachable, and therefore over-
         | represented in their classes) than the techniques to get
         | correct behavior, which is highly specific to every domain.
         | O(n^2) vs. O(log(n)) matters, but as long as you 're hitting
         | the correct complexity classes, performance is just not a big
         | deal -- almost always, almost nobody cares. The (1 - almost)^2
         | times it does matter, you often drop down a safety level and
         | work around the type system.
         | 
         | * Static type systems have massively more power to help ensure
         | correctness (by proving certain classes of errors aren't
         | happening) than to improve optimization. You can mathematically
         | prove that your type system prevents X, Y, and Z, and it's up
         | to the programmer to decide if that's worth the restrictions
         | your type system imposes. But optimization? That's all done
         | through thousands of unrelated heuristics that may or may not
         | help, increase the "quirkiness" of your compiled code, are a
         | big source of compiler bugs, and depend on how the programmer
         | writes the code. It is theoretically impossible (halting
         | problem) to prove that any given heuristic is actually helping.
         | Note that you can have a (valuable and loved) type-checker that
         | is not actually a compiler (e.g. TypeScript's).
         | 
         | As a rough, 90% correct soundbite: correctness is hard for
         | people but easy for compilers; performance is easy for people
         | but hard for compilers.
         | 
         | This is why the few people who are working on bleeding-edge
         | performance tasks are not using higher-level languages that are
         | pushing the boundaries of type theory. They are using C and
         | assembly.
        
         | marcosdumay wrote:
         | Well, if you want to go empirically, I guess type systems are
         | mostly driven by academic opportunities to study type systems.
         | 
         | But people are driven to them for several reasons. They are a
         | flexible tool that you can use to make your programs shorter,
         | easier to read, less repetitive, faster to run, less prone to
         | mistakes, easier to modularize, etc. (But, of course, not all
         | of those at the same time.)
        
       | valenterry wrote:
       | First:
       | 
       | > whenever you introduce a new linguistic abstraction to your
       | language, it may reside either on the statics level, on the
       | dynamics level, or on the both levels. In the first two cases,
       | where the abstraction is located only on one particular level,
       | you introduce inconsistency to your language; in the latter case,
       | you inevitably introduce the feature biformity (...)
       | 
       | Then:
       | 
       | > 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. In a dependently typed language Idris
       | (...)
       | 
       | Great article overall!
        
       | paulddraper 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...Combining
       | statics and dynamics in a single working solution is also
       | complicated since you cannot invoke dynamics in a static context.
       | In terms of function colours, dynamics is coloured red, whereas
       | statics is blue.
       | 
       | Yeah, but unless you have some fancy totality checker, that's
       | just the way it is.
       | 
       | > Idris: The way out?
       | 
       | Yeah okay, like that.
       | 
       | > A hypothetical solution should take the best from the both
       | worlds. Programming languages ought to be rethought.
       | 
       | ---
       | 
       | You can have static checking, "uniformity", or simplicity.
       | 
       | Choose two.
        
       | voidhorse wrote:
       | Interesting article. I agree that this double language phenomenon
       | of "biformity" can be a source of complexity, but I actually
       | think the majority of complexity comes from one level higher: the
       | paradigm, as the paradigmatic level is ultimately where
       | assumptions about the modeling of problem spaces lie.
       | 
       | The go example in the article is actually an instance of this:
       | kubernetes went ahead and implemented an oop system in golang--
       | why? because they felt go's assumption about the problem space
       | (that it can be modeled and solved as imperative programs) was
       | not a good fit for their actual problem space.
       | 
       | Haskell's assumption of purity leads to a problem/solution space
       | that's a good fit for problems that are primarily themselves pure
       | and mathematical, but leads to complexity when it comes to having
       | to solve problems that are not in this space (having to use
       | monads or effects for io)
       | 
       | Java's problem/solution space assumes you can model everything as
       | objects and classes and runs into complexity when we attempt to
       | use it for problems that are actually better modeled by other
       | means.
       | 
       | Many languages that are "multiparadigm" or "general purpose"
       | really have an underlying problem/solution model driving the
       | organization of programs. When our particular problem is not a
       | good fit for this model, we have to contort and wind up spending
       | more time dealing with language constructs themselves than
       | actually expressing our problem and solution. Couple this with
       | the fact that languages have different performance properties,
       | which may also be a constraint you need to satisfy and things
       | get...complicated. A lazy pure language might be the best
       | modeling system for your problem (e.g. dealing with infinite
       | sequences) but a non-starter due to memory constraints (not
       | enough resources).
        
         | klodolph wrote:
         | > having to use monads or effects for io
         | 
         | Speaking as a Haskell programmer, this is not a problem. You
         | put "IO" in the function signature and "do" in the body, and
         | that's workable. Or some other Monad. You get so many choices,
         | which is its own problem, but "having to use monads" is not a
         | problem in practice.
         | 
         | There are other problems that make Haskell hard to work with.
         | This just happens to not be it.
        
           | voidhorse wrote:
           | Sure, it was just an off the cuff example. And while I agree
           | that monads are not horrible by any means I think you are
           | oversimplifying a bit. Monad transformers, the mtl library,
           | ect. all exist after all... Even if using monads is
           | relatively painless I wouldn't say that it's easier than
           | using a language that allows you to freely execute side
           | effects wherever for programs that are highly interactive.
        
           | staunton wrote:
           | > There are other problems that make Haskell hard to work
           | with.
           | 
           | What are the biggest or most important ones in your opinion?
        
             | DonaldPShimoda wrote:
             | I'm not who you asked but I think the clearest example is
             | lazy evaluation. Anecdotally speaking as someone who's
             | helped teach a few classes in Haskell, students often have
             | problems with laziness until they really adapt to the
             | functional paradigm, and even then continue to have some
             | problems.
             | 
             | The other common problem I see is not actually a Haskell
             | problem but merely a problem Haskell exacerbates: many
             | students fail to think through their types prior to
             | starting an implementation. I frequently had students in
             | office hours trying to force their way through a problem
             | and when questioned about the types were unable to reason
             | at the type level intuitively. I suspect this reflects
             | larger problems in my university's undergraduate program,
             | but I figured I'd comment on it anyway.
        
         | JohnFen wrote:
         | Very well said!
         | 
         | This is why I think devs should know multiple languages, and at
         | least one language from each major paradigm. You don't want a
         | toolbox with only one kind of tool in it.
        
         | smarterclayton wrote:
         | > kubernetes went ahead and implemented an oop system in golang
         | 
         | I don't think this was ever an objective, can you clarify what
         | you mean by "oop system" and where we implemented it?
         | 
         | We aggressively used composition of interfaces up to a point in
         | the "go way", but certainly in terms of serialization modeling
         | (which I am heavily accountable for early decisions) we also
         | leveraged structs as behavior-less data. Everything else was
         | more "whatever works".
         | 
         | > why? because they felt go's assumption about the problem
         | space (that it can be modeled and solved as imperative
         | programs)
         | 
         | You'd have to articulate which subsystems you feel support this
         | statement - certainly a diverse set of individuals were able to
         | quickly and rapidly adapt their existing mental models to Go
         | and ship a mostly successful 1.0 MVP in about 12 months, which
         | to me is much more of the essential principle of Go:
         | 
         | pragmatism and large team collaboration
        
           | gammadist wrote:
           | Presumably, the poster is referencing the patterns described
           | in this talk: https://archive.fosdem.org/2019/schedule/event/
           | kubernetesclu...
           | 
           | > Unknown to most, Kubernetes was originally written in Java.
           | If you have ever looked at the source code, or vendored a
           | library you probably have already noticed a fair amount of
           | factory patterns and singletons littered throughout the code
           | base. Furthermore Kubernetes is built around various
           | primitives referred to in the code base as "Objects". In the
           | Go programming language we explicitly did not ever build in a
           | concept of an "Object". We look at examples of this code, and
           | explore what the code is truly attempting to do. We then take
           | it a step further by offering idiomatic Go examples that we
           | could refactor the pseudo object oriented Go code to.
        
             | smarterclayton wrote:
             | I have a lot of respect for Kris but in this context, as
             | the person who approved the PR adding the "Object"
             | interface to the code base (and participated in most of the
             | subsequent discussions about how to expand it), it was not
             | done because we felt Go lacked a fundamental construct or
             | forces an imperative style. We simply chose a generic name
             | because at the time "not using interface{}" seemed to be
             | idiomatic go.
             | 
             | The only real abstraction we need from Go is zero-cost
             | serialization frameworks, which I think _is_ an example of
             | where having that would compromise Go's core principles.
        
           | voidhorse wrote:
           | I have nothing against go or kubernetes, I was simply citing
           | the linked article in the thread, which at one point states:
           | 
           | > Golang] Kubernetes, one of the largest codebases in Golang,
           | has its own object-oriented type system implemented in the
           | runtime package.
           | 
           | I would agree that Golang is overall a great language well-
           | suited for solving a large class of problems. The same is
           | true for the other languages I cited. This isn't about
           | immutable properties of languages so much as it is about
           | languages and problems fitting or not fitting well together.
        
             | smarterclayton wrote:
             | That's fair - as the perpetrator of much of that
             | abstraction I believe that highlighting the type system
             | aspect of this code obscures the real problem we were
             | solving for, which Go is spectacularly good at: letting you
             | get within inches of C performance with reasonable effort
             | for serialization and mapping versions of structs.
             | 
             | I find it amusing that the runtime registry code is cited
             | as an example for anything other than it's possible to
             | overcomplicate a problem in any language. We thought we'd
             | need more flexibility than we needed in practice, because
             | humans involved (myself included) couldn't anticipate the
             | future.
        
               | throwawaymaths wrote:
               | But do you really need "near c" performance for a
               | container orchestrator? I would think your bottleneck
               | will be talking to etcd
        
       | rsrsrs86 wrote:
       | Author writes about types being applied for ergonomy, which is
       | interesting. Types add static information that can be used by
       | IDEs. But there are other reasons for using type systems:
       | correctness and performance (unboxing, optimization)
        
       | DarkNova6 wrote:
       | Dynamic languages have the same type of complexity, but you are
       | not aware of it because it is well hidden.
       | 
       | That's a big no-no for building applications.
        
         | riku_iki wrote:
         | > Dynamic languages have the same type of complexity, but you
         | are not aware of it because it is well hidden
         | 
         | I would say you can ignore it until it fails in production.
        
       | marcosdumay wrote:
       | The lack of union between validating and executing things has
       | been bothering me relatively often, on several different
       | contexts, for many years already.
       | 
       | Want to write some CRUD? The relationship between the code and
       | the database is left hanging, and as a result not only the system
       | is prone to failure, but you also have to repeat yourself all the
       | time.
       | 
       | Want to write a simulation library? The language won't help you
       | checking the simulation data at all, you have to do that
       | yourself.
       | 
       | Want to describe some system for DevOps? There's a reason people
       | use shitty custom languages for it instead of just reusing a dev
       | one. Part of that reason is that you can check things compiling
       | the custom language, but that implies you write the entire
       | compiler.
       | 
       | And the list goes on. Somehow it always eluded me that the entire
       | definition of a dependent type system is one that solves this
       | kind of problem. I guess I'm learning Idris soon.
        
       | nabla9 wrote:
       | Peter Norvig demonstrated it well using design patterns as an
       | example.
       | 
       | Design Patterns in Dynamic Languages http://norvig.com/design-
       | patterns/design-patterns.pdf
        
       ___________________________________________________________________
       (page generated 2023-08-09 23:01 UTC)