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