This is why I enjoy Go, it shepherds you to write clear and uniform code that cannot be mistaken. This is 'boring,' true, but I will die on the hill that engineering should be boring if that's what produces a robust outcome. Elegant functional trickery can be cool in a personal project, but I don't want to spend 2 hours deciphering someone else's elegance when trying to solve a business problem.
Let’s play devils advocate; does it return a filtered list, does it change itself or both?
As clear as it may look at first, I still would have to look in the docs to find out.
You could make the same arguments when you're working with slices in Go. Take append(slice, element). Does it return a slice with the element appended to it, or does it modify the slice in-place?. For some things, you have to look at the docs, that's how it is. Usually languages have conventions around that. In JS, map, filter and reduce always return a fresh array and leave the initial array untouched.
> You could make the same arguments when you're working with slices in Go. Take append(slice, element). Does it return a slice with the element appended to it, or does it modify the slice in-place?
A funny trick question as it pretty much does both.
Also I'll take "functional trickery" over needing a page called Slice Tricks so you can find out how to delete an item any day of the week.
In some languages, especially many ahead-of-time compiled languages, this will be a part of the signature so that it’s at least much more difficult to get wrong¹. In Rust, for example:
• `fn filter(self, P²) -> A` consumes self and returns a new thing of type A;
• `fn filter(&mut self, P)` mutates in-place;
• `fn filter(&self, P) -> B<'_>` doesn’t mutate self, but produces a new thing of type B containing references to the contents of self.
As a concrete example, Rust has `Vec::retain(&mut self, P)` to filter a list/array in place, and `Iterator::filter(self, P) -> Filter<Self, P>` to filter an iterator, consuming that iterator object and wrapping it in another.
This is a sterling example of the point of the original article: Rust pushes you to thinking about ownership, and it’s really great and I miss it when working in other languages, where I have to stop and think whether I’m allowed to mutate this object or whether I should make a copy, and the cost of getting it wrong is bad performance in one direction and bugs that can be a nightmare to diagnose in the other direction.
—⁂—
¹ You need linear types to make it just about impossible to get wrong by accident. Rust has a weaker substitute that you can annotate functions with, #[must_use], which says “warn the user if they neglect to use the return value, because they’re almost certainly doing the wrong thing”.
² I’ve omitted the filter predicate P from the signatures to reduce distraction; in each case it’d probably be something like `<P: FnMut(&Self::Item) -> bool>`: a function that is passed a reference to an item and returns true or false.
Then what is? A filtering then later mapping with a bit of flatMap and a final reduce?
Because if it is not functional trickery, I don’t really know what is (and no, recursion is not the answer) and it is sure clearer than the 3 nested loop or whatever one might come up with.
People don't properly account for the cost of reading volumes of code. They'd rather see 10,000 lines that will take them a week to read than 10 lines that will take them an hour to read.
It's more about the context. Yes, this module could have 10,000 lines of boring code or 10 dense lines. But the overall system is 100,000 lines across 3 languages, 8 runtimes, and 2 databases, so the savings from that one module are minor in the big picture.
It's surprising to me that as a engineer you seem to be taking pride in not knowing things. I personally have a biais for functional programming in general. I think there is a lot to learn by studying the languages and pattern used. And I think the last 5-10 years have proved that I'm right, as more and more languages are adopting features that were used only by functional languages before. Records, ADTs, pattern matching. I think Go would have benefited from studying these languages.
But I also appreciate Go as a language. I love the wide standard library, I love how asynchronous IO is handled, I love the tooling and the fast compilation, I love the cross-compilation. And more than that, I recognize them as good solutions to problems. I wish other languages would adopt that. I wish making a web server/API in OCaml was as easy as in Go. I wish Rust had some M:N threading model when doing stuff that's a bit less performance-sensitive. I wish Stack/Cabal/Nix were as easy as go build. When I had to make a small program to cut text files for a friend, I did it in Go, and it was a great experience.
We're engineers, let's try to be a bit less tribalist and a bit more pragmatic, and we will all end up with better tools.
>It's surprising to me that as a engineer you seem to be taking pride in not knowing things.
I'm not the GP, but I think this is off the mark. There's a big difference in purposefully choosing boring technology and purposefully taking pride in not knowing things.
I think the message that I initially replied to hints more at the "taking pride in not knowing things", or at least can be interpreted that way easily. A different message, that, according to me, doesn't have this issue would be:
"This is why I enjoy Go, it shepherds you to write clear and uniform code that cannot be mistaken. Go makes well-known tradeoffs that have already been discussed to death, and I find that I agree with these tradeoffs. In my experience, collaborating on Go code is easier as the absence of some means of abstractions prevents people from creating their "language inside the language", and thus allow for a team to share more easily their mental model about how the code works."
You could even add a caveat in the form of "Go isn't perfect, no language is, and has made what could be considered objective flaws. But on the other hand, the handling of things like generics shows that the maintainers are not closed to idea. They tend to err on the side of taking more time to make things rights, even if that time isn't always needed, and that's a tradeoff I agree with.".
My sentiment is moreso that an engineer should be trying to create robust, maintainable software and my experience has guided me towards believing that simplicity is the easiest route to this, which Go solves in my opinion.
I agree with you about robust, maintanable software and simplicity. I also think that Go solves those problems pretty well. My point is that there is no need to put down other things, since simplicity may vary from person to person, teams to teams and problems to problems.
This can derail the convo a bit, but one of the major criticism of React as a view layer library is that it shepherds you into doing a whole lot, much like a framework.
This resonates - I've thought about certain technique and language couplings as "impedance mismatches", where the technique itself is fine and good, but the language makes it a horrible experience that's not worth it.
The example that I've run into most is trying to write functional immutable code in Java. I have just never gotten it to work well - of course there are libraries that have weird workarounds (that aren't compatible with standard data types or libraries), or tons of boilerplate you could write, but ultimately Java just fights you the whole way. It's not really worth going down that path imo, and better to switch to something like kotlin or scala if you can.
If you have to interact with classes that do some reflection-based magic and you need the getter setter convention, yeah it is hard. But with records I really don’t feel that it would be bad. It didn’t become a haskell overnight, but one can write FP in Java reasonably well.
@Value and @Builder from Lombok get you halfway to Scala case classes. Just because Java requires a bunch of noise doesn’t mean your staff has to reread it.
Having arguments against Java deflected with "oh but annotation processors" is so annoying. Just because you can somewhat patch the holes doesn't make it a weakness of the language. Same arguments are used for C ("just use static analysis") or untyped languages ("just add a bunch of unit tests")
In my current project we use Error Prone, Checkstyle, Immutables, JSR305, NullAway and a couple of other things. It makes the language tolerable, but it's a massive waste that each setup needs to introduce and tune those, and often inexperience or lack of knowledge will mean they're never introduced, if only because after a while they become painful to introduce.
Better languages exists, some are even JVM, which just do this stuff properly.
In the C -> C++ -> Java -> Scala progression Java was a gigantic golang-style detour from a multi paradigm language. STL had so much power and a lot of it can be attributed to quasi-FP abstractions. The first few posts in Bartosz Milewski's blog showed the same examples in both C++ and Haskell.
What surprises me is that even among those lucky to work on a powerful platform such as the JVM or .NET it's more typical to stick to a single language. In my mind it'd be more natural to write Java/Kotlin/C# when you want imperative abstractions and Scala/F# when you are in mood for FP. The same platform/libraries, the same IDE/toolchain, only the syntax/abstractions differ.
But we know that Scala and F# adoption is nowhere near what some of us would hope for. We also know that the ecosystem fragments immediately and Scala projects don't share [m]any libraries with Java ones for example. So mixing the two languages in one system is mostly about wrapping some legacy code.
The "worse is better" essay touched upon some deep truth.
Boilermaker language. As in: you make a bunch of boilerplate. And also you get your beer but you’re liable to get a shot in the foot with it. (This is all off the top of my head)
Maybe ad-hoc, as in "not planned ahead well at the time of designing and thus needing lots of fixes for edge cases" or perhaps inelegant as in "requiring many concepts as opposed to something like Smalltalk, which fits on a post card".
What about endless typing attributes that the language should infer for itself and instead forces everyone to write templates to work around such matters?
It would be interesting if you could have a hybrid somewhere between Pascal and Python, where you could start just naming variables, and the compiler could fill the types in once the code was complete, then you could iterate and tweak.
You'd end up with fast greenfield code to get your prototype working, but then could tighten it up to run faster, and clear out bad type assumptions as the discipline level goes up.
Not quite what you mean, but still, you might find interesting the talks on how to approach coding in languages like Idris, where the compiler is able to support the editor in type suggestions.
To expand a bit on the sibling post, this is basically OCaml's niche. The type inference is great, so you usually don't have to write any types (not even in functions signatures like Rust). The language has support for functional, modular, imperative and object-oriented programming. Usually you start with functional code, that's plenty fast (especially compared to Python) since it compiles to binaries, but still optimized for readability. You also have a fast bytecode compiler for a faster development loop. When needed, you can switch over to imperative code and mutable variable for performance, OO for GUI work for example, and modules when defining interfaces between parts of your program.
You can also do the opposite of what you suggest: start by writing the interface file (which has the functions and types that you will expose), and then write the implementation the way you want. This allows you to cleanly separates the API from the implementation, and also gives you fast compilation times.
There may be a slight difference compared to what you suggest, as everything is statically typed, but you can start a program with permissive types and gradually move on to stricted typed. For example, replace a string that can be anything with an enum (called variant types in OCaml).
If you don't need the native compilation/want the C# ecosystem, F# is a close cousin. Scala is a different beast, but shares some origins. It relies more on objects.
I didn't encounter the error myself for some time, but I don't know if it's because the language improved or I myself improved, so I can't give you a straight answer.
It's a shame OCaml never became more popular. The language itself seems pretty fine (in my limited experience), but with popularity there would be more libraries available. And maybe if multicore OCaml would have been a thing a decade ago..
Multicore wasn't a real blocker, JS is single threaded too and everyone is fine with it. OCaml runs circles around Python and Ruby, yet they are more used.
Explicit typing hurts the signal:noise of the code. I’d like to have invariants and pre/post-conditions, inferred or verified where possible, visually separated from the expressions that do the important work (comments to the right, a block below the body, whatever).
EDIT: i assume here you mean explicitly typing out type names, like in Pascal's "var name: type" or C's "type name" and not something else.
I am not a fan of this because the type attributes also act as documentation for that the variables, parameters, etc are supposed to be and that clearly typed out text is much easier and faster to read than some IDE's tooltip at a mouse hover or whatever (assuming you even have that in the context you see the code - even if your primary IDE supports this, you may still need to read the code outside of the IDE and sometimes outside of the surrounding code like, e.g. reading a diff).
And yes, for that reason C++'s "auto" is one of my least favorite additions to the language. Even when i have to use the language, i avoid using "auto".
(if anything sometimes feel that readability benefits when having to be explicit not only about types but also the parameter names in function calls ala Smalltalk... e.g. in Pascal i sometimes wonder what that "True" or "1" really means in a call. In recent versions of Free Pascal that have added scoped enum support - like class enums in C++ - i try to use those instead of booleans as they document the API even when it is used but this is only a small case)
> That was actually a kind of jab how D nowadays has a jungggle of attributes, and the workaround taken by most on the community.
Ah yeah, this is why i added that EDIT but you replied before i did :-P. I agree about that part, i remember D being a bit attribute trigger happy :-P but i guess the alternative would be to break backwards compatibility which is IMO worse. Though perhaps something similar to Free Pascal's "modeswitch" directive would help (FP devs use it to introduce new syntax without breaking backwards compatibility).
> Delphi has type inference via inline variables
I know it is there but i don't really like it myself for the reasons i mentioned above (also i prefer sticking all variables together even in C++, too much code is usually a sign i need to break the functions into subfunctions and Pascal allowing nested functions is a feature i use very often). It is also one of the very very few case where Free Pascal developers have explicitly decided to not implement something because they feel it doesn't fit the language :-P.
But, not everybody likes the eye strain of overly symbolic almost hieroglyphics and unreadable chicken scratch code. A lot is to be said for readability and maintainability.
And any repetitive words can be handled by IDEs, editors, and macros.
> And any repetitive words can be handled by IDEs, editors, and macros.
An IDE or editor may be able to write code for you but it can't read or understand it for you, and that's what takes most of the time on nontrivial projects. Macros undermine your tooling and mean the reader has to learn your dialect before understanding your code - they work best when you have a few common ones that are used across every codebase and supported in your tools, but at that point you're better off having them included in the language itself.
I always wonder how developers work where it makes a difference if I type } or END. I appreciate with regards to readability it comes a lot down to what you are used to, but I never understood the argument that you safe something by typing a bit less.
From my experience the design planning and just thinking about programs takes so much more time than the typing in that I sometimes wonder if I do something wrong.
What is the advantage of a less verbose language if let’s say the language capabilities are similar?
I also never liked BEGIN and END and other Pascal keywords BEING in ALL CAPS. It JUST made it ANNOYING to read THE code, like BEGIN and END were the MOST IMPORTANT things in THE code.
FWIW in Lazarus i rarely type begin or end, i type b and press ctrl+j with the IDE adding "begin", "end" and placing the cursor right between them at the proper indentation.
Similarly i rarely update the var section manually, i just press F9 to compile, the IDE moves the cursor right at the missing variable (the compiler is fast enough to make this almost instant), i press Ctrl+Shift+C and the IDE adds the variable. Same for the interface and implementation sections (think header and source files in C but at the same source code): i write the declaration for the classes, procedures functions, etc, press Ctrl+Shift+C and the IDE creates the bodies for their definitions automatically. Using Ctrl+Shift+Up/Down arrow i can move quickly between the declaration (in interface section) and definition (in implementation section) for each entry, allowing for quick filling in the code.
There are other shortcut keys, etc in Lazarus and i never really feel like having to type boilerplate code myself - but i still get the benefit of the more readable Pascal code (i have decades of active C and Pascal experience and i always find Pascal to be the more readable of the two, especially when looking at other people's code).
PHP has to be the worst offender with its pseudo-Java boilerplate enshrined in its PSRs. PHP is a perfectly fine procedural language but version 5 was like the Second Coming of Christ with its religious devotion to all things OOP. I don't know about shepherding - with PHP it's more like being beaten over the head with a club.
PHP PSRs are practices, not language features or restrictions.
Most PHP developers ignore or don't know about them, which is evident from interviewing PHP developers.
It is either OOP or FP. You will not make everyone happy. Procedural programming unfortunately does not scale for the needs of the PSR. Remember, the group started as a platform standardization (between the laravel level projects) and not PHP in general.
That why tending to really old PHP code bases is the comfiest job ever.
Everyone understands that the code is horrible and you are regarded as a hero for diving in but honestly most of it is just straightforward procedural code and you can be productive in minutes.
Now those projects that went all crazy on OO? Yeah, I would rather burn it all to the ground.
Though I would say, all in all the PSRs did also lots of good for PHP. It makes navigating modern projects quite a bit easier.
>That why tending to really old PHP code bases is the comfiest job ever.
Everyone understands that the code is horrible and you are regarded as a hero for diving in but honestly most of it is just straightforward procedural code and you can be productive in minutes. Now those projects that went all crazy on OO? Yeah, I would rather burn it all to the ground.
The comment could have just as easily been:
I've worked on legacy PHP code. Most of it is just straightforward OOP code.
Now those projects that went all crazy on procedural code? Yeah, I would rather burn it all to the ground.
I know, because that's my experience. The issue is not particularly OOP vs procedural, but rather how crazy it is.
However in the PHP world, I would question those wanting to write in a procedural style, as all the mature libraries and testing tools are built with OOP in mind.
There was no OO before PHP 5 so you are missing the point.
Obviously you wouldn't write modern software like this but maintaining those old systems is a hell lot easier then those where people went nuts on OO and inheritance was used more lightly. Bad abstraction are worse that lack of abstractions, basically.
Either work on a modern project that has been build in recent years or if you have to maintain things, better go with something really, really old. Those in-between projects that don't seem to be totally hopeless but are still rotten, yeah that is there most pain lies.
Sure it depends depends on the specific project, everything can be abused but that is a totally different discussion.
Testing? You think the people with such horrible legacy code would pay me to write tests? I am happy if they don't want me to edit it directly on the live servers.
But yeah good point with the testing. I should have specified that I am talking about agency work, not actually caring about the software you keep alive.
It was a great move for PHP to take inspiration from Java.
It gave people a recognisable syntax and structure, to run things on PHP's shared-nothing architecture, which is arguably its real strength.
Similarly it was a great move for Symfony to take inspiration from Spring. All the older PHP projects like Drupal have slowly been ported over to using it.
I have never met anyone who seriously wants to go back to PHP 4.
PHP is not perfectly fine as a procedural language. There are several important features missing, like
Function autoloading, without it you need to come up with a reliable way to require dependencies. This usually differs from project to project in a non-reliable way and will be messy if you don’t think this thru. It will also affect things like testing.
Only global state or (closed) function state. Namespaces are fake in PHP, it only adds the namespace name to the class name or function name, you can’t store anything inside a namespace like in C++. Thus everything is global, which is horrible and bug prone.
You can use static inside a function to keep state as an alternative, but that is hard to manipulate from the outside when needed. It can be done with a separate function that keeps track on other functions state. When I tested modern pure procedural that is what I did.
No typedef for function signatures. If you want to create a contract enforced by the compiler you use interfaces in OOP, in procedural you want to define function signatures instead, but there is no reliable way to do that in PHP without classes (ignoring any external tooling that uses phpdoc)
Testing. Have you loaded a function into the runtime you can’t unload it again, thus mocking a function you already tested becomes impossible. You can solve this by running every test unit in a separate PHP process, but that is expensive.
These are the biggest and none of them will probably never be fixed, perhaps function typedefs has the biggest chance but I doubt it.
If you are writing a large project (+500k lines) all of these are very important. That is why OOP PHP rules because it can handle all of this.
The problem with OOP is that many of the legacy OOP projects inherited the inheritance based OOP from Java, however today we know that composition based OOP is better (both in Java and PHP).
I've become worried about Rust as "async" worms its way into more crates. "Async" is optimized for I/O bound programs. The special case of a large number of slow network connections talking to a server, web services, is what it's really used for. If you have multiple compute-bound tasks crunching away, it's a mismatch.
Supporting async isn’t a bad thing. It is annoying when working with async stuff and suddenly a library doesn’t support futures or streams.
But I do agree that it seems that people jump to use async instead of e.g. threads. I think there should be more literature around what situations work best for threads vs async, etc.
I think in a lot of cases the async programming model is easier to work with than the thread programming model. In a sense it’s declarative vs procedural programming over again. With threads you have to identify parallelism opportunities at write time, and if you plan poorly you can end up with low core efficiency. With async programming you break your execution up into a graph of inputs and outputs and let your async scheduler dynamically bin-pack everything, paying an overhead for consistently high core efficiency without having to think about it or change your code very much.
That's a very intriguing metaphor.. and reminds me of the optimization manually vs using `gcc -O2` .. I am wondering if the "pre-mature optimization is the root of evil" quote applies here and we should all just use async first and only go for threaded/multi-processing programming if there seems to be a performance issue for the business application.
It’s probably very painful to try to convert a piece of async software to use a threaded model (though I’m just guessing, having never had to attempt this), so I’d say it’s more a matter of thinking up front about whether you need to control threads directly. But to your main point, I agree. I suspect the vast majority of software has no need for thread-model parallelism, and languages have only historically presented that parallelism model because it’s the model that the OS exposes to userland, even though the OS scheduler is async-style scheduling the machine threads themselves!
You might not even need to do that, most async runtimes support some kind of middle ground where you take explicit control over which tasks are scheduled to which threads.
It's also useful as documentation even in single-tasked programs, since async ends up as a "good-enough" IO monad.
Then again, few programs are actually single-tasked in practice, and libraries often end up taking advantage of those async primitives for doing stuff like connection pooling even if your application code doesn't see it.
I always thought this, but people don't seem to find the convenience of async as a strong selling point over threads. There isn't a fundamental reason, but doing something like:
is almost always more difficult to do with threads. Sure, some threading APIs support joining nicely but it tends to be more awkward. Especially if you want to do something like handle the first request to finish first.
Also the startup cost of threads affects how you write code. Does your library spawn a new thread for every HTTP request? Probably not, it is expensive and the server may be on the same host. So you end up with less consistent async support. Since async programming is a lot cheaper you tend to see better support which adds up.
This is quite impossible for a language in Rust’s space; it would violate Rust’s principle of only paying for what you use, because there is necessarily runtime overhead in providing such an abstraction.
Threads and async have different restrictions and costs. No shared abstraction is possible for a language like Rust without losing what makes it Rust.
Minimal-cost threading support requires taking Send + Sync into account, essentially thread-safety: and the thread-safe primitives are more expensive than the thread-local primitives. Send + Sync matters become pervasive through the design of threaded code, even if they don’t need to be spelled out most of the time.
Minimal-cost async requires compiling the code into a state machine, which means you’ve got to restrict borrowing across suspend points, or mess around with Pin.
Threading and async are quite different in a language like Rust. They can even play quite nicely together. But you really can’t unify them.
Sure, you could do simple cases like single-threaded blocking-or-async, e.g. `async? fn foo() { bar().await; }` compiling to something like strawman `fn sync#foo() { sync#bar(); } async fn async#foo() { async#bar().await; }`, but this falls apart completely as soon as you want to do anything beyond immediate .await.
> it would violate Rust’s principle of only paying for what you use
I guess I'm glad that Rust developers don't design CPUs, as we wouldn't have memory abstractions like cache coherent NUMA (which doesn't come for free).
Who said anything about "free"? chrismorgan wrote "only paying for what you use". In NUMA systems, pretty much every program uses and benefits from cache coherency.
Well, that depends on the details of the CPU architecture in question. Without knowing those details, I'd say probably yes, but userspace programs will need to ask the kernel to change the memory attributes of the mapped area they want to access non-coherently.
Case in point: a CPU plus a discrete GPU is technically a NUMA system, and low-level APIs like Vulkan explicitly expose the ability to allocate non-coherent memory. I strongly assume that accessing that memory doesn't incur coherency overhead.
But not at the language level, which is what started this thread.
As another example: compare "GC'd versus non-GC'd memory in Rust" to "coherent versus non-coherent memory in CPU architecture"; here Rust gives us just the bare machine, while CPU architects give us quite some abstraction plus a choice.
It is true that you could theoretically write a GC in Rust, but using it from the language itself would probably break ergonomics so much that you'd be better off with another language.
The only way to run code in another Thread is by spawning an `Isolate`. But once you have one, you can expose an API that is indistinguishable from an usual async one.
Dart in general has made many sensible choices, it's a nice language to work with. I recently decided to give it a try and I had literally zero friction coming from Java background. I'm rewriting some of my tools with Dart now instead of current mishmash of scripts.
I also come from a Java background and I really like that Dart can run as a script (like JavaScript) but it can also compile to a native executable (and unlike GraalVM, it compiles in a couple of seconds, not minutes, and pretty much any Dart code will work - no issues like Java has with reflection, for example).
Try `dart compile exe app.dart` and then run `./app.exe`... it's really fast and the binary is fairly small!
Yes I recently started programming with Dart and I find it incredible how little buzz there is around it. I’m using it for writing small scripts right now and it’s been so refreshing to not run into one road block.
The installation process is simple, documentation is excellent, the core library makes sense, the defacto package repository and package management is clean. And WRT this thread, the async model is dead simple to pick up.
As a scripting language it seems to be a perfect fit. But there is also a lot of first rate tooling available for Dart and I am starting to wonder if it wouldn’t be a good choice for larger projects as well.
It is, at least for me, not at all clear that that's possible to do in a good way within the expressive power of any mainstream language. And "good" in this case of Rust would include "without runtime overhead that could be avoided". Is it possible?
Once the compiler has computed the async graph, it could in theory fuse subgraphs of nodes (especially linear sequences) back together and re-express the execution flow in terms of spawning and joining threads. This would be simple and could even be a performance win for some graphs (i.e. embarrassingly parallel ones), and would be a complete trainwreck for many, probably most. I certainly wouldn’t want to be stuck reading the error messages produced by a compiler when it failed to find a satisfactory solution to a complex execution graph. Profile guided optimization might help I suppose, but still.
You can run an async func synchronously, just push it onto an executor. Threads are basically orthogonal - you can do that in the same thread or on a dedicated thread.
You can't really in rust, because it doesn't let you abstract over async-vs-not. If your function does something that a caller might want to happen async, it has to make a choice about whether to do it in an async way and return future (cumbersome and inefficient to use from blocking thread-based applications), or do it in a blocking way (unsuitable for use from async applications).
Function is async and you want to run it synchronously? Create an async runtime and use its equivalent of `runtime.block_on(async_fn())` to drive the future to completion synchronously.
Function is blocking and you want to use it in an async context without blocking the async worker thread? Use your runtime's equivalent of `spawn_blocking(|| sync_fn()).await` that will run the function call in a threadpool dedicated for blocking tasks.
Right, and in both cases you end up being inefficient and using more threads than you wanted to (and in the second case, potentially undermine the whole point of using async at all).
>in both cases you end up being inefficient and using more threads than you wanted to
There are no extra threads in the former case.
>(and in the second case, potentially undermine the whole point of using async at all)
If code is blocking for long periods of time, it shouldn't be running on the async worker thread in the first place. Marking such a function `async` would be wrong. If I write a tight loop that takes seconds to do its job, putting an `async` on the function is worse than not having it. Spawning such work in a different thread is correct. There's no "whole point of using async" that is being "undermined".
> If code is blocking for long periods of time, it shouldn't be running on the async worker thread in the first place. Marking such a function `async` would be wrong.
Right, you need to go right down and e.g. use a non-blocking I/O primitive instead of a blocking I/O primitive. Rust gives you no way to polymorphically do one or the other.
Depends on the compute tasks. Even OpenMP has "dynamic" scheduling pragma for loops, in the case each loop iteration takes different time to complete. "async" is a way to address that, sorta.
Yeah, it may just be me, but I was looking into WebDriver crates yesterday to try and control browser engines, and there are non-async ones, but async ones seem more common (at least in terms of blog posts talking about them).
The examples they give to use the API using async, I really don't understand, and I can only assume I'm missing something: the example steps of going to a URL, finding a resultant element, entering text, then clicking the search button, then finding another element and then capturing a .png snapshot image for the result are in my mind all sequential, and depend on the previous step: I really struggle to understand how async helps in this case (at least the basic one), as I don't see how any of those steps can be parallelised.
The fact that they're async doesn't mean you're expected to be able to parallelize the calls, just that you can do something else while waiting for a response if you want. They involve IPC to another process which can take its own arbitrary amount of time to return a response. There's no reason to block your program during that period.
I don't know anything about webdriver's API specifically, but as a general example, "do something else while waiting for a response" can include "stop waiting for a response after a timeout expires". Maybe you want to set a timeout on the "fetch a URL" function call. You can't do that with a synchronous function unless the function internally supports a timeout. With an asynchronous function you can just race it with a timeout future, and if the timeout future completes first you can stop waiting for the fetch future and fail your test.
Classic WebDriver, when used to control a single browser is indeed a synchronous API. So for example the server-side WebDriver implementation used in geckodriver is fully synchonous; it accepts a HTTP request, and does a bunch of blocking work to execute that command in Firefox. But there are a couple of reasons you might want an async client, even though the browser side is synchronous:
* You might want to drive multiple browsers at the same time. For example when running multiple tests in parallel you would have multiple concurrent requests going to different browser instances. Obviously you could implement that as multiple threads/processes/etc. but if async has the ergonomics you're looking for it is certainly a viable option.
* Modern extensions to WebDriver don't follow the command/response model. Many clients have additional functionality that's not implemented on top of WebDriver, but using browsers' built in debugging protocol. Even Selenium these days uses the Chrome Debug Protocol for some features (e.g. access to logs). Those protocols are event based and so not suitable for a fully blocking API. Of course one could still design something that doesn't require async for those use cases, but I think it's generally believed that async offers the best APIs in languages that support it.
What I'm doing looks like this.[1] That's frantically loading and unloading textures at different resolutions as the camera moves to stay within GPU memory limits, but you don't see that happening. (It's running at about 60FPS, but the video capture program, Kazaam, doesn't capture in sync with the graphics card, which is why there's some tearing in the videos. Switching to OBS for capture.)
A rendering library I use added an 'async' framework, but it turns out using it is not going to be mandatory. Much relieved.
Also works better than callbacks for single threaded/event loop scenarios like JS or some GUI framework (with the caveat that you need to make sure you are resuming on the event loop if environment is multithreaded).
Not necessarily, if you want to do non-blocking compute on client you would ideally spin off a worker thread but if not available you can periodically yield from your compute task to the event loop to avoid blocking.
Async is pushing Rust where it shouldn't really be going anyway: to be a Node.js replacement. The actor model is far better suited to solve these kind of problems to begin with.
I get that feeling, too. My general comment is that web back-end stuff should be written in Go. Go is designed for exactly that. Goroutines are lightweight but preemptable, so you don't have the async vs. thread problem at all. There's only one way to do it. Most of the Go libraries you need for web back-end are well-exercised, because they were written at Google and are used internally.
Writing in Rust takes more engineering effort and is less agile. You can code yourself into a corner which requires considerable rewriting, or unsafe code, to escape.
What I'm writing in Rust is a metaverse client. These are not common yet. They have both the real-time problems of a game client and the unexpected content-driven loads of a web browser. The goal is to keep the frame rate constant as a stream of update transactions comes in, perhaps hundreds per second. The transactions modify the scene graph and change what's on screen. So refresh is in one high-priority thread, while updating is in other low priority threads. Overload bursts happen routinely and have to be dealt with, updating near and large things first. In MMOs, such overloads are usually detected and prevented during game design, but a metaverse viewer does not have that luxury. Existing clients tend to choke and stall on overload, but with enough CPUs working on the problem, that can be overcome.
It's a fun problem. It doesn't have the shape of most common applications.
Yes, I can see that as a good match for Rust. When you started to describe it I thought 'Erlang' but as the description progressed it clearly is a bad match for Erlang due to the performance requirements.
But typical web stuff: Go or even Java would seem to be a much better choice than Rust, for the same reasons I would not write a typical web service in C or C++.
async, when looked at from the "writing code" perspective is horrible.
it pollutes every function with async annotation.
it allows you to quickly write the code imperatively by just annotating a function and using the async function inside it.
it results in code that is massively polluted with async when at most places you never had to use it at all (if you just restructured the code).
having async be as verbose as possible would enforce people to not use it as much and to structure their code to have async bits nicely separated from non-async bits.
I guess it depends on what OP is talking about. Async code can be written with callbacks (callback hell incoming), or with Futures/Promises, or with syntax sugar like async (do notation).
Whatever the reason is for using async, I believe that the mismatch happens not because Async is optimized for I/O bound programs but because it's insanely easy to use (in Rust, Javascript).
I will have to say that I like the direction that the V programming language (vlang) is going. It has blended many good qualities of Oberon/Pascal, C, and Go. I've read a lot of debates among C and Pascal advocates, so good to see newer languages trying to find middle ground.
Debatably, the Go language has put itself somewhere in the middle, as a more modern acceptable compromise and blend with its own twists.
The V language is continuing on the path of where Go went, where readability, maintainability, and simplicity is more evident. Doing so, where it feels like a more natural feature. At the end of the day, many want to be more focused on the problems to be solved, not tripping over the eccentricities and traps of the language.
Clearly, perfection will probably never be achieved. But, I do like where various newer languages are going, as it shows progress. Usability, being easy to learn and teach, as well as being more practical about common programming issues.
Agreed. I'm sick of people referencing that damn website from 2.5 years ago. It's not vaporware whatsoever... It has over 12k github commits FFS. It's an excellent language and has a great community on Discord of excellent people. Frankly the more I read on HN the more I think Zig, Nim, (possibly Rust) supporters here are rather toxic to the community.
Hey, I am not a supporter or hater of any language... if the claims made back then are no longer correct, just point it out by writing a blog post showing how much progress has been made and how the claims made on the website are now all accurate... you don't just get trust back automatically because you're working hard on the problem... you need to show how you've addressed the issues, how each claim you made on the website is now provably true, and why we should believe you to not be lying again when clearly, you lied before.
V is still vaporware at this point, most of the claims on the website are just not true. For example, the "coreutils" example is just filled with folders containing "delete.me" files, with no actual code.
Vaporware is about something that doesn't exist. V clearly does exist and has a number of developers and contributors working on it. I understand there was some controversy from 2019, and mostly prior to it being open sourced on GitHub. Then some lingering animosity that stayed around in 2019 and a bit of 2020. Well, that's a few years ago, and its 2022 now.
better to look at what's going on in the present.
Of any controversy these days, seems more about competing languages and their advocates, not liking another "new kid on the block" or wanting to protect their interests. But, such is the usual between programming languages. I'm talking more from the perspective of how various languages are designed for greater convenience, simplicity, and usability.
I will not download and play around with the language made by people that still keep fake claims on their websites. If V was more honest, I would myself give it an honest try. It isn't. Some other examples of false claims:
> V functions are pure by default, meaning that their return values are a function of their arguments only, and their evaluation has no side effects (besides I/O).
> This is achieved by a lack of global variables and all function arguments being immutable by default, even when references are passed.
But there is nothing preventing you to create side effects. A function making a side effect isn't pure. For example, Wikipedia [1] says that the return value depending only on input variables is necessary, but you also need no side effects.
The claim about Option and Result types [2] is also a bit weird. It feels closer to Zig's error handling than to regular Option and Result types. An important part of these types is that they can be replaced by user-made types without losing much, while in V they seem to rely a lot on compiler support.
In the comparaison with Go [3], there is a "No GC". On the main page, "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". "There's also an option to use GC for everything via v -gc boehm.".
This is just the beginning. I don't have any trust in the people behind V and will not rely on them for building software.
I think you are being overly dismissive of a language that you claim to haven't even played with. Go has been in existence since 2009, so has a 10 year developmental head start on V (mid 2019), and not to mention Go having a major player like Google in their back pocket. But, that can be seen as a bit of an disadvantage too, where Google can direct and influence the language to its purposes versus the majority.
V is in alpha, and has been making steady progress and changes over the last few years. To get an idea about this, refer to https://github.com/vlang/v/discussions/7610 or search their feature request history. The developers and contributors had and are clearly having a healthy exchange on what features they believe are best for the language.
It's at 0.2.4, and have stated they won't lock in most things until 0.3. Which at their rate of progress (based on their history), looks like that will be this year. Then they are shooting for 1.0 (which is quite fast).
Some volatility is expected type behavior for such a relatively young language. Various things are options or "up in the air" versus built-in or might not make the cut for 0.3. Which is understandable.
Various people and "angry" supporters of other languages, are acting like everything about V was supposed to be carved in stone and on lock from day 1. Actually, as if the developers were suppose to supply a finished product upon its release on GitHub. That's outrageously unrealistic and bias hostility that appears to show threatened interests.
I'm sure they will be updating their website to reflect the state of their language, as necessary. Their users, contributors, and developers understand where they are at.
You're missing my point. V not being as developped as other languages is perfectly fine, and I have no issues with that. My problem here is one of trust. The people behind the language have made wild claims that are objectively false. That is my issue with it. They have been called out for it multiple times. The first time I discovered V was in 2019, the website still hasn't been updated to reflect the truth. This, to me, is a dealbreaker. I feel like I can't trust the people behind this language. This is more important for me than the technical merits or lack of of the language.
> Some volatility is expected type behavior for such a relatively young language. Various things are options or "up in the air" versus built-in or might not make the cut for 0.3. Which is understandable.
It is understandable, what I'm asking them is to be clear and honest about that.
> Various people and "angry" supporters of other languages, are acting like everything about V was supposed to be carved in stone and on lock from day 1. Actually, as if the developers were suppose to supply a finished product upon its release on GitHub. That's outrageously unrealistic and bias hostility that appears to show threatened interests.
I think you're being a bit too paranoid here. The vast majority of new languages fail without anyone trying to make them fail. Especially these days in the "C/C++ replacement" niche. There's at least Ada, Rust, D, Zig, Go, Nim, Odin, Myrddin, ATS, Swift. There are probably way more than I forgot.
> I'm sure they will be updating their website to reflect the state of their language, as necessary.
What makes you so sure? They haven't been doing it in the last 3 years. I think you're the one showing pro-V biais here in the face of objective evidence.
> Their users, contributors, and developers understand where they are at.
But they still show something else to everyone. This is my issue, a lack of clarity, honesty ; thus creating a lack of trust for me.
The "trust" argument appears disingenuous. There is a difference between a bias preference or supporting another language, versus not trusting.
Anybody who took the time to actually use the language and study them on GitHub, would see they are on a tremendous development pace where they have been doing weekly releases for years (https://github.com/vlang/v/releases). That's called consistency. Usually the hallmark of reliability, which can instill trust.
On GitHub (https://github.com/vlang), you can see the activity of not only V, but many of the related projects such as vc, vinix, gitly, vls, vab, etc...
> The people behind the language have made wild claims that are objectively false.
> It is understandable, what I'm asking them is to be clear and honest about that.
Again, this is quite a distorted take, which many might say is clouded by bias. V's GitHub (https://github.com/vlang) very accurately shows the state and activity of the programming language. There are no "wild claims" on V's GitHub, other than clear and precise data, activity, and releases.
In regards to V's web page (https://vlang.io/), they describe the language relatively accurately, in addition to promotion. No different to what other languages do. For example, Nim describes itself as "efficient, expressive, elegant". Really? Is that a "wild claim" too or simple self promotion?
For greater clarity, it's quite simple to click the link to the documentation on V's web page (https://github.com/vlang/v/blob/master/doc/docs.md), which goes over the features and capabilities of the language more precisely. There is no "wild claims" in their documentation, other than the usual need for some updates here and there, for a language in alpha.
If anything, the "wild claim" is to keep saying or pretending V is vaporware, as if it doesn't exist, after more than 84 releases (they just had a new one), YouTube videos, ebooks, documentation, etc...
> That is my issue with it. They have been called out for it multiple times. The first time I discovered V was in 2019...
V was debatably being called out by advocates and supporters of other competing programming languages before they could even release an alpha on GitHub (mid 2019).
Then the developers were getting dragged, as if the just released alpha was supposed to be a finished product. And if you read into the various back and forth, there appeared to be elements of jealousy and odd focus over them having Patreon support or it being equivalent to that of their preferred language. That's more indicative of "cat fighting" between languages and disguised pettiness.
> I think you're being a bit too paranoid here. The vast majority of new languages fail without anyone trying to make them fail. Especially these days in the "C/C++ replacement" niche. There's at least Ada, Rust, D, Zig, Go, Nim, Odin, Myrddin, ATS, Swift. There are probably way more than I forgot.
Not being paranoid, so much as calling it as I see it. There is a lot of bias and competitiveness between languages. So, when assessing the viability of a language, its best to see through some of what is being said.
True, many languages and projects have failed. This then demonstrates that V's present weekly release schedule makes them even more credible. They are demonstrating that they can independently stand with Rust...
> In regards to V's web page (https://vlang.io/), they describe the language relatively accurately, in addition to promotion.
No they don't, see my various points that you have ignored about false claims. You're again talking a lot about bias, as if I was defending another language "against" V. I am not. I'm judging V on what they show, and the first thing you see is vlang.io, which contains many false claims, and has contained them for years now. You're right that consistency is important. The actual developement behind the language seems to be going well, which is great. All I'm asking is for them to stop lying and making false claims on their page, because this completly breaks my trust in them.
> For example, Nim describes itself as "efficient, expressive, elegant". Really? Is that a "wild claim" too or simple self promotion?
For the record, I think Nim marketing (on the page or by people using it) is also exagerated, and that's one of the thing that makes me avoid it. Their claims are more vague, which makes them harder to refute, but for example:
> Modern type system with local type inference, tuples, generics and sum types.
That's ML. 1973. I wouldn't call that "modern". But that's more of a nitpick. The website of Nim is in general more reasonable about their claims. It doesn't say "here are the coreutils, reimplemented in Nim" while pointing ot an empty repo.
For V, on the comparaison page with Go: "- No GC", on the main page of V: "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". Either no GC is false, or the frontpage gives false informations about memory management. In both case that's not inspiring confidence.
> If anything, the "wild claim" is to keep saying or pretending V is vaporware, as if it doesn't exist, after more than 84 releases (they just had a new one), YouTube videos, ebooks, documentation, etc...
V, as in the language that's on github.com/vlang, exists and is very real. V, as in the language described on vlang.io, doesn't. They had two full years to fix this, and they didn't. So this means that at best, showing correct information is not high-priority for them. This goes against my values. Since you're accusing pretty much everyone else of showing bias, I can get a bit paranoid too. Maybe they keep a page like that because it'll make people talk about the language. Maybe it will even convince a few people to try. I can't accept that attitude coming from people working on a compiler, and thus won't use V as long as this issue exists.
> True, many languages and projects have failed. This then demonstrates that V's present weekly release schedule makes them even more credible. They are demonstrating that they can independently stand with Rust, Go, Zig, and Nim as a useful language.
We are using two different definition of "failed" here. I could work on a C replacement for 50 years, and say that it "stand with Rust, Go, Zig and Nim as a useful language". Which would be technically true. But people here often talk about popularity. In that sense, V stands with Zig and Nim as "known by a few people but not much more", while Rust and Go now have a place in the industry.
I also wish you would spend less time accusing me of being biased against V and spend more time explaining to me why the main page of V still has wrong information in the favor of V 2 years later. It's easy to dismiss any external criticism as competition trying to put you down. Some people are probably doing this. But there is still valid criticism from non-users, like what I'm offering here. You're free to take it or leave it.
> ...the first thing you see is vlang.io, which contains many false claims...
Maybe vlang.io is first, depending on the search. Even so, likely a search will also bring up V's GitHub page, usually right underneath it.
There are no outright false claims or lies on the vlang.io main page, in my opinion. Debatably, there are some disputable or questionable claims, which mostly center around comparing V to other programming languages. This appears to be where the hostility towards V comes from, and where advocates of similar competing languages feel they are compared unfavorable, so are possibly out for "revenge" or upset.
Those upset at various claims by the comparison of V to their favorite language, might be better served to study V in more detail. Simply saying they are "lying", without ever having used V or knowing anything about its development details, results in erroneous counter claims or comes off very odd to those who have used V. I made a point of providing links, so that those making such counter claims, could either download or examine information about V.
Comparisons and competitiveness is inevitable, but clearly, there is nowhere for the outrageous opinion that V is vaporware, after 84 releases and weekly updates on GitHub (https://github.com/vlang/v/releases).
> For V, on the comparaison page with Go: "- No GC", on the main page of V: "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". Either no GC is false, or the frontpage gives false informations about memory management. In both case that's not inspiring confidence.
It's quite telling that is the focus of saying V is "lying" or is "vaporware", which is not on the front page, but a link to comparing it with other languages (in this case Go).
If a person bothered to take time to use V, or study the discussion and issues on it's GitHub, they would have likely realized that the intent of the V developers/contributors appears to be to use the autofree engine. The existing capability to use GC (-gc boehm) is both optional and experimental, because autofree isn't finished yet.
GC is not a feature of V, as its considered too slow, and the experimental -gc boehm appears to be provided for temporary purposes. This can be assumed through reading of their discussions and issues by developers and contributors to users on their GitHub.
On V's web page, they make it very clear that the autofree engine won't be ready until 0.3 (they are at 0.2.4). And they will allow for the disabling of autofree, -noautofree.
It's not anything forced, but an option. This would appear to include any use of GC (as part of autofree) for additional clean up.
So, the necessity of having to change statements on their web page is clearly a matter of opinion, but it's definitely not "lying" or looks like any intention to deceive. Furthermore, as they are in alpha, if they find that the experimental GC (-gc boehm) is working better than they expected then don't see anything wrong whatsoever with them deciding to keep it as another option. Which if they did, they would probably update their web page. That is the nature of alpha. You experiment, play with things, then make decisions based on the result.
> ...language that's on github.com/vlang, exists and is very real. V, as in the language described on vlang.io, doesn't...
First, those arguing against V (and perhaps some hiding their true reasons), usually don't make such a distinction. Usually its straight up claiming the V is vaporware, which clearly doesn't make any sense, with a quick look on GitHub.
The new position that you appear to be presenting is that the language as described on vl...
> There are no outright false claims or lies on the vlang.io main page, in my opinion.
You've ignored my earlier point about pure functions that are allowed to have side effects.
> A very good argument can be made that V is closer to Zig and Nim, in terms of its present niche.
Zig maybe, Nim I doubt it. Nim is a managed langauge trying to appear as if it was not, like Swift.
Now for the rest I don't see the point of arguing, as you spend half of your energy trying to frame my comments as some kind of attacks on V. They are not. My comments are raising points that, to some people, are obvious red flags. If you don't see those as red flags and think the current situation is fine, no problems. I'll go my way, you'll go yours, and that's it. Energy is better spent on what the actual users want, not what external people think. Now, if you want to actively fight the reputation of vaporware that V has, it might be a good idea to listen to my feedback and feedback like that. Remove/replace the claim about "pure functions by default", remove the coreutils or mark them as x% complete or wip, like the C translation.
I'll stop replying here, as it's honestly exhausting to be constantly accused of something I'm not doing, especially when there is no way to prove you the opposite. Please keep the paranoia away next time.
> You've ignored my earlier point about pure functions that are allowed to have side effects.
I was more hoping that you would read what they actually wrote. If one bothers to actually click the link on vlang.io which refers to "pure functions" it would go to (https://github.com/vlang/v/blob/master/doc/docs.md#pure-func...) their documentation where they elaborate on it further.
There you will find, "V is not a purely functional language however".
You are arguing (or condemning) from the perspective of what is a pure function, where there are various opinions about such. V's documentation gives their interpretation and explanation on what is a "pure function".
Thus they are not lying, attempting to deceive, or hiding anything. Click the link, get their explanation about pure functions. Arguing about the "purity" of pure functions or the possibility of side effects happening, is somewhat similar to arguing about what is or is not "real" OOP. There are different interpretations of such. It can become a more philosophical debate than about practical programming.
> ...to be constantly accused of something I'm not doing...
Reading your other comments on this thread (in other locations), it does appear that you are a Go language supporter. Nothing wrong with that and you are definitely entitled to your opinion. Not saying you are doing anything malicious, but rather people are allowed to also have a different opinion to what you have stated, or like the V language as you appear to with Go.
It's interesting when the creators had a clear intention to shepherd people one way and the programming community went another. Both Objective-C and Java (which was informed by ObjC significantly, including having a few implementors hired from NeXT) clearly intend for low level, C-style imperative code to be isolated early behind a message-passing interface and for systems to be built out of message-passing between objects. This is most explicit in Brad Cox's book describing the ObjC way, "Object-Oriented Programming: an Evolutionary Approach". In practice, a lot of Objective-C software and quite a lot of Java software is designed as big C-style procedures which call into APIs using object methods.
Similarly, the OP describes Perl, which is designed using a postmodern philosophy: however you think about your problem, you can solve it that way! In practice as OP describes many people reach for regex.
>...clearly intend for low level, C-style imperative code to be isolated early behind a message-passing interface and for systems to be built out of message-passing between objects.
>...Java software is designed as big C-style procedures which call into APIs using object methods.
Do you mind going into more detail regarding those two styles in Java? I wouldn't exactly say that Java shepherd's its programmers towards the message passing style at all.
The default scope of everything is package level. If one writes all of their code in one directory, Java is basically procedural code with syntactic sugar for functions with structs as the first parameter. The moment you want to share with other packages, one has to use the public keyword. I'd argue what Java 'wants' you to do is to have a few public methods that operate on internal state, but it doesn't care too much if state is shared within a package. A good Java API wouldn't have many Setters/Getters that do nothing but set/get references to internal objects, but rather ones that parse and make sense of it's internal state.
In practice, I've found most codebases don't care about any of that so every variable is private, they all have public getters/setters (so it's NOMINALLY encapsulated) and most methods are public with a few private methods strewn about.
Thanks for the explanation. I got caught up on the "message passing" vs "api" part of your original comment and was wondering how you differentiated between the two.
>A good Java API wouldn't have many Setters/Getters that do nothing but set/get references to internal objects, but rather ones that parse and make sense of it's internal state.
I completely agree. Getter/Setter obsession often leads to breaking the Law of Demeter in most Java code bases I've seen.
I'm not the original poster so you should wait for their reply, hah! They may have meant something else. This is just a topic I've spent some time thinking on and wanted to share my own $0.02.
My go-to language for personal projects/small tools at work is Java. I think if one looks at Java codebases as strict encapsulation at the package level it becomes a lot more bearable.
This shows how much programmers hate distraction. Perl doesn't really shepherd you into regexp, it's just easy to s/// in it, so you think: hmm, searching for a good xml library takes time, replacing is fast and non-distracting. In C, replacing in a string is so much harder, so you're okay with setting up a parser and using it. If only Perl had this out of box:
use xml;
foreach (xml::select(<STDIN>, "item[color=orange]")) {
$_->setattr("color", "red")
}
print(xml::result)
But just install XML::Parser you could say! Well, look here: [123] and tell how to do the above from it quickly. You can't. You still want regexp-like selector thing and Perl semantics, not just a deep hash of hashes of arrays of hashes which loses Perl's appeal instantly.
Better languages have better tools to work with out of box, and these tools match the language spirit. There is no XML Parser for Perl on CPAN, they all are for some other language that Perl isn't.
> Perl doesn't really shepherd you into regexp, it's just easy to s/// in it
I would also count that as "shephearding" or "pushing into a direction" or however you want to call it.
I'm not necessarily agreeing with the whole post, but I believe it even matters how easy it is to integrate libraries in general. Some programming languages make that easier than others and also require it more than others (by having a smaller stdlib).
And that definitely changes the way people think and act within the whole ecosystem.
Well, that's pretty identical, this guy knows Perl.
But for me it's still hard to continue the search after those two, when you have s///, and stackoverflow is few years in the future. I believe that most Perl programmers felt similar.
Perlmonks has been around for ages, and until the rise of SO was the definite go-to resource for any question Perl-related. Eons ago, the naive questions about HTML and regexes ( like https://perlmonks.org/?node_id=47553 21 years ago) were replied with "use a proper parser module".
>Perl doesn't really shepherd you into regexp, it's just easy to s/// in it
Well, that's what meant with "shepherding" though. It's not like a language forces you to use a particular feature or only offers that way of doing things.
It's about a language encouraging such a use, including by making it very easy (and, in Perl, first class) - and it's also the culture (and existing codebases) of the language going that way (which, for Perl, is also the case).
> Well, look [at libxml] and tell how to do the above from it quickly. You can't.
Bullshit. Any reason for writing such flamebait? On /. you would have been voted into oblivion for that.
use XML::LibXML;
my $dom = XML::LibXML->load_xml(IO => STDIN);
$_->setAttribute(color => 'red')
for $dom->findnodes('//item[@color="orange"]');
print $dom->toString;
It is bullshit, as many commenters already noted, but it is also my experience back then, in the light of an article's meaning. The experience was bullshit, let that sink in. Perl, google, CPAN documentation on the first two (big) modules gave no clear reason to not s/// and go deeper. I chose s///. Was(am) I smart? Probably not so much. Was all of that inviting me to s///? Yes. And it worked, I had to parse these stupid enormous xmls into my computer parts warehouse where I worked at, and had no time to read all the manuals while clients crowded at the door.
Look at the doc again, where is your snippet there? Down 2 levels of links probably. You didn't write this right the first time you opened it in your browser. Where is ->findnodes() reference? I skimmed ::DOM, ::Document, ::Element to no avail. Even now my patience is over, while e.g. jQuery made it obvious "$(sel).attr(…)" even though I never really used it nor read its docs.
Lots of misconceptions and wrong statements. It gives me a headache!
When I said bullshit, I meant to express that you are talking nonsense regarding the quoted part. I proved that it is nonsense by producing the desired code. You don't get to redefine the word bullshit how it suits you after the fact.
You previously wrote that "you [ed: that is, the other HN readers] can't" "tell how to do the above from [the documentation] quickly". But now it turns out that you meant to express that "I [that is, wruza] can't" use the documentation in the specified way. That's a completely different idea.
> Perl, google, CPAN documentation […] gave no clear reason to not s/// and go deeper.
This is the same abdication of personal responsibility as a programmer as shown in the article. I won't stand for it. The premise of "shepherding" as described is plain wrong: a programming language or its documentation can't make one do anything. It is not a living being with will and intentionality, it is just a thing. Anthropomorphising a thing is not a rational way of looking at the world, but belongs into the realm of magical thinking because this idea allows one to say "a thing made me do something" when the reality is that someone chose to react to the thing in the way he did, and the responsibility for doing so falls on him alone.
The true meaning of "shepherding" involves another human being. I do not rely just on Web search engines and documentation (i.e. technical means) as I have a healthy amount of scepticism about my own ability, so I can take a moment to talk to another programmer about my idea of XML parsing, and if he sees regex, he will point out the potential flaws and set me right. Code review is a thing, problem averted by social means. Given the results, I suppose you did not talk a programmer. I suppose you also did not bother to stop for a moment and think "TIMTOWTDI, am I doing this in the best possible way?". If that is so, then you can see there is room for growth and improvement. This is the difference between banging with one's car against the crash barriers on the mountain to keep it on the road and taking the time to learn to drive properly.
The reason why the concept of regex for XML has not been eradicated yet even though we could do so by expending a bit of technical and social effort is that we have come to agree that Perl tries to be for everyone. I will not harshly criticise this practice or not say anything at all when beginners or non-professional programmers do it, but you cannot be afforded this protection, the expectations of conduct are higher.
> [I] had no time to read all the manuals while clients crowded at the door
The correct way to handle this situation would have been to take time management into your own hand (again, responsibility as an adult in a work setting) and say something along the lines: "Pardon, I have work to do and need to concentrate. I cannot be productive when I am perpetually distracted by people standing in my door. I need to ask you to go away for now. Please send me an email and we can appoint a meeting time for discussing what's on your mind. If you have something urgent, I cannot deal with it right now, notify my substitute colleague/my manager/my boss (as appropriate)."
And there you have 5 minutes of free time with a clear head and can get an overview of the documentation.
> look at the doc again, where is your snippet there? Down 2 levels of links probably.
That's false. Each method is down one link, i.e. directly reachable from the main documentation page.
When I was studying Masters in Physics Engineering in Uppsala I spent all my time in the Sun+Solaris computer labs instead of going to the lessons we had in our Math and Physics courses.
I fell into the rabbit hole of HTML+.js+Applets and just couldn't do anything else. I made my first HTTP server that I ran on the university account without anyone noticing! :) I found Karl Hornells javaonthebrain.com (little did I know he was sitting in the building next to me):
High-performance Java is indeed basically writing C in Java, including using you own byte array for memory. It's amazingly un-idiomatic though, and you'll get (rightly) roasted by fellow Java developers for doing it without a clear and compelling need.
In other words, you've missed the whole point of the article. Just because you can do it, doesn't mean you'll end up doing it.
>Clear and compelling need? Isn't $1/KWh enough for you? I don't think you understand that real electricity prices can never go down!
So? It's not like the electricity bill is a major concern for 99% of companies with servers. It's usually less than what they pay to stock the fridge with water bottles...
Many small things is the way big things form, all collapses happen this way because humans don't understand compounded exponential changes and react too late.
Right now not a single company is making any profit, the margin calls are filling with debt at a rate that nobody can comprehend.
Just a question of time, it's inevitable, what goes up must come down.
>High-performance Java is indeed basically writing C in Java, including using you own byte array for memory.
Oh, I wish. For example, in C you can easily return multiple values through pointers. In Java, you have no such luxury without allocating so it's even less flexible if you can't allocate.
Of course it's a huge error, which is why you should never do that. Instead, you pass the addresses of local stack variables to that function as pointer arguments and let that function write its outputs there. Voilà, multiple return values without heap allocations.
That's a bit of a stretch using the word 'return' there. Forgive my exactness, but return implies, well, returning a value from a function.
I'd say that Java has similar semantics to C. One can easily pass object references to a function that sets their data. That looks and behaves almost exactly like C. The fact that the references are allocated on the heap is an implementation detail.
Let me be clear, a pointer to a struct on the stack in C is obviously not the same as a reference to an object allocated on the heap in Java. The point is that from the point of view of what the language induces you to do, Java and C are similar. Anyone that has used older libraries in Java can attest to this; they look and feel remarkably like C.
I am a long term C# dev and have my educated guesses in what I am shepherd to ... But I am very curious about your opinion here since I am probably blind to this.
And to keep the discourse focused on the interesting things, let us exclude "into Microsofts hands / into Azure cloud".
Being on the Java/.NET/C++ boat since the early days, and having been on the receiving end of those stacks on the Microsoft ecosystem, I would actually make a point of "into Azure cloud", given that the shepards at the desktop side don't seem to have any clue about what they are supposed to do after the Windows 8 debacle.
Then the whole WinDev vs DevDiv politics seem to always influence what sheppards happen to take care of the herd, and the way decisions are taken.
Meanwhile C# seems to be turning into the C++/CLI replacement while sucking the life out of F# and VB.NET.
While I enjoy many of the latest .NET improvements, I occasionally wonder where they are going now.
Now imagine what bad behaviors "No Code" languages encourage in new programmers.
Lots of them blatantly encourage there users to "copy&paste" instead of using functions and structures.
And if they try to encourage good behavior, the sales people start to bugger the developers to "make it simpler" and the result is again a 9000 lines long papyrus of copy & paste.
> C shepherds you into manipulating data via pointers rather than value objects.
Overly simplified IMHO, this was probably true for K&R C, but in more recent C versions (e.g. in the last two decades or so), passing structs around by value is fine and often preferable.
(in general I agree with the article though, the shepherding side effect of programming languages is probably more important than the syntax and feature set)
How? Unless you are talking about really small structs, something like 16 bytes or less. Unless there are optimizations that I am not aware of, calling by value takes up stack space and make full copies each time. If you are doing C, chances are that you care about that.
Few languages pass structs/objects by value nowadays, at least not as a default action, because it is rarely what you want. Usually, a reference is what you want. In fact, if C++ wasn't designed as a superset of C, passing by reference probably would have been the default, like in most other languages.
C shepherds you into manipulating data via pointers simply because pointers are what you are using for the all important task of referencing objects.
What I like about C is that one can easily tell if something is passed by value or not, because I know these things won't be modified. C++ does not have this property, because C++ has (non-const) references - so when reading code one always has to check the signature of the called function.
On the other hand, C sometimes nudges you to pass by pointer where semantically you want a copy. Well, Matrix4x4 is the only example I can think of right now actually, and here I usually just bite the bullet and pass a copy, which might be a little wasteful but whatever (copying 64 bytes is not the end of the world, and the code might actually be inlined). And here, C++'s const-references can be an improvement to the situation.
It is indeed hilarious. Same people that will insist (until their grave apparently) that C is fine and that the OpenSSL guys were idiots for allowing Heartbleed, nevermind the fact that buffer overflows and underflows are literally the No. 1 cause of security problems when working with C...
What really ticks me off about those kinds of people is that they are extremely likely to just have been blind about their own mistakes when coding C, while loudly claiming that "you just need to be disciplined".
> and that the OpenSSL guys were idiots for allowing Heartbleed
This is the most baffling part for me. I'm yet to figure out who these mythical non-idiots are that would not have fallen for any of the pitfalls that have led to exploits in the last several decades.
Same with me. Worst part is, I actually worked with some of these smug insufferable nerds. Nothing on this Earth can convince them that they can make a mistake. Literally worst possible people to have as colleagues. So glad I don't work with them anymore.
Transformation according to logical invariants is basically the goal of compilation. Eschewing compilation creates a treadmill whereby a programmer thinks they're getting a lot done because they're mechanistically spitting out a large volume of code, but really they're acting as an imperfect compiler.
See also the large crowd of people writing JVM assembly (aka "Java"), as opposed to using higher level languages like Clojure or Scala. Yes JVM is leaps and bounds ahead of C, just like C is leaps and bounds ahead of machine assembler. But unless you're often writing things that existing compilers cannot, then personally standing in for a compiler is mostly just a way of introducing bugs.
This line of thinking is just an extension of everyday thinking of far too many people.
Take nuclear reactors as an example. Some people seem to legitimately think that its possible to eliminate all sources of errors in such an immensely complex system. If there's been an accident, the analysis is generally "we have to add sections in the manual for this specific scenario and we'll be safe", rather than accepting that it's impossible to prevent all errors and that it's merely a question of time before the next anomaly arises.
Not the best possible analogy but I get your point. The problem with OpenSSL and Heartbleed was there weren't even procedures in case of sh_t hitting the fan, whereas with nuclear reactors that's often the case.
In programming, and especially systems programming, people love to pretend they are infallible, to the detriment of everyone suffering from permanent firmware bugs and critical security infrastructure leaking their secrets.
People working in systems safety work on (at least) two levels:
1. Physical
2. Social
On the physical side they try to establish controls that prevent or mitigate issues or minimize the consequences of them. For instance, a modern reactor (probably, not my domain) is going to be designed in a way that certain meltdown situations can't occur (physically designed to separate reactor materials at certain thresholds, resulting in an automatic shutdown versus manual) or are properly contained (smaller reactors where a runaway reaction is more easily contained with modern materials at a reasonable cost).
On the social side there is training, regulatory controls, and an emphasis on safety culture/discipline. Discipline doesn't scale, but you can't totally avoid having it. This would be things like not turning off an alarm because it disrupts your nap (looking at you, Homer J. Simpson), but actually responding to it to determine its legitimacy (related concept: normalization of deviance). They'd also want to look at it from an HCI perspective and minimize false alarms and overwhelming operators with alarms or information that seem to be at the same level of importance, but aren't. So true critical issues are brought to the front as high priority, but less critical issues (ones that should be addressed but are lower priority) won't overwhelm the operator. (I've worked with others on this wrt avionics systems, alarm fatigue is real so you have to be choosy in which things get audible alarms and which get visual alarms.)
> If there's been an accident, the analysis is generally "we have to add sections in the manual for this specific scenario and we'll be safe",
In my experience, that's a belief held by the prideful and the novices.
> rather than accepting that it's impossible to prevent all errors and that it's merely a question of time before the next anomaly arises.
Anyone with a modicum of humility or experience will accept this and act accordingly.
> buffer overflows and underflows are literally the No. 1 cause of security problems when working with C
Why don't these ancient projects just move to C++ for vectors and strings,
that do the buffer handling correctly for you?
Surely they can run a C++ compiler on any workstation these days?
The usual and not completely unfounded reasons are
- Dependency on huge C++ runtime library and possible ABI problems. Twice so for embedded targets.
- Careless use of C++ features can lead to large binaries and unexpected runtime costs.
- Lack of judgement and template metaprogramming or even plain polymorphism can lead to code that's really difficult to maintain.
- Bad experience with pre-C++11 versions of the language or Windows C++ APIs.
There are many modern things that run software, are connected to internet even if they shouldn't be and are tiny by modern standards. Just because something runs on servers doesn't mean that it doesn't run on processors that are just enough to run a minimal version of Linux kernel. And, have you ever worked with newbie C++ developers? When it comes to bad judgement, C++ gives a lot more rope and especially rope that's C developers are not familiar with.
> Just because something runs on servers doesn't mean that it doesn't run on processors that are just enough to run a minimal version of Linux kernel.
Maybe, but it seems unreasonable to hold the world back because of those systems.
> When it comes to bad judgement, C++ gives a lot more rope and especially rope that's C developers are not familiar with.
I think one could just ban most of that rope to junior devs.
"Don't try to roll your own classes or templates, for getting them right requires both skill and discipline."
Also common guidelines ban rope that isn't needed but for implementing the superior alternatives,
that are usually found in the standard library.
You can still program in a procedural way and use just the low hanging fruits of the STL, that was designed for correctness above all, for Alex Stepanov made it to demonstrate his ideas of generic programming: C++ just happened to make implementing it possible.
I'd guess a lot of C projects nowadays have very conservative management (most of which might even be actual programmers) and they could operate under "it works good enough, don't you dare touch it!".
Sadly this is the case for most topics on HN. Most discussions involve people talking past each other and beating dead horses. Not that it’s unique to HN.
Sometimes it's worth beating the dead horse. I would love it if I can eventually shout the "you-just-need-discipline" people to extinction. They should never win this debate. We must be humble and accept it's in our nature to make mistakes and that we should improve and move the area forward, not constantly scream at each other in favour of something impossible like "well just be 100% disciplined 100% of the time, duh".
> I would love it if I can eventually shout the "you-just-need-discipline" people to extinction.
You shouldn't. They're partly right.
You are correct that "just be 100% disciplined 100% of the time" will fail. But "just get a language/compiler smart enough that I don't need discipline" will also fail.
Ah, I thought that was a given. You definitely should learn a lot of discipline when you're a junior dev. You should develop eye for details and what feels right or wrong. That takes time and experience and can't be skipped.
Speaking from the point of view of a senior dev however, certain practices become ultra tedious and annoying from one point and on. Literal soul crushers.
I don't know if it's sad. Realizing this is not going to change, but one can change one's own outlook on it. Currently, I use it as a filter: if I see this in a discussion, I know there's nothing for me to learn. And people show their 'true' colors, when they bite on these easy and emotional pseudo-discussions. So I have a free filter that works really well, and I don't have to do anything for it. :)
I for one enjoy the plurality of opinions! I much rather live in a society where people argue and don't agree on things, than in a society where there is one 'correct' opinion on everything. Even if I'm on a particular bandwagon, I don't necessarily want everyone else on it. If my bandwagon is actually heading for the cliffs, it's going to be the people not on it who may happen to focus my attention to the imminent crash so to speak.
But in either case, the arguments are “bandwagons” because they are context-free. Without context there can’t be a reasonable discussion of merits and trade offs, nor exploration of the bounds of the solution. Just parties talking past each other, never learning a thing.
You also just need to be disciplined to calculate the structural integrity of a bridge in your head, yet civil engineers learned the hard way that bridges collapse independently of how genius they thought they were.
Using design systems (languages) that eliminate entire classes of mistakes, because they make them impossible is the rational choice. It is human to make mistakes, but how we learn from the past to prevent mistakes in the future is where you can tell between engineers who truly care and those who are in it for their own ego.
I’m absolutely in support of various compile time enforced constraints on languages, but do not forget that the field we are in has unbounded complexity. So after a certain range they have diminishing returns. (Eg. types are a trivial property that can be checked through arbitrary code bases, but race conditions and a litany of other bugs are not. Introducing harder and harder concepts to try to claim a tiny formal verification will quickly become diminishing returns at the price of a much complex language z)
I don't disagree. The problem is that people stop with the better practices (like statically strongly typed languages) and increased coverage/branch testing veeeeeeeeery long before they hit the diminishing returns. That's the crux of the issue most of the times: "eh, good enough".
Additionally, I really want us to stop pretending we can program CPUs directly (and I include the higher-level languages here as well, not just machine code and assembly). We can't. We need more declarative languages, e.g. I want to be able to say "I want to open a huge file with scatter-gather mechanics and with these 3 functions piped that do mapping and reduction". Why should we always code this by hand? There are objective best ways to produce this code in most languages. Let's have a DSL library that allows us to express that intent and be done with it and move on to be productive on another task.
This is not done enough to this day. Various people and organizations are doing it here and there and to me they are the beacons of our future hope. Everybody else is just banging at their Emacs / VIM in the meantime.
You can join me in a mourning session because when I stumbled upon them years ago I never took the effort of so much even writing down their names so now it's a blank stare from me, sorry... :(
I would consider coding these things by hand in the following non-exhaustive cases:
- I am writing the thing you are asking someone to write
- I have performance/memory constraints that existing high-level abstractions can’t adequately deal with
- I have legal or other business constraints preventing me from using existing high level tools (eg company prefers to build in house to minimize dependency risk exposure)
- I want to learn how that stuff works
- I am working on new technology that does not fit cleanly into the prescribed mold of the high level implementations
RE: your second point, it can easily be covered by a declarative DSL with a few conditions (in macros / code generating constructs).
Example: in Elixir you have the so-called streaming functions you can pipe to each other that can do e.g. filtering, mapping, chunking and what not. But they don't actually operate on the data you passed them; they generate further functions and produce chains that only start working when you put a certain command at the end of the command pipe.
This works really well if you work with a collection of 100_000 elements. Since Elixir is an FP language, you don't want to copy this huge list, say, 7 times one after another (assuming your transformation pipe has 7 stages). By using the streaming functions you only use the original list and produce a final list. Just one roundtrip to the memory.
But... it has a performance impact and it's definitely not worth doing if your collections are e.g. less than 1000 elements, maybe even 2000.
A declarative DSL can easily cover for such cases and have separate code branches addressing them.
RE: tinkering, obviously. I am not saying normal programming should be "banned". But it's about time we have higher-order programming, man. We're all collectively slacking off on that and should be really ashamed!
The rest of your points I don't see how to automate and that's OK. Like any tool, declarative programming should be used sparingly and only where it truly applies.
Race conditions aren't preventable as a general phenomenon, they're something real in our world, if you put the cat out the front door and then walk to the kitchen and close the kitchen door, the cat may meanwhile run around the building and back in through the kitchen door, so, don't do that.
However data races (a small but important subset of race conditions in concurrent software) are preventable, and so you can use languages which do so. Unlike the cat race condition I described above, data races are often unfathomable in real systems, the ultimate cause of truly mysterious and hard to reproduce bugs. So they're very much worth preventing.
Anyway, while it is true that "unbounded complexity" is potentially necessary, tools capable of such power should not be the default. When I spread jam on toast, I don't use the butcher's cleaver, I use a butter knife that's too blunt to cut flesh let alone bone. Which means, if I screw up the worst case is maybe I get jam on the carpet instead of the toast, not I cut my own hand off.
That difference in tooling can exist both within a language, and also between languages. There are many programs you cannot write in WUFFS, whereas there are (almost) no programs you can't write in C++. But that actually means we should use WUFFS when we can, because we don't want those programs. The image viewer where PNG files with long rectangular orange areas cause the hard disk to get reformatted, easily possible in C++ and not WUFFS. We don't want that program. The icon maker where a JPEG text comment with a shell script in it executes the script, easily possible in C++, not in WUFFS. Don't want that either.
I think we are not in disagreement - I am absolutely for static types and even for some notion of ownerships a la Rust, but for example dependent types may not be worth the extra mental overhead/arguings with the compiler.
Are you sure dependent types don't make your programs simpler instead of more complex? A more powerful type system making programs simpler isn't anything new.
I personally haven't seen them simply anything yet, but your assumption that verifying things with a dependent type would necessarily result in more complexity than a non-verified program is not trivial and needs support.
Indeed, and it points to why there are multiple languages, because features that eliminate one class of mistakes can make it harder to eliminate another class. For instance, a mistake might be a spelling error in an identifier, or it might be a program that does completely the wrong thing because it was too hard to read, or that does nothing because it took too long to write.
Yeah, that's very true. Try and do FP in Java or C++ or PHP or Python and see where it gets you.
And before I get a ton of "ackchyally you can do X" guys, yes you technically can but there's friction and you are implicitly not encouraged to do so.
I like languages that push you to think about X or Y so you can't miss it. Case in point: strong static types in many languages, or lifetimes/ownership in Rust.
In my experience the issue with doing e.g. FP in Python is that even if your own code is fine you probably want to use libraries which are not FP which will create a pretty jarring missmatch.
Yep, exactly that. Plus it's very likely everybody else in your companies is not writing FP Python so you'll be peer-pressured to stop because it confuses your colleagues during code reviews.
> I like languages that push you to think about X or Y so you can't miss it.
Yet most modern languages steer away from checked exceptions, that you cannot miss. And I agree with them, catching UrlMalformedException from a hardcoded static url is unnecessary.
But most importantly, checked exceptions just don't play well with FP, that's why they are getting pushed off the ship.
I have such a weird relationship with checked exceptions in Java. I like checked exceptions because it makes exceptions part of the API. However, much of the time it's super intrusive and try-catch can be clunky. At this point I'm convinced there is no good solution from a language level for exceptions/errors. They just suck.
oh I don't think doing functional programming in PHP is all difficult/frictiony. C++ there's probably a bit more friction but if you're on a modern version it's also not too bad.
This is a valid point. The culture of various languages forces their favorite paradigm. In the case of many class-based OOP languages (or those that have strongly adopted it), programmers can be publicly shamed and ridiculed for not writing class-based OOP libraries and programs. Even just using simpler objects can be frowned upon because its not using classes, even if it can be considered valid OOP too.
This is why, unless forced or pressured to on a professional level, prefer dabbling in those languages where there is no classes or its truly an option, and not the main (forced) paradigm. Prefer less stress, hidden spaghetti code (pretending it's not), bullying, or shaming. Just way more enjoyable to write in languages where the paradigm that you choose is because it makes sense to solve the problem, not because "that's just what we do around here".
That might be true for perl, but I don’t believe that’s true for programming languages in general.
> C++ shepherds you into providing dependencies as header-only libraries.
There’re many C++ libraries provided as DLLs too, like QT. Just because the standard library is mostly header-only, doesn’t mean that’s always a good idea for other libraries.
> Java does not shepherd you into using classes and objects, it pretty much mandates them.
Maybe, but modern C#, despite similar to Java in many regards, doesn’t promote any particular coding style. Classes and objects are working fine, but so is FP or procedural. And there’re decent libraries, both Microsoft’s and third party, implementing reactive and actor approaches.
>Perl shepherds you into using regexps...Note that there is nothing about Perl that forces you to do this. It provides all the tools needed to do the right thing.
It does have index() and rindex(), but doesn't have a begins_with(), ends_with(), and other string functions that Python, Ruby, PHP, java, etc, have. You can roll your own, but there are arguably things missing.
It actually has this in a more powerful form: you'd use /^./ or /.$/ of course, and if you want more explicit writing, you can use Regexp::English that provides the kind of functions you like (see https://metacpan.org/pod/Regexp::English ).
The thing is that all of these functions are completely superfluous, because a Perlist will use regexp which are much more compact and allow writing in a nice, functional way (map and grep FTW). In fact, in most languages, manipulation of strings is so cumbersome that I go back to Perl almost every time, because a regexp is almost always the most compact, fast and readable solution.
Also frankly I never heard of anyone trying to parse XML or HTML with regexps this century for anything but a quick throwaway one-liner, so the example is actually quite poor.
"Note that there is nothing about Perl that forces you to [use regexes]...It provides all the tools needed"
I was saying that Perl does sort of force you down the regex path, because things like starts_with() aren't there. You're basically confirming that.
Also, it varies by search string size, version and machine type, but at least for me, index() is significantly faster in many situations...2 to 3x faster.
But I don't want functions such as start_with() or startWith()! I'd much rather use regexes, which are more powerful and more expressive, than a mish-mash of tests like
if ( (myvar.startsWith('hello') and myvar.endsWith('world')) or ((myvar.startsWith('Hello') and myvar.endsWith('World'))) {...}
I much prefer
if ( $myvar =~ /^[H|h]ello.*[W|w]orld$/ ) { }
Perl doesn't have these functions because it has better, more powerful tools. It's akin to saying that Python lacks line numbers as GW-BASIC did...
This is a canonical case of premature optimisation. I have performance problems with the database backend, the network, disk access, but too slow a regex is a completely non-existing problem in real-life for me. Or else I wouldn't be using Perl/PHP/Python/Ruby but C/C++/Rust/Java etc.
What's the cause, and what's the effect? I reach for Perl when I have that kind of a problem, because Perl makes it easier than anything else I've ever seen. Does Perl push me to program that way? Or is it the kind of problem that I program in Perl that pushes me to program that way?
I wounder whether Java is usually used for extra-large projects just because it shepherds developers into more-less readable way of doing things.
So that even multi-year hundred-people dozen-companies project still at least runs.
Of course, I've seen with my own eyes that even that could be broken with enough courage (seen a company ignoring all NPEs in main()), but on average Java projects stay more readable, than, say, Python.
> but on average Java projects stay more readable, than, say, Python
Can we say that is due to shepherding or rather that Java lays it's types bare for you any time you create or pass a variable? You can do this in python with type annotations, but since they aren't required a lot of dev's just don't. This leads to the code being harder to read in my opinion.
Java didn't even have generics when it was released. Any sort of higher-order functions and support for anonymous function weren't introduced until Java 8 in 2014.
Language debuted 1995, generics in 2004 (almost a decade!), and lambda/FP facilities in another 20 years.
In Java, a public class has to have the same name as the file as well.
You had a very constrained design space historically and some non-negotiable enforced conventions.
It's similar to Go code, where most of it more-or-less looks the same and you can pick up most codebases and run with them if you're familiar with the language.
"Perl has several XML parsers available and they are presumably good at their jobs (I have never actually used one so I wouldn't know). [...] This is a clear case of shepherding. Text manipulation in Perl is easy. Importing, calling and using an XML parser is not."
The people writing those lame scripts have the same logic the author does. "Must be hard, probably? So I won't even try to learn, I'll just use this regex"
A Shepherd's job is to keep the flock safe. If the blind are leading the blind, that is a lack of shepherding. It's not shepherding users to use regex. It's letting the sheep roam free and they're falling off the cliff.
Perl's problem has always been that it is too easy to use. 97% of people who write Perl aren't even programmers. It doesn't shepherd you, it lets you write programs even when you don't know what you're doing.
We should require drivers licenses in order to program. Programmers think they are capable even when they're not, and their false confidence and laziness leads to shitty code that causes problems. Same for people who try to drive without passing a driver's test. It just causes harm.
We should set legal engineering requirements for code just like for other trades. Otherwise some idiot is going to assume XML parsers are super annoying without even looking at them and try to use regex.
Do not be fooled into thinking, "The language made me do it" when the culture that surrounds the language made you do it. Different technologies have cultures brought on by often self-appointed leadership figures, even anonymous ones. Those leadership figures can popularize abuse by convincing others that it's a well-established cultural norm, simply because we tend to loathe non-conformity.
Third-party frameworks are often used to establish one's self as a leader and establish abusive practices. Once the foothold is firm, it becomes literally "The framework made me do it." Cultural norms mandate the framework, and the framework forces the abuse. If anyone protests about the consequences of abuse, blame is redirected back at the language.
To any onlooker, the consensus seems clear: Your language sucks! Those same onlookers are happy to reinforce this perception because they are busy trying to establish competing cultures & leaders of their own. "You should try my language!" Of course their primary goal is not to help, but to improve their own status by rising the tide and lifting their own boat. If they wanted to really help, they would work on establishing better culture instead of jockeying for popular culture.
Don't be fooled into thinking it's either one or the other. In reality, most of these kinds of things are a combination of social and "physical" systems. "physical" because while programming languages aren't technically physical entities, they still have their own kind of physics and paths of least resistance that people will tend to follow.
Recently I've been frustrated with the Python ecosystem and how many "object oriented" libraries there are which can't easily be tested because of all of the weird shit their constructors do (most recently, the class I'd like to test just builds JSON documents and sends them to an API, but the constructor hard-codes the HTTP client so you have to use magic to swap the hard-coded client with a mock).
Of course, someone will come riding in to defend OOP with "that's not true OOP" and another will come riding in to defend Python with "you can write bad code in any language!", both of which miss the point: this sort of thing is much more prevalent in the Python ecosystem than, say, the Go ecosystem or the Rust ecosystem precisely due to culture. And by the way, I'm sure there are lots of other languages that have a culture of poor programming practices--the intent here isn't to pick on Python (I've used it professionally for 15 years).
^ I'm also curious to hear from people whether this kind of design is "OOP" or not, or if there are any good terms for talking about this particular antipattern.
I'm not really an OOP or Python defender, and generally agree that this type of constructor is inscrutable, frustrating, and quite common in the Python ecosystem.
That said, I would not consider default mock values in a constructor to be good OOP. In general, "new"ing things in a constructor IMO is a no-go and makes for harder testing and confusing behavior.
At the end of the day I'm not even sure why this class supports mock behavior. I'd expect two separate classes with divorced implementations, instead of an if statement on a boolean param.
"Don't do work in the constructor" is an OOP principle that must be decades old.
The problem with Python and Ruby is that being dynamic it makes them real easy to do highly lightweight OOP. There's just a cultural disconnect in the OO world where once you get past objects wrapping data the design patterns tend to come from the Java world and quickly descend into explaining dependency injection containers that nobody uses in Python or Ruby. There's a bit of a cultural middle ground which is not well defined due to the history of how it all evolved.
This is why I like how various newer languages such as Go (golang) and V (vlang) or prototype languages like JavaScript or Lua have approached OOP. V even allows to easily choose if you want it on the stack or heap. With these languages, there is either no class-based OOP or classes are optional. "Freedom of choice", is way more obvious.
Maybe a "shout-out" for Object Pascal in this regard too, as they have advanced records (structs with methods) and Free Pascal has simpler objects (but Delphi's dialect doesn't).
Though I will say, Object Pascal's implementation of class-based OOP is a gentler version, versus the "harsher" implementations in other languages. Still, as with all the class-based OOP languages that I've seen, you are going to get forced feeding and forced to use plenty of OOP libraries.
Forcing or pushing class-based OOP is debatably how things can more easily and unnecessarily go bananas. Complexity traded for simplicity. Users are usually trapped in the paradigm, until they like it (programming version of Stockholm syndrome?), and are often scared to go outside of it or think the world might end if they do.
What good is a good language if you can't learn anything from other people, use any common library, or collaborate with anybody without bad practices contaminating your work?
They are different phenomena, but every practical consequence is the same. Including what you must do to avoid the problems.
Pragmatically speaking, yes, and this is the most important thing: The language eventually becomes a lost cause, regardless of where the finger of blame points. Worst of all, programming language evolution runs in circles as people invent & seek out regressive anti-languages in reaction to the misapprehension of blame.
Some examples of common cultural (that is, completely avoidable) antipatterns:
- LISP dialect devs insisting on using incredibly obscure technical abbreviations for important names, like car/cadr/cdr instead of first/second/rest or something else human readable.
- POSIX-ish shell standardizers and builders making it really difficult to deal with anything but ASCII alphanumeric filenames. Saying "You shouldn't put special characters in filenames!" isn't helpful if I'm trying to make a script to support other people's filenames.
- C-style language makers' insistence on terminating statements with semicolon, so that a tiny fraction of multi-line statements can be shortened to one line.
- Python devs treating time-zone-less datetimes as not an atrocity, for example in utcnow.
- Nix, well, being generally incredibly hard to read and learn, even after about 20 other languages. I hope somebody goes ham on that language and creates a thoroughly backwards incompatible implementation of the fantastic ideas behind it.
I guess I've pissed off the entire internet now, but remember that these are my gripes, not some absolute truths I'm trying to convince y'all of.
282 comments
[ 6.1 ms ] story [ 334 ms ] threadA funny trick question as it pretty much does both.
Also I'll take "functional trickery" over needing a page called Slice Tricks so you can find out how to delete an item any day of the week.
• `fn filter(self, P²) -> A` consumes self and returns a new thing of type A;
• `fn filter(&mut self, P)` mutates in-place;
• `fn filter(&self, P) -> B<'_>` doesn’t mutate self, but produces a new thing of type B containing references to the contents of self.
As a concrete example, Rust has `Vec::retain(&mut self, P)` to filter a list/array in place, and `Iterator::filter(self, P) -> Filter<Self, P>` to filter an iterator, consuming that iterator object and wrapping it in another.
This is a sterling example of the point of the original article: Rust pushes you to thinking about ownership, and it’s really great and I miss it when working in other languages, where I have to stop and think whether I’m allowed to mutate this object or whether I should make a copy, and the cost of getting it wrong is bad performance in one direction and bugs that can be a nightmare to diagnose in the other direction.
—⁂—
¹ You need linear types to make it just about impossible to get wrong by accident. Rust has a weaker substitute that you can annotate functions with, #[must_use], which says “warn the user if they neglect to use the return value, because they’re almost certainly doing the wrong thing”.
² I’ve omitted the filter predicate P from the signatures to reduce distraction; in each case it’d probably be something like `<P: FnMut(&Self::Item) -> bool>`: a function that is passed a reference to an item and returns true or false.
Because if it is not functional trickery, I don’t really know what is (and no, recursion is not the answer) and it is sure clearer than the 3 nested loop or whatever one might come up with.
But I also appreciate Go as a language. I love the wide standard library, I love how asynchronous IO is handled, I love the tooling and the fast compilation, I love the cross-compilation. And more than that, I recognize them as good solutions to problems. I wish other languages would adopt that. I wish making a web server/API in OCaml was as easy as in Go. I wish Rust had some M:N threading model when doing stuff that's a bit less performance-sensitive. I wish Stack/Cabal/Nix were as easy as go build. When I had to make a small program to cut text files for a friend, I did it in Go, and it was a great experience.
We're engineers, let's try to be a bit less tribalist and a bit more pragmatic, and we will all end up with better tools.
I'm not the GP, but I think this is off the mark. There's a big difference in purposefully choosing boring technology and purposefully taking pride in not knowing things.
"This is why I enjoy Go, it shepherds you to write clear and uniform code that cannot be mistaken. Go makes well-known tradeoffs that have already been discussed to death, and I find that I agree with these tradeoffs. In my experience, collaborating on Go code is easier as the absence of some means of abstractions prevents people from creating their "language inside the language", and thus allow for a team to share more easily their mental model about how the code works."
You could even add a caveat in the form of "Go isn't perfect, no language is, and has made what could be considered objective flaws. But on the other hand, the handling of things like generics shows that the maintainers are not closed to idea. They tend to err on the side of taking more time to make things rights, even if that time isn't always needed, and that's a tradeoff I agree with.".
Here goes my morning
Your code calls a library. A framework calls your code, which is what React does.
It is a view framework, not an app framework though.
The example that I've run into most is trying to write functional immutable code in Java. I have just never gotten it to work well - of course there are libraries that have weird workarounds (that aren't compatible with standard data types or libraries), or tons of boilerplate you could write, but ultimately Java just fights you the whole way. It's not really worth going down that path imo, and better to switch to something like kotlin or scala if you can.
In my current project we use Error Prone, Checkstyle, Immutables, JSR305, NullAway and a couple of other things. It makes the language tolerable, but it's a massive waste that each setup needs to introduce and tune those, and often inexperience or lack of knowledge will mean they're never introduced, if only because after a while they become painful to introduce.
Better languages exists, some are even JVM, which just do this stuff properly.
What surprises me is that even among those lucky to work on a powerful platform such as the JVM or .NET it's more typical to stick to a single language. In my mind it'd be more natural to write Java/Kotlin/C# when you want imperative abstractions and Scala/F# when you are in mood for FP. The same platform/libraries, the same IDE/toolchain, only the syntax/abstractions differ.
But we know that Scala and F# adoption is nowhere near what some of us would hope for. We also know that the ecosystem fragments immediately and Scala projects don't share [m]any libraries with Java ones for example. So mixing the two languages in one system is mostly about wrapping some legacy code.
The "worse is better" essay touched upon some deep truth.
Verbose, maybe, but that's not as fun as some of the catchy words there are to insult languages that aren't.
You'd end up with fast greenfield code to get your prototype working, but then could tighten it up to run faster, and clear out bad type assumptions as the discipline level goes up.
https://www.youtube.com/watch?v=DRq2NgeFcO0
You can also do the opposite of what you suggest: start by writing the interface file (which has the functions and types that you will expose), and then write the implementation the way you want. This allows you to cleanly separates the API from the implementation, and also gives you fast compilation times.
There may be a slight difference compared to what you suggest, as everything is statically typed, but you can start a program with permissive types and gradually move on to stricted typed. For example, replace a string that can be anything with an enum (called variant types in OCaml).
If you don't need the native compilation/want the C# ecosystem, F# is a close cousin. Scala is a different beast, but shares some origins. It relies more on objects.
Does OCaml still have the infuriating "Provided A but elsewhere wanted B" error with no indication what this elsewhere is?
I was running into it surprisingly often when I last tried OCaml.
I am not a fan of this because the type attributes also act as documentation for that the variables, parameters, etc are supposed to be and that clearly typed out text is much easier and faster to read than some IDE's tooltip at a mouse hover or whatever (assuming you even have that in the context you see the code - even if your primary IDE supports this, you may still need to read the code outside of the IDE and sometimes outside of the surrounding code like, e.g. reading a diff).
And yes, for that reason C++'s "auto" is one of my least favorite additions to the language. Even when i have to use the language, i avoid using "auto".
(if anything sometimes feel that readability benefits when having to be explicit not only about types but also the parameter names in function calls ala Smalltalk... e.g. in Pascal i sometimes wonder what that "True" or "1" really means in a call. In recent versions of Free Pascal that have added scoped enum support - like class enums in C++ - i try to use those instead of booleans as they document the API even when it is used but this is only a small case)
https://dlang.org/spec/attribute.html
Most likely you are aware of this already, but even Delphi has type inference via inline variables.
Personally I enjoy using type inference everywhere on my own code.
At work, I take the rule of only using it when the type is clear, e.g.
Ah yeah, this is why i added that EDIT but you replied before i did :-P. I agree about that part, i remember D being a bit attribute trigger happy :-P but i guess the alternative would be to break backwards compatibility which is IMO worse. Though perhaps something similar to Free Pascal's "modeswitch" directive would help (FP devs use it to introduce new syntax without breaking backwards compatibility).
> Delphi has type inference via inline variables
I know it is there but i don't really like it myself for the reasons i mentioned above (also i prefer sticking all variables together even in C++, too much code is usually a sign i need to break the functions into subfunctions and Pascal allowing nested functions is a feature i use very often). It is also one of the very very few case where Free Pascal developers have explicitly decided to not implement something because they feel it doesn't fit the language :-P.
And any repetitive words can be handled by IDEs, editors, and macros.
An IDE or editor may be able to write code for you but it can't read or understand it for you, and that's what takes most of the time on nontrivial projects. Macros undermine your tooling and mean the reader has to learn your dialect before understanding your code - they work best when you have a few common ones that are used across every codebase and supported in your tools, but at that point you're better off having them included in the language itself.
From my experience the design planning and just thinking about programs takes so much more time than the typing in that I sometimes wonder if I do something wrong.
What is the advantage of a less verbose language if let’s say the language capabilities are similar?
Similarly i rarely update the var section manually, i just press F9 to compile, the IDE moves the cursor right at the missing variable (the compiler is fast enough to make this almost instant), i press Ctrl+Shift+C and the IDE adds the variable. Same for the interface and implementation sections (think header and source files in C but at the same source code): i write the declaration for the classes, procedures functions, etc, press Ctrl+Shift+C and the IDE creates the bodies for their definitions automatically. Using Ctrl+Shift+Up/Down arrow i can move quickly between the declaration (in interface section) and definition (in implementation section) for each entry, allowing for quick filling in the code.
There are other shortcut keys, etc in Lazarus and i never really feel like having to type boilerplate code myself - but i still get the benefit of the more readable Pascal code (i have decades of active C and Pascal experience and i always find Pascal to be the more readable of the two, especially when looking at other people's code).
Everyone understands that the code is horrible and you are regarded as a hero for diving in but honestly most of it is just straightforward procedural code and you can be productive in minutes.
Now those projects that went all crazy on OO? Yeah, I would rather burn it all to the ground.
Though I would say, all in all the PSRs did also lots of good for PHP. It makes navigating modern projects quite a bit easier.
The comment could have just as easily been:
I've worked on legacy PHP code. Most of it is just straightforward OOP code.
Now those projects that went all crazy on procedural code? Yeah, I would rather burn it all to the ground.
I know, because that's my experience. The issue is not particularly OOP vs procedural, but rather how crazy it is.
However in the PHP world, I would question those wanting to write in a procedural style, as all the mature libraries and testing tools are built with OOP in mind.
Obviously you wouldn't write modern software like this but maintaining those old systems is a hell lot easier then those where people went nuts on OO and inheritance was used more lightly. Bad abstraction are worse that lack of abstractions, basically.
Either work on a modern project that has been build in recent years or if you have to maintain things, better go with something really, really old. Those in-between projects that don't seem to be totally hopeless but are still rotten, yeah that is there most pain lies.
Sure it depends depends on the specific project, everything can be abused but that is a totally different discussion.
I have found creating test seams in legacy OO code easier than starting from scratch with an untested 5000 line procedural file.
In that case, having some abstractions was better than having none.
But that's just my opinion.
But yeah good point with the testing. I should have specified that I am talking about agency work, not actually caring about the software you keep alive.
It gave people a recognisable syntax and structure, to run things on PHP's shared-nothing architecture, which is arguably its real strength.
Similarly it was a great move for Symfony to take inspiration from Spring. All the older PHP projects like Drupal have slowly been ported over to using it.
I have never met anyone who seriously wants to go back to PHP 4.
Function autoloading, without it you need to come up with a reliable way to require dependencies. This usually differs from project to project in a non-reliable way and will be messy if you don’t think this thru. It will also affect things like testing.
Only global state or (closed) function state. Namespaces are fake in PHP, it only adds the namespace name to the class name or function name, you can’t store anything inside a namespace like in C++. Thus everything is global, which is horrible and bug prone.
You can use static inside a function to keep state as an alternative, but that is hard to manipulate from the outside when needed. It can be done with a separate function that keeps track on other functions state. When I tested modern pure procedural that is what I did.
No typedef for function signatures. If you want to create a contract enforced by the compiler you use interfaces in OOP, in procedural you want to define function signatures instead, but there is no reliable way to do that in PHP without classes (ignoring any external tooling that uses phpdoc)
Testing. Have you loaded a function into the runtime you can’t unload it again, thus mocking a function you already tested becomes impossible. You can solve this by running every test unit in a separate PHP process, but that is expensive.
These are the biggest and none of them will probably never be fixed, perhaps function typedefs has the biggest chance but I doubt it.
If you are writing a large project (+500k lines) all of these are very important. That is why OOP PHP rules because it can handle all of this.
The problem with OOP is that many of the legacy OOP projects inherited the inheritance based OOP from Java, however today we know that composition based OOP is better (both in Java and PHP).
But I do agree that it seems that people jump to use async instead of e.g. threads. I think there should be more literature around what situations work best for threads vs async, etc.
Then again, few programs are actually single-tasked in practice, and libraries often end up taking advantage of those async primitives for doing stuff like connection pooling even if your application code doesn't see it.
Also the startup cost of threads affects how you write code. Does your library spawn a new thread for every HTTP request? Probably not, it is expensive and the server may be on the same host. So you end up with less consistent async support. Since async programming is a lot cheaper you tend to see better support which adds up.
Thus you only pay for it if it is required.
Minimal-cost threading support requires taking Send + Sync into account, essentially thread-safety: and the thread-safe primitives are more expensive than the thread-local primitives. Send + Sync matters become pervasive through the design of threaded code, even if they don’t need to be spelled out most of the time.
Minimal-cost async requires compiling the code into a state machine, which means you’ve got to restrict borrowing across suspend points, or mess around with Pin.
Threading and async are quite different in a language like Rust. They can even play quite nicely together. But you really can’t unify them.
Sure, you could do simple cases like single-threaded blocking-or-async, e.g. `async? fn foo() { bar().await; }` compiling to something like strawman `fn sync#foo() { sync#bar(); } async fn async#foo() { async#bar().await; }`, but this falls apart completely as soon as you want to do anything beyond immediate .await.
I guess I'm glad that Rust developers don't design CPUs, as we wouldn't have memory abstractions like cache coherent NUMA (which doesn't come for free).
Case in point: a CPU plus a discrete GPU is technically a NUMA system, and low-level APIs like Vulkan explicitly expose the ability to allocate non-coherent memory. I strongly assume that accessing that memory doesn't incur coherency overhead.
What are you even talking about? Rust gives you plenty of choice, it even lets you create as many "non-free" abstractions as you'd like.
As another example: compare "GC'd versus non-GC'd memory in Rust" to "coherent versus non-coherent memory in CPU architecture"; here Rust gives us just the bare machine, while CPU architects give us quite some abstraction plus a choice.
It is true that you could theoretically write a GC in Rust, but using it from the language itself would probably break ergonomics so much that you'd be better off with another language.
The only way to run code in another Thread is by spawning an `Isolate`. But once you have one, you can expose an API that is indistinguishable from an usual async one.
And the Dart 2.15 announcement (latest version as of today), which had a focus on making Isolates fast: https://medium.com/dartlang/dart-2-15-7e7a598e508a
EDIT: the main article about concurrency in Dart is actually this one: https://dart.dev/guides/language/concurrency
Try `dart compile exe app.dart` and then run `./app.exe`... it's really fast and the binary is fairly small!
The installation process is simple, documentation is excellent, the core library makes sense, the defacto package repository and package management is clean. And WRT this thread, the async model is dead simple to pick up.
As a scripting language it seems to be a perfect fit. But there is also a lot of first rate tooling available for Dart and I am starting to wonder if it wouldn’t be a good choice for larger projects as well.
Function is blocking and you want to use it in an async context without blocking the async worker thread? Use your runtime's equivalent of `spawn_blocking(|| sync_fn()).await` that will run the function call in a threadpool dedicated for blocking tasks.
(Both those examples are for tokio.)
There are no extra threads in the former case.
>(and in the second case, potentially undermine the whole point of using async at all)
If code is blocking for long periods of time, it shouldn't be running on the async worker thread in the first place. Marking such a function `async` would be wrong. If I write a tight loop that takes seconds to do its job, putting an `async` on the function is worse than not having it. Spawning such work in a different thread is correct. There's no "whole point of using async" that is being "undermined".
Right, you need to go right down and e.g. use a non-blocking I/O primitive instead of a blocking I/O primitive. Rust gives you no way to polymorphically do one or the other.
The examples they give to use the API using async, I really don't understand, and I can only assume I'm missing something: the example steps of going to a URL, finding a resultant element, entering text, then clicking the search button, then finding another element and then capturing a .png snapshot image for the result are in my mind all sequential, and depend on the previous step: I really struggle to understand how async helps in this case (at least the basic one), as I don't see how any of those steps can be parallelised.
I don't know anything about webdriver's API specifically, but as a general example, "do something else while waiting for a response" can include "stop waiting for a response after a timeout expires". Maybe you want to set a timeout on the "fetch a URL" function call. You can't do that with a synchronous function unless the function internally supports a timeout. With an asynchronous function you can just race it with a timeout future, and if the timeout future completes first you can stop waiting for the fetch future and fail your test.
Classic WebDriver, when used to control a single browser is indeed a synchronous API. So for example the server-side WebDriver implementation used in geckodriver is fully synchonous; it accepts a HTTP request, and does a bunch of blocking work to execute that command in Firefox. But there are a couple of reasons you might want an async client, even though the browser side is synchronous:
* You might want to drive multiple browsers at the same time. For example when running multiple tests in parallel you would have multiple concurrent requests going to different browser instances. Obviously you could implement that as multiple threads/processes/etc. but if async has the ergonomics you're looking for it is certainly a viable option.
* Modern extensions to WebDriver don't follow the command/response model. Many clients have additional functionality that's not implemented on top of WebDriver, but using browsers' built in debugging protocol. Even Selenium these days uses the Chrome Debug Protocol for some features (e.g. access to logs). Those protocols are event based and so not suitable for a fully blocking API. Of course one could still design something that doesn't require async for those use cases, but I think it's generally believed that async offers the best APIs in languages that support it.
Async/await as concept taints the call stack all the way up to main and is unmanageble on existing code.
While working on C++/WinRT I am seeing the same problematic showing up, so no wonder that Rust ecosystem is now facing the same issues.
A rendering library I use added an 'async' framework, but it turns out using it is not going to be mandatory. Much relieved.
[1] https://vimeo.com/user28693218
Also works better than callbacks for single threaded/event loop scenarios like JS or some GUI framework (with the caveat that you need to make sure you are resuming on the event loop if environment is multithreaded).
Writing in Rust takes more engineering effort and is less agile. You can code yourself into a corner which requires considerable rewriting, or unsafe code, to escape.
What I'm writing in Rust is a metaverse client. These are not common yet. They have both the real-time problems of a game client and the unexpected content-driven loads of a web browser. The goal is to keep the frame rate constant as a stream of update transactions comes in, perhaps hundreds per second. The transactions modify the scene graph and change what's on screen. So refresh is in one high-priority thread, while updating is in other low priority threads. Overload bursts happen routinely and have to be dealt with, updating near and large things first. In MMOs, such overloads are usually detected and prevented during game design, but a metaverse viewer does not have that luxury. Existing clients tend to choke and stall on overload, but with enough CPUs working on the problem, that can be overcome.
It's a fun problem. It doesn't have the shape of most common applications.
But typical web stuff: Go or even Java would seem to be a much better choice than Rust, for the same reasons I would not write a typical web service in C or C++.
Depends on whether you are talking about a single machine or a distributed program.
it pollutes every function with async annotation.
it allows you to quickly write the code imperatively by just annotating a function and using the async function inside it.
it results in code that is massively polluted with async when at most places you never had to use it at all (if you just restructured the code).
having async be as verbose as possible would enforce people to not use it as much and to structure their code to have async bits nicely separated from non-async bits.
Whatever the reason is for using async, I believe that the mismatch happens not because Async is optimized for I/O bound programs but because it's insanely easy to use (in Rust, Javascript).
That's 'front end and back-end' ... probably the bulk of software right there.
A bunch of threads running 'compute bound tasks' is probably a special case.
Debatably, the Go language has put itself somewhere in the middle, as a more modern acceptable compromise and blend with its own twists.
The V language is continuing on the path of where Go went, where readability, maintainability, and simplicity is more evident. Doing so, where it feels like a more natural feature. At the end of the day, many want to be more focused on the problems to be solved, not tripping over the eccentricities and traps of the language.
Clearly, perfection will probably never be achieved. But, I do like where various newer languages are going, as it shows progress. Usability, being easy to learn and teach, as well as being more practical about common programming issues.
A few years ago it was heatly discussed on HN... Most people seem to agree V is a textbook example of vaporware[1].
[1] https://christine.website/blog/v-vaporware-2019-06-23
Vaporware is about something that doesn't exist. V clearly does exist and has a number of developers and contributors working on it. I understand there was some controversy from 2019, and mostly prior to it being open sourced on GitHub. Then some lingering animosity that stayed around in 2019 and a bit of 2020. Well, that's a few years ago, and its 2022 now. better to look at what's going on in the present.
Of any controversy these days, seems more about competing languages and their advocates, not liking another "new kid on the block" or wanting to protect their interests. But, such is the usual between programming languages. I'm talking more from the perspective of how various languages are designed for greater convenience, simplicity, and usability.
> V functions are pure by default, meaning that their return values are a function of their arguments only, and their evaluation has no side effects (besides I/O).
> This is achieved by a lack of global variables and all function arguments being immutable by default, even when references are passed.
But there is nothing preventing you to create side effects. A function making a side effect isn't pure. For example, Wikipedia [1] says that the return value depending only on input variables is necessary, but you also need no side effects.
The claim about Option and Result types [2] is also a bit weird. It feels closer to Zig's error handling than to regular Option and Result types. An important part of these types is that they can be replaced by user-made types without losing much, while in V they seem to rely a lot on compiler support.
In the comparaison with Go [3], there is a "No GC". On the main page, "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". "There's also an option to use GC for everything via v -gc boehm.".
This is just the beginning. I don't have any trust in the people behind V and will not rely on them for building software.
[1]: https://en.wikipedia.org/wiki/Pure_function
[2]: https://github.com/vlang/v/blob/master/doc/docs.md#optionres...
[3]: https://vlang.io/compare#go
V is in alpha, and has been making steady progress and changes over the last few years. To get an idea about this, refer to https://github.com/vlang/v/discussions/7610 or search their feature request history. The developers and contributors had and are clearly having a healthy exchange on what features they believe are best for the language.
It's at 0.2.4, and have stated they won't lock in most things until 0.3. Which at their rate of progress (based on their history), looks like that will be this year. Then they are shooting for 1.0 (which is quite fast).
Some volatility is expected type behavior for such a relatively young language. Various things are options or "up in the air" versus built-in or might not make the cut for 0.3. Which is understandable.
Various people and "angry" supporters of other languages, are acting like everything about V was supposed to be carved in stone and on lock from day 1. Actually, as if the developers were suppose to supply a finished product upon its release on GitHub. That's outrageously unrealistic and bias hostility that appears to show threatened interests.
I'm sure they will be updating their website to reflect the state of their language, as necessary. Their users, contributors, and developers understand where they are at.
> Some volatility is expected type behavior for such a relatively young language. Various things are options or "up in the air" versus built-in or might not make the cut for 0.3. Which is understandable.
It is understandable, what I'm asking them is to be clear and honest about that.
> Various people and "angry" supporters of other languages, are acting like everything about V was supposed to be carved in stone and on lock from day 1. Actually, as if the developers were suppose to supply a finished product upon its release on GitHub. That's outrageously unrealistic and bias hostility that appears to show threatened interests.
I think you're being a bit too paranoid here. The vast majority of new languages fail without anyone trying to make them fail. Especially these days in the "C/C++ replacement" niche. There's at least Ada, Rust, D, Zig, Go, Nim, Odin, Myrddin, ATS, Swift. There are probably way more than I forgot.
> I'm sure they will be updating their website to reflect the state of their language, as necessary.
What makes you so sure? They haven't been doing it in the last 3 years. I think you're the one showing pro-V biais here in the face of objective evidence.
> Their users, contributors, and developers understand where they are at.
But they still show something else to everyone. This is my issue, a lack of clarity, honesty ; thus creating a lack of trust for me.
The "trust" argument appears disingenuous. There is a difference between a bias preference or supporting another language, versus not trusting.
Anybody who took the time to actually use the language and study them on GitHub, would see they are on a tremendous development pace where they have been doing weekly releases for years (https://github.com/vlang/v/releases). That's called consistency. Usually the hallmark of reliability, which can instill trust.
On GitHub (https://github.com/vlang), you can see the activity of not only V, but many of the related projects such as vc, vinix, gitly, vls, vab, etc...
> The people behind the language have made wild claims that are objectively false. > It is understandable, what I'm asking them is to be clear and honest about that.
Again, this is quite a distorted take, which many might say is clouded by bias. V's GitHub (https://github.com/vlang) very accurately shows the state and activity of the programming language. There are no "wild claims" on V's GitHub, other than clear and precise data, activity, and releases.
In regards to V's web page (https://vlang.io/), they describe the language relatively accurately, in addition to promotion. No different to what other languages do. For example, Nim describes itself as "efficient, expressive, elegant". Really? Is that a "wild claim" too or simple self promotion?
For greater clarity, it's quite simple to click the link to the documentation on V's web page (https://github.com/vlang/v/blob/master/doc/docs.md), which goes over the features and capabilities of the language more precisely. There is no "wild claims" in their documentation, other than the usual need for some updates here and there, for a language in alpha.
If anything, the "wild claim" is to keep saying or pretending V is vaporware, as if it doesn't exist, after more than 84 releases (they just had a new one), YouTube videos, ebooks, documentation, etc...
> That is my issue with it. They have been called out for it multiple times. The first time I discovered V was in 2019...
V was debatably being called out by advocates and supporters of other competing programming languages before they could even release an alpha on GitHub (mid 2019).
Then the developers were getting dragged, as if the just released alpha was supposed to be a finished product. And if you read into the various back and forth, there appeared to be elements of jealousy and odd focus over them having Patreon support or it being equivalent to that of their preferred language. That's more indicative of "cat fighting" between languages and disguised pettiness.
> I think you're being a bit too paranoid here. The vast majority of new languages fail without anyone trying to make them fail. Especially these days in the "C/C++ replacement" niche. There's at least Ada, Rust, D, Zig, Go, Nim, Odin, Myrddin, ATS, Swift. There are probably way more than I forgot.
Not being paranoid, so much as calling it as I see it. There is a lot of bias and competitiveness between languages. So, when assessing the viability of a language, its best to see through some of what is being said.
True, many languages and projects have failed. This then demonstrates that V's present weekly release schedule makes them even more credible. They are demonstrating that they can independently stand with Rust...
No they don't, see my various points that you have ignored about false claims. You're again talking a lot about bias, as if I was defending another language "against" V. I am not. I'm judging V on what they show, and the first thing you see is vlang.io, which contains many false claims, and has contained them for years now. You're right that consistency is important. The actual developement behind the language seems to be going well, which is great. All I'm asking is for them to stop lying and making false claims on their page, because this completly breaks my trust in them.
> For example, Nim describes itself as "efficient, expressive, elegant". Really? Is that a "wild claim" too or simple self promotion?
For the record, I think Nim marketing (on the page or by people using it) is also exagerated, and that's one of the thing that makes me avoid it. Their claims are more vague, which makes them harder to refute, but for example:
> Modern type system with local type inference, tuples, generics and sum types.
That's ML. 1973. I wouldn't call that "modern". But that's more of a nitpick. The website of Nim is in general more reasonable about their claims. It doesn't say "here are the coreutils, reimplemented in Nim" while pointing ot an empty repo.
For V, on the comparaison page with Go: "- No GC", on the main page of V: "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". Either no GC is false, or the frontpage gives false informations about memory management. In both case that's not inspiring confidence.
> If anything, the "wild claim" is to keep saying or pretending V is vaporware, as if it doesn't exist, after more than 84 releases (they just had a new one), YouTube videos, ebooks, documentation, etc...
V, as in the language that's on github.com/vlang, exists and is very real. V, as in the language described on vlang.io, doesn't. They had two full years to fix this, and they didn't. So this means that at best, showing correct information is not high-priority for them. This goes against my values. Since you're accusing pretty much everyone else of showing bias, I can get a bit paranoid too. Maybe they keep a page like that because it'll make people talk about the language. Maybe it will even convince a few people to try. I can't accept that attitude coming from people working on a compiler, and thus won't use V as long as this issue exists.
> True, many languages and projects have failed. This then demonstrates that V's present weekly release schedule makes them even more credible. They are demonstrating that they can independently stand with Rust, Go, Zig, and Nim as a useful language.
We are using two different definition of "failed" here. I could work on a C replacement for 50 years, and say that it "stand with Rust, Go, Zig and Nim as a useful language". Which would be technically true. But people here often talk about popularity. In that sense, V stands with Zig and Nim as "known by a few people but not much more", while Rust and Go now have a place in the industry.
I also wish you would spend less time accusing me of being biased against V and spend more time explaining to me why the main page of V still has wrong information in the favor of V 2 years later. It's easy to dismiss any external criticism as competition trying to put you down. Some people are probably doing this. But there is still valid criticism from non-users, like what I'm offering here. You're free to take it or leave it.
Maybe vlang.io is first, depending on the search. Even so, likely a search will also bring up V's GitHub page, usually right underneath it.
There are no outright false claims or lies on the vlang.io main page, in my opinion. Debatably, there are some disputable or questionable claims, which mostly center around comparing V to other programming languages. This appears to be where the hostility towards V comes from, and where advocates of similar competing languages feel they are compared unfavorable, so are possibly out for "revenge" or upset.
Those upset at various claims by the comparison of V to their favorite language, might be better served to study V in more detail. Simply saying they are "lying", without ever having used V or knowing anything about its development details, results in erroneous counter claims or comes off very odd to those who have used V. I made a point of providing links, so that those making such counter claims, could either download or examine information about V.
Comparisons and competitiveness is inevitable, but clearly, there is nowhere for the outrageous opinion that V is vaporware, after 84 releases and weekly updates on GitHub (https://github.com/vlang/v/releases).
> For V, on the comparaison page with Go: "- No GC", on the main page of V: "Most objects (~90-100%) are freed by V's autofree engine: the compiler inserts necessary free calls automatically during compilation. Remaining small percentage of objects is freed via GC.". Either no GC is false, or the frontpage gives false informations about memory management. In both case that's not inspiring confidence.
It's quite telling that is the focus of saying V is "lying" or is "vaporware", which is not on the front page, but a link to comparing it with other languages (in this case Go).
If a person bothered to take time to use V, or study the discussion and issues on it's GitHub, they would have likely realized that the intent of the V developers/contributors appears to be to use the autofree engine. The existing capability to use GC (-gc boehm) is both optional and experimental, because autofree isn't finished yet.
GC is not a feature of V, as its considered too slow, and the experimental -gc boehm appears to be provided for temporary purposes. This can be assumed through reading of their discussions and issues by developers and contributors to users on their GitHub.
On V's web page, they make it very clear that the autofree engine won't be ready until 0.3 (they are at 0.2.4). And they will allow for the disabling of autofree, -noautofree. It's not anything forced, but an option. This would appear to include any use of GC (as part of autofree) for additional clean up.
So, the necessity of having to change statements on their web page is clearly a matter of opinion, but it's definitely not "lying" or looks like any intention to deceive. Furthermore, as they are in alpha, if they find that the experimental GC (-gc boehm) is working better than they expected then don't see anything wrong whatsoever with them deciding to keep it as another option. Which if they did, they would probably update their web page. That is the nature of alpha. You experiment, play with things, then make decisions based on the result.
> ...language that's on github.com/vlang, exists and is very real. V, as in the language described on vlang.io, doesn't...
First, those arguing against V (and perhaps some hiding their true reasons), usually don't make such a distinction. Usually its straight up claiming the V is vaporware, which clearly doesn't make any sense, with a quick look on GitHub.
The new position that you appear to be presenting is that the language as described on vl...
You've ignored my earlier point about pure functions that are allowed to have side effects.
> A very good argument can be made that V is closer to Zig and Nim, in terms of its present niche.
Zig maybe, Nim I doubt it. Nim is a managed langauge trying to appear as if it was not, like Swift.
Now for the rest I don't see the point of arguing, as you spend half of your energy trying to frame my comments as some kind of attacks on V. They are not. My comments are raising points that, to some people, are obvious red flags. If you don't see those as red flags and think the current situation is fine, no problems. I'll go my way, you'll go yours, and that's it. Energy is better spent on what the actual users want, not what external people think. Now, if you want to actively fight the reputation of vaporware that V has, it might be a good idea to listen to my feedback and feedback like that. Remove/replace the claim about "pure functions by default", remove the coreutils or mark them as x% complete or wip, like the C translation.
I'll stop replying here, as it's honestly exhausting to be constantly accused of something I'm not doing, especially when there is no way to prove you the opposite. Please keep the paranoia away next time.
I was more hoping that you would read what they actually wrote. If one bothers to actually click the link on vlang.io which refers to "pure functions" it would go to (https://github.com/vlang/v/blob/master/doc/docs.md#pure-func...) their documentation where they elaborate on it further.
There you will find, "V is not a purely functional language however".
You are arguing (or condemning) from the perspective of what is a pure function, where there are various opinions about such. V's documentation gives their interpretation and explanation on what is a "pure function".
Thus they are not lying, attempting to deceive, or hiding anything. Click the link, get their explanation about pure functions. Arguing about the "purity" of pure functions or the possibility of side effects happening, is somewhat similar to arguing about what is or is not "real" OOP. There are different interpretations of such. It can become a more philosophical debate than about practical programming.
> ...to be constantly accused of something I'm not doing...
Reading your other comments on this thread (in other locations), it does appear that you are a Go language supporter. Nothing wrong with that and you are definitely entitled to your opinion. Not saying you are doing anything malicious, but rather people are allowed to also have a different opinion to what you have stated, or like the V language as you appear to with Go.
:= is used for assignment, as is = ??? Yikes
Immutable strings - How do you pull the first character out from a line?
The horror, the horror!
Similarly, the OP describes Perl, which is designed using a postmodern philosophy: however you think about your problem, you can solve it that way! In practice as OP describes many people reach for regex.
>...Java software is designed as big C-style procedures which call into APIs using object methods.
Do you mind going into more detail regarding those two styles in Java? I wouldn't exactly say that Java shepherd's its programmers towards the message passing style at all.
In practice, I've found most codebases don't care about any of that so every variable is private, they all have public getters/setters (so it's NOMINALLY encapsulated) and most methods are public with a few private methods strewn about.
>A good Java API wouldn't have many Setters/Getters that do nothing but set/get references to internal objects, but rather ones that parse and make sense of it's internal state.
I completely agree. Getter/Setter obsession often leads to breaking the Law of Demeter in most Java code bases I've seen.
My go-to language for personal projects/small tools at work is Java. I think if one looks at Java codebases as strict encapsulation at the package level it becomes a lot more bearable.
Better languages have better tools to work with out of box, and these tools match the language spirit. There is no XML Parser for Perl on CPAN, they all are for some other language that Perl isn't.
[1] https://metacpan.org/pod/XML::Parser
[2] https://stackoverflow.com/questions/487213/whats-the-best-xm...
[3] https://metacpan.org/pod/XML::LibXML
I would also count that as "shephearding" or "pushing into a direction" or however you want to call it.
I'm not necessarily agreeing with the whole post, but I believe it even matters how easy it is to integrate libraries in general. Some programming languages make that easier than others and also require it more than others (by having a smaller stdlib).
And that definitely changes the way people think and act within the whole ecosystem.
But for me it's still hard to continue the search after those two, when you have s///, and stackoverflow is few years in the future. I believe that most Perl programmers felt similar.
Well, that's what meant with "shepherding" though. It's not like a language forces you to use a particular feature or only offers that way of doing things.
It's about a language encouraging such a use, including by making it very easy (and, in Perl, first class) - and it's also the culture (and existing codebases) of the language going that way (which, for Perl, is also the case).
Bullshit. Any reason for writing such flamebait? On /. you would have been voted into oblivion for that.
Look at the doc again, where is your snippet there? Down 2 levels of links probably. You didn't write this right the first time you opened it in your browser. Where is ->findnodes() reference? I skimmed ::DOM, ::Document, ::Element to no avail. Even now my patience is over, while e.g. jQuery made it obvious "$(sel).attr(…)" even though I never really used it nor read its docs.
When I said bullshit, I meant to express that you are talking nonsense regarding the quoted part. I proved that it is nonsense by producing the desired code. You don't get to redefine the word bullshit how it suits you after the fact.
You previously wrote that "you [ed: that is, the other HN readers] can't" "tell how to do the above from [the documentation] quickly". But now it turns out that you meant to express that "I [that is, wruza] can't" use the documentation in the specified way. That's a completely different idea.
> Perl, google, CPAN documentation […] gave no clear reason to not s/// and go deeper.
This is the same abdication of personal responsibility as a programmer as shown in the article. I won't stand for it. The premise of "shepherding" as described is plain wrong: a programming language or its documentation can't make one do anything. It is not a living being with will and intentionality, it is just a thing. Anthropomorphising a thing is not a rational way of looking at the world, but belongs into the realm of magical thinking because this idea allows one to say "a thing made me do something" when the reality is that someone chose to react to the thing in the way he did, and the responsibility for doing so falls on him alone.
The true meaning of "shepherding" involves another human being. I do not rely just on Web search engines and documentation (i.e. technical means) as I have a healthy amount of scepticism about my own ability, so I can take a moment to talk to another programmer about my idea of XML parsing, and if he sees regex, he will point out the potential flaws and set me right. Code review is a thing, problem averted by social means. Given the results, I suppose you did not talk a programmer. I suppose you also did not bother to stop for a moment and think "TIMTOWTDI, am I doing this in the best possible way?". If that is so, then you can see there is room for growth and improvement. This is the difference between banging with one's car against the crash barriers on the mountain to keep it on the road and taking the time to learn to drive properly.
The reason why the concept of regex for XML has not been eradicated yet even though we could do so by expending a bit of technical and social effort is that we have come to agree that Perl tries to be for everyone. I will not harshly criticise this practice or not say anything at all when beginners or non-professional programmers do it, but you cannot be afforded this protection, the expectations of conduct are higher.
> [I] had no time to read all the manuals while clients crowded at the door
The correct way to handle this situation would have been to take time management into your own hand (again, responsibility as an adult in a work setting) and say something along the lines: "Pardon, I have work to do and need to concentrate. I cannot be productive when I am perpetually distracted by people standing in my door. I need to ask you to go away for now. Please send me an email and we can appoint a meeting time for discussing what's on your mind. If you have something urgent, I cannot deal with it right now, notify my substitute colleague/my manager/my boss (as appropriate)."
And there you have 5 minutes of free time with a clear head and can get an overview of the documentation.
> look at the doc again, where is your snippet there? Down 2 levels of links probably.
That's false. Each method is down one link, i.e. directly reachable from the main documentation page.
• http://p3rl.org/XML::LibXML::Parser#load_xml
• http://p3rl.org/XML::LibXML::Element#setAttribute
• bullen ↗ When I was studying Masters in Physics Engineering in Uppsala I spent all my time in the Sun+Solaris computer labs instead of going to the lessons we had in our Math and Physics courses. dtech ↗ High-performance Java is indeed basically writing C in Java, including using you own byte array for memory. It's amazingly un-idiomatic though, and you'll get (rightly) roasted by fellow Java developers for doing it without a clear and compelling need. cerved ↗ You can do it for fun :P bullen ↗ Clear and compelling need? Isn't $1/KWh enough for you? I don't think you understand that real electricity prices can never go down! coldtea ↗ >Clear and compelling need? Isn't $1/KWh enough for you? I don't think you understand that real electricity prices can never go down! bullen ↗ Many small things is the way big things form, all collapses happen this way because humans don't understand compounded exponential changes and react too late. coldtea ↗ >Right now not a single company is making any profit noctune ↗ >High-performance Java is indeed basically writing C in Java, including using you own byte array for memory. kaba0 ↗ I’m not sure what you have in mind but returning pointer to a value on the stack may very well be a huge error. guavaNinja ↗ I think they meant you allocate data once and pass their pointers to a function to put its outputs there.
`void return_multi(T* out0, M*out1);` kaba0 ↗ But then you can just pass in a mutable object as well in java. tempodox ↗ Of course it's a huge error, which is why you should never do that. Instead, you pass the addresses of local stack variables to that function as pointer arguments and let that function write its outputs there. Voilà, multiple return values without heap allocations. kaba0 ↗ Or you pass `this` and set a field of that in a function, which is equivalent. AmpsterMan ↗ That's a bit of a stretch using the word 'return' there. Forgive my exactness, but return implies, well, returning a value from a function. oaiey ↗ I am a long term C# dev and have my educated guesses in what I am shepherd to ... But I am very curious about your opinion here since I am probably blind to this. pjmlp ↗ Being on the Java/.NET/C++ boat since the early days, and having been on the receiving end of those stacks on the Microsoft ecosystem, I would actually make a point of "into Azure cloud", given that the shepards at the desktop side don't seem to have any clue about what they are supposed to do after the Windows 8 debacle. PicassoCTs ↗ Now imagine what bad behaviors "No Code" languages encourage in new programmers. pjmlp ↗ Depends, there are those that do encorage modularity via packages and modules. flohofwoe ↗ > C shepherds you into manipulating data via pointers rather than value objects. GuB-42 ↗ How? Unless you are talking about really small structs, something like 16 bytes or less. Unless there are optimizations that I am not aware of, calling by value takes up stack space and make full copies each time. If you are doing C, chances are that you care about that. jstimpfle ↗ What I like about C is that one can easily tell if something is passed by value or not, because I know these things won't be modified. C++ does not have this property, because C++ has (non-const) references - so when reading code one always has to check the signature of the called function. jgilias ↗ It's pretty hilarious how a good part of the comment section here is some variation of what the article starts out with: pdimitar ↗ It is indeed hilarious. Same people that will insist (until their grave apparently) that C is fine and that the OpenSSL guys were idiots for allowing Heartbleed, nevermind the fact that buffer overflows and underflows are literally the No. 1 cause of security problems when working with C... pdpi ↗ > and that the OpenSSL guys were idiots for allowing Heartbleed pdimitar ↗ Same with me. Worst part is, I actually worked with some of these smug insufferable nerds. Nothing on this Earth can convince them that they can make a mistake. Literally worst possible people to have as colleagues. So glad I don't work with them anymore. user-the-name ↗ Me. It's me. stackbutterflow ↗ A few years ago I've read a comment on reddit of someone saying that they don't write tests because they don't write bug. JoeyJoJoJr ↗ Reminds me of a comment where someone said they don’t need static type checking because they have never have type related bugs. mindslight ↗ I know a few of them. They're robots. For example https://www.call-cc.org/ pdimitar ↗ Very well put! Thank you, I am stealing some of your quotes. :) strictfp ↗ This line of thinking is just an extension of everyday thinking of far too many people. jeltz ↗ I do not get your example. The same applied to bridges, cars, airplanes, trains, space crafts, hydro electric plants, skyscrapers, ... pdimitar ↗ Not the best possible analogy but I get your point. The problem with OpenSSL and Heartbleed was there weren't even procedures in case of sh_t hitting the fan, whereas with nuclear reactors that's often the case. gunfighthacksaw ↗ The trick is to make ‘time before the next anomaly’ large enough to allow future generations a fair crack at solving/preventing it. inglor_cz ↗ "Some people seem to legitimately think that its possible to eliminate all sources of errors in such an immensely complex system." beaconstudios ↗ yes this, good design not only includes means of avoiding failure, but means of failing safely. Jtsummers ↗ People working in systems safety work on (at least) two levels: TheMonarchist ↗ > buffer overflows and underflows are literally the No. 1 cause of security problems when working with C oriolid ↗ The usual and not completely unfounded reasons are
- Dependency on huge C++ runtime library and possible ABI problems. Twice so for embedded targets.
- Careless use of C++ features can lead to large binaries and unexpected runtime costs.
- Lack of judgement and template metaprogramming or even plain polymorphism can lead to code that's really difficult to maintain.
- Bad experience with pre-C++11 versions of the language or Windows C++ APIs. TheMonarchist ↗ > Dependency on huge C++ runtime library and possible ABI problems. oriolid ↗ There are many modern things that run software, are connected to internet even if they shouldn't be and are tiny by modern standards. Just because something runs on servers doesn't mean that it doesn't run on processors that are just enough to run a minimal version of Linux kernel. And, have you ever worked with newbie C++ developers? When it comes to bad judgement, C++ gives a lot more rope and especially rope that's C developers are not familiar with. TheMonarchist ↗ > Just because something runs on servers doesn't mean that it doesn't run on processors that are just enough to run a minimal version of Linux kernel. oriolid ↗ You're trying to convince the wrong person. Linus Torvalds is the one you should start from. pdimitar ↗ I'd guess a lot of C projects nowadays have very conservative management (most of which might even be actual programmers) and they could operate under "it works good enough, don't you dare touch it!". wglb ↗ Heartbleed was not a memory safety issue. marcosdumay ↗ A buffer overflow isn't a memory safety issue? wglb ↗ A buffer overflow is a simple coding issue, not a C-style memory safetey error. beaconstudios ↗ yes it was - it caused the server to read past the end of a ping message and into unrelated memory. wglb ↗ See how this could have just as well been done in Rust: https://tonyarcieri.com/would-rust-have-prevented-heartbleed... beaconstudios ↗ I'm sure it could, but it was still a memory issue. Rust can have those too. rambambram ↗ I knew to expect this before I clicked the comments section for this post, haha. cle ↗ Sadly this is the case for most topics on HN. Most discussions involve people talking past each other and beating dead horses. Not that it’s unique to HN. pdimitar ↗ Sometimes it's worth beating the dead horse. I would love it if I can eventually shout the "you-just-need-discipline" people to extinction. They should never win this debate. We must be humble and accept it's in our nature to make mistakes and that we should improve and move the area forward, not constantly scream at each other in favour of something impossible like "well just be 100% disciplined 100% of the time, duh". AnimalMuppet ↗ > I would love it if I can eventually shout the "you-just-need-discipline" people to extinction. pdimitar ↗ Ah, I thought that was a given. You definitely should learn a lot of discipline when you're a junior dev. You should develop eye for details and what feels right or wrong. That takes time and experience and can't be skipped. rambambram ↗ I don't know if it's sad. Realizing this is not going to change, but one can change one's own outlook on it. Currently, I use it as a filter: if I see this in a discussion, I know there's nothing for me to learn. And people show their 'true' colors, when they bite on these easy and emotional pseudo-discussions. So I have a free filter that works really well, and I don't have to do anything for it. :) jgilias ↗ I for one enjoy the plurality of opinions! I much rather live in a society where people argue and don't agree on things, than in a society where there is one 'correct' opinion on everything. Even if I'm on a particular bandwagon, I don't necessarily want everyone else on it. If my bandwagon is actually heading for the cliffs, it's going to be the people not on it who may happen to focus my attention to the imminent crash so to speak. jt2190 ↗ But in either case, the arguments are “bandwagons” because they are context-free. Without context there can’t be a reasonable discussion of merits and trade offs, nor exploration of the bounds of the solution. Just parties talking past each other, never learning a thing. atoav ↗ You also just need to be disciplined to calculate the structural integrity of a bridge in your head, yet civil engineers learned the hard way that bridges collapse independently of how genius they thought they were. kaba0 ↗ I’m absolutely in support of various compile time enforced constraints on languages, but do not forget that the field we are in has unbounded complexity. So after a certain range they have diminishing returns. (Eg. types are a trivial property that can be checked through arbitrary code bases, but race conditions and a litany of other bugs are not. Introducing harder and harder concepts to try to claim a tiny formal verification will quickly become diminishing returns at the price of a much complex language z) pdimitar ↗ I don't disagree. The problem is that people stop with the better practices (like statically strongly typed languages) and increased coverage/branch testing veeeeeeeeery long before they hit the diminishing returns. That's the crux of the issue most of the times: "eh, good enough". TheMonarchist ↗ > I want to be able to say "I want to open a huge file with scatter-gather mechanics and with these 3 functions piped that do mapping and reduction". pdimitar ↗ Very far from it. I get what you're saying but it's not even close IMO. selestify ↗ > Various people and organizations are doing it here and there and to me they are the beacons of our future hope. pdimitar ↗ You can join me in a mourning session because when I stumbled upon them years ago I never took the effort of so much even writing down their names so now it's a blank stare from me, sorry... :( cle ↗ I would consider coding these things by hand in the following non-exhaustive cases: pdimitar ↗ RE: your second point, it can easily be covered by a declarative DSL with a few conditions (in macros / code generating constructs). tialaramex ↗ Race conditions aren't preventable as a general phenomenon, they're something real in our world, if you put the cat out the front door and then walk to the kitchen and close the kitchen door, the cat may meanwhile run around the building and back in through the kitchen door, so, don't do that. kaba0 ↗ I think we are not in disagreement - I am absolutely for static types and even for some notion of ownerships a la Rust, but for example dependent types may not be worth the extra mental overhead/arguings with the compiler. ReleaseCandidat ↗ Depends what you're used to.
For me dependent types (like in Coq) are a way more natural way of thinking than Rust's affine types. marcosdumay ↗ Are you sure dependent types don't make your programs simpler instead of more complex? A more powerful type system making programs simpler isn't anything new. posix86 ↗ Not yet, right? This is a topic of active research. analog31 ↗ Indeed, and it points to why there are multiple languages, because features that eliminate one class of mistakes can make it harder to eliminate another class. For instance, a mistake might be a spelling error in an identifier, or it might be a program that does completely the wrong thing because it was too hard to read, or that does nothing because it took too long to write. pdimitar ↗ Yeah, that's very true. Try and do FP in Java or C++ or PHP or Python and see where it gets you. jeltz ↗ In my experience the issue with doing e.g. FP in Python is that even if your own code is fine you probably want to use libraries which are not FP which will create a pretty jarring missmatch. pdimitar ↗ Yep, exactly that. Plus it's very likely everybody else in your companies is not writing FP Python so you'll be peer-pressured to stop because it confuses your colleagues during code reviews. deepsun ↗ > I like languages that push you to think about X or Y so you can't miss it. AmpsterMan ↗ I have such a weird relationship with checked exceptions in Java. I like checked exceptions because it makes exceptions part of the API. However, much of the time it's super intrusive and try-catch can be clunky. At this point I'm convinced there is no good solution from a language level for exceptions/errors. They just suck. valenterry ↗ The solution is already there. It's a combination of subtyping, union types, effect-handling and syntactic sugar for effect handling. asddubs ↗ oh I don't think doing functional programming in PHP is all difficult/frictiony. C++ there's probably a bit more friction but if you're on a modern version it's also not too bad. Tozen ↗ This is a valid point. The culture of various languages forces their favorite paradigm. In the case of many class-based OOP languages (or those that have strongly adopted it), programmers can be publicly shamed and ridiculed for not writing class-based OOP libraries and programs. Even just using simpler objects can be frowned upon because its not using classes, even if it can be considered valid OOP too. Const-me ↗ That might be true for perl, but I don’t believe that’s true for programming languages in general. tyingq ↗ >Perl shepherds you into using regexps...Note that there is nothing about Perl that forces you to do this. It provides all the tools needed to do the right thing. wazoox ↗ It actually has this in a more powerful form: you'd use /^./ or /.$/ of course, and if you want more explicit writing, you can use Regexp::English that provides the kind of functions you like (see https://metacpan.org/pod/Regexp::English ). tyingq ↗ Yeah, I was answering this from the article: wazoox ↗ But I don't want functions such as start_with() or startWith()! I'd much rather use regexes, which are more powerful and more expressive, than a mish-mash of tests like
I much prefer
Perl doesn't have these functions because it has better, more powerful tools. It's akin to saying that Python lacks line numbers as GW-BASIC did... tyingq ↗ They are more powerful and expressive, but are also less efficient and significantly slower for simple things like starts/ends_with(). [deleted] ↗ (comment deleted) wazoox ↗ This is a canonical case of premature optimisation. I have performance problems with the database backend, the network, disk access, but too slow a regex is a completely non-existing problem in real-life for me. Or else I wouldn't be using Perl/PHP/Python/Ruby but C/C++/Rust/Java etc. tyingq ↗ Maybe. Batch processing huge text files is pretty common in Perl. AnimalMuppet ↗ What's the cause, and what's the effect? I reach for Perl when I have that kind of a problem, because Perl makes it easier than anything else I've ever seen. Does Perl push me to program that way? Or is it the kind of problem that I program in Perl that pushes me to program that way? tyingq ↗ That's true, though Perl was around when there weren't a whole lot of choices in languages too. deepsun ↗ I wounder whether Java is usually used for extra-large projects just because it shepherds developers into more-less readable way of doing things. DerArzt ↗ > but on average Java projects stay more readable, than, say, Python gavinray ↗ In Java, everything is a class. throwaway984393 ↗ "Perl has several XML parsers available and they are presumably good at their jobs (I have never actually used one so I wouldn't know). [...] This is a clear case of shepherding. Text manipulation in Perl is easy. Importing, calling and using an XML parser is not." meijer ↗ Among other things, F# shepherds you into avoiding cyclic dependencies which is really nice both for readibility and proper module design. kerblang ↗ Do not be fooled into thinking, "The language made me do it" when the culture that surrounds the language made you do it. Different technologies have cultures brought on by often self-appointed leadership figures, even anonymous ones. Those leadership figures can popularize abuse by convincing others that it's a well-established cultural norm, simply because we tend to loathe non-conformity. Jtsummers ↗ Don't be fooled into thinking it's either one or the other. In reality, most of these kinds of things are a combination of social and "physical" systems. "physical" because while programming languages aren't technically physical entities, they still have their own kind of physics and paths of least resistance that people will tend to follow. throwaway894345 ↗ 100%. [deleted] ↗ (comment deleted) AmpsterMan ↗ I wonder to what extent these problems have to do with OOP being poorly defined vs poorly understood vs poorly implemented. throwaway894345 ↗ By the way, if anyone is curious what a really terrible constructor looks like: blandflakes ↗ I'm not really an OOP or Python defender, and generally agree that this type of constructor is inscrutable, frustrating, and quite common in the Python ecosystem. lamontcg ↗ "Don't do work in the constructor" is an OOP principle that must be decades old. Tozen ↗ This is why I like how various newer languages such as Go (golang) and V (vlang) or prototype languages like JavaScript or Lua have approached OOP. V even allows to easily choose if you want it on the stack or heap. With these languages, there is either no class-based OOP or classes are optional. "Freedom of choice", is way more obvious. marcosdumay ↗ What good is a good language if you can't learn anything from other people, use any common library, or collaborate with anybody without bad practices contaminating your work? kerblang ↗ Pragmatically speaking, yes, and this is the most important thing: The language eventually becomes a lost cause, regardless of where the finger of blame points. Worst of all, programming language evolution runs in circles as people invent & seek out regressive anti-languages in reaction to the misapprehension of blame. l0b0 ↗ Some examples of common cultural (that is, completely avoidable) antipatterns: spicybright ↗ These are amazing points, #2 being one of the biggest peeves. TheMonarchist ↗ > terminating statements with semicolon ozfive ↗ This needs to stay the top comment here as there are many truths in this comment. hosh ↗ “The medium is the message”. https://youtu.be/ImaH51F4HBw
I fell into the rabbit hole of HTML+.js+Applets and just couldn't do anything else. I made my first HTTP server that I ran on the university account without anyone noticing! :) I found Karl Hornells javaonthebrain.com (little did I know he was sitting in the building next to me):
You don't need to code Java in a stupid way, here is how you do it like C without cache misses: http://www.javaonthebrain.com/java/iceblox/iceblox.java
"Java does not shepherd you into using classes and objects, it pretty much mandates them." - Well it mandates ONE class, you do you!
In other words, you've missed the whole point of the article. Just because you can do it, doesn't mean you'll end up doing it.
Combine that with peak computer hardware performance/efficiency and you have a time bomb that will leave nobody out of harm!
Java can do atomic parallel things on multiple cores you are hard pressed to build a VM with GC to get working in C.
WASM tried only VM and Go tried only GC, both failed compared to Java on the server.
Servers that crash are not an option.
So? It's not like the electricity bill is a major concern for 99% of companies with servers. It's usually less than what they pay to stock the fridge with water bottles...
Right now not a single company is making any profit, the margin calls are filling with debt at a rate that nobody can comprehend.
Just a question of time, it's inevitable, what goes up must come down.
I'm pretty sure this is not factually correct.
Also, not related to server electricity costs.
Oh, I wish. For example, in C you can easily return multiple values through pointers. In Java, you have no such luxury without allocating so it's even less flexible if you can't allocate.
I'd say that Java has similar semantics to C. One can easily pass object references to a function that sets their data. That looks and behaves almost exactly like C. The fact that the references are allocated on the heap is an implementation detail.
Let me be clear, a pointer to a struct on the stack in C is obviously not the same as a reference to an object allocated on the heap in Java. The point is that from the point of view of what the language induces you to do, Java and C are similar. Anyone that has used older libraries in Java can attest to this; they look and feel remarkably like C.
And to keep the discourse focused on the interesting things, let us exclude "into Microsofts hands / into Azure cloud".
Then the whole WinDev vs DevDiv politics seem to always influence what sheppards happen to take care of the herd, and the way decisions are taken.
Meanwhile C# seems to be turning into the C++/CLI replacement while sucking the life out of F# and VB.NET.
While I enjoy many of the latest .NET improvements, I occasionally wonder where they are going now.
Lots of them blatantly encourage there users to "copy&paste" instead of using functions and structures.
And if they try to encourage good behavior, the sales people start to bugger the developers to "make it simpler" and the result is again a 9000 lines long papyrus of copy & paste.
However just like in text based languages, there are always those that write functions bodies that take several screens.
Overly simplified IMHO, this was probably true for K&R C, but in more recent C versions (e.g. in the last two decades or so), passing structs around by value is fine and often preferable.
(in general I agree with the article though, the shepherding side effect of programming languages is probably more important than the syntax and feature set)
Few languages pass structs/objects by value nowadays, at least not as a default action, because it is rarely what you want. Usually, a reference is what you want. In fact, if C++ wasn't designed as a superset of C, passing by reference probably would have been the default, like in most other languages.
C shepherds you into manipulating data via pointers simply because pointers are what you are using for the all important task of referencing objects.
On the other hand, C sometimes nudges you to pass by pointer where semantically you want a copy. Well, Matrix4x4 is the only example I can think of right now actually, and here I usually just bite the bullet and pass a copy, which might be a little wasteful but whatever (copying 64 bytes is not the end of the world, and the code might actually be inlined). And here, C++'s const-references can be an improvement to the situation.
Person A: "Programming language X is bad, code written in it is unreadable and horrible."
Person B: "No it's not. You can write good code in X, you just have to be disciplined."
What really ticks me off about those kinds of people is that they are extremely likely to just have been blind about their own mistakes when coding C, while loudly claiming that "you just need to be disciplined".
This is the most baffling part for me. I'm yet to figure out who these mythical non-idiots are that would not have fallen for any of the pitfalls that have led to exploits in the last several decades.
Transformation according to logical invariants is basically the goal of compilation. Eschewing compilation creates a treadmill whereby a programmer thinks they're getting a lot done because they're mechanistically spitting out a large volume of code, but really they're acting as an imperfect compiler.
See also the large crowd of people writing JVM assembly (aka "Java"), as opposed to using higher level languages like Clojure or Scala. Yes JVM is leaps and bounds ahead of C, just like C is leaps and bounds ahead of machine assembler. But unless you're often writing things that existing compilers cannot, then personally standing in for a compiler is mostly just a way of introducing bugs.
Take nuclear reactors as an example. Some people seem to legitimately think that its possible to eliminate all sources of errors in such an immensely complex system. If there's been an accident, the analysis is generally "we have to add sections in the manual for this specific scenario and we'll be safe", rather than accepting that it's impossible to prevent all errors and that it's merely a question of time before the next anomaly arises.
When building complex things anomalies will happen which means we should follow good engineering practices and add extra safety systems.
In programming, and especially systems programming, people love to pretend they are infallible, to the detriment of everyone suffering from permanent firmware bugs and critical security infrastructure leaking their secrets.
That mostly describes amateurs. Designers of modern reactors generally think along the "let us minimize possible negative effects of a failure" lines.
So, no positive void coefficient (a horrible feature of RBMK in Chernobyl), addition of core catchers [0] etc.
[0] https://en.wikipedia.org/wiki/Core_catcher
1. Physical
2. Social
On the physical side they try to establish controls that prevent or mitigate issues or minimize the consequences of them. For instance, a modern reactor (probably, not my domain) is going to be designed in a way that certain meltdown situations can't occur (physically designed to separate reactor materials at certain thresholds, resulting in an automatic shutdown versus manual) or are properly contained (smaller reactors where a runaway reaction is more easily contained with modern materials at a reasonable cost).
On the social side there is training, regulatory controls, and an emphasis on safety culture/discipline. Discipline doesn't scale, but you can't totally avoid having it. This would be things like not turning off an alarm because it disrupts your nap (looking at you, Homer J. Simpson), but actually responding to it to determine its legitimacy (related concept: normalization of deviance). They'd also want to look at it from an HCI perspective and minimize false alarms and overwhelming operators with alarms or information that seem to be at the same level of importance, but aren't. So true critical issues are brought to the front as high priority, but less critical issues (ones that should be addressed but are lower priority) won't overwhelm the operator. (I've worked with others on this wrt avionics systems, alarm fatigue is real so you have to be choosy in which things get audible alarms and which get visual alarms.)
> If there's been an accident, the analysis is generally "we have to add sections in the manual for this specific scenario and we'll be safe",
In my experience, that's a belief held by the prideful and the novices.
> rather than accepting that it's impossible to prevent all errors and that it's merely a question of time before the next anomaly arises.
Anyone with a modicum of humility or experience will accept this and act accordingly.
Why don't these ancient projects just move to C++ for vectors and strings, that do the buffer handling correctly for you? Surely they can run a C++ compiler on any workstation these days?
C++ runtime is hardly huge by modern standards, and the usual resolution to ABI issues is to provide C bindings.
> Careless use of C++ -- Lack of judgement --
C doesn't make you immune to any of this. What are buffer overruns if not careless use of C?
Edit: Considering that I suggested moving to C++ for vectors and strings, your bringing up of TMP and polymorphism sounds like a strawman argument.
Maybe, but it seems unreasonable to hold the world back because of those systems.
> When it comes to bad judgement, C++ gives a lot more rope and especially rope that's C developers are not familiar with.
I think one could just ban most of that rope to junior devs. "Don't try to roll your own classes or templates, for getting them right requires both skill and discipline." Also common guidelines ban rope that isn't needed but for implementing the superior alternatives, that are usually found in the standard library.
You can still program in a procedural way and use just the low hanging fruits of the STL, that was designed for correctness above all, for Alex Stepanov made it to demonstrate his ideas of generic programming: C++ just happened to make implementing it possible.
Mind you, I am of the firm belief that C is at fault for many of the security vulnerabilities, but this is not one of them.
You shouldn't. They're partly right.
You are correct that "just be 100% disciplined 100% of the time" will fail. But "just get a language/compiler smart enough that I don't need discipline" will also fail.
Speaking from the point of view of a senior dev however, certain practices become ultra tedious and annoying from one point and on. Literal soul crushers.
Using design systems (languages) that eliminate entire classes of mistakes, because they make them impossible is the rational choice. It is human to make mistakes, but how we learn from the past to prevent mistakes in the future is where you can tell between engineers who truly care and those who are in it for their own ego.
Additionally, I really want us to stop pretending we can program CPUs directly (and I include the higher-level languages here as well, not just machine code and assembly). We can't. We need more declarative languages, e.g. I want to be able to say "I want to open a huge file with scatter-gather mechanics and with these 3 functions piped that do mapping and reduction". Why should we always code this by hand? There are objective best ways to produce this code in most languages. Let's have a DSL library that allows us to express that intent and be done with it and move on to be productive on another task.
This is not done enough to this day. Various people and organizations are doing it here and there and to me they are the beacons of our future hope. Everybody else is just banging at their Emacs / VIM in the meantime.
IME C++ templates and generic programming help with that aim.
Who are these people and organizations?
Example: in Elixir you have the so-called streaming functions you can pipe to each other that can do e.g. filtering, mapping, chunking and what not. But they don't actually operate on the data you passed them; they generate further functions and produce chains that only start working when you put a certain command at the end of the command pipe.
This works really well if you work with a collection of 100_000 elements. Since Elixir is an FP language, you don't want to copy this huge list, say, 7 times one after another (assuming your transformation pipe has 7 stages). By using the streaming functions you only use the original list and produce a final list. Just one roundtrip to the memory.
But... it has a performance impact and it's definitely not worth doing if your collections are e.g. less than 1000 elements, maybe even 2000.
A declarative DSL can easily cover for such cases and have separate code branches addressing them.
RE: tinkering, obviously. I am not saying normal programming should be "banned". But it's about time we have higher-order programming, man. We're all collectively slacking off on that and should be really ashamed!
The rest of your points I don't see how to automate and that's OK. Like any tool, declarative programming should be used sparingly and only where it truly applies.
However data races (a small but important subset of race conditions in concurrent software) are preventable, and so you can use languages which do so. Unlike the cat race condition I described above, data races are often unfathomable in real systems, the ultimate cause of truly mysterious and hard to reproduce bugs. So they're very much worth preventing.
Anyway, while it is true that "unbounded complexity" is potentially necessary, tools capable of such power should not be the default. When I spread jam on toast, I don't use the butcher's cleaver, I use a butter knife that's too blunt to cut flesh let alone bone. Which means, if I screw up the worst case is maybe I get jam on the carpet instead of the toast, not I cut my own hand off.
That difference in tooling can exist both within a language, and also between languages. There are many programs you cannot write in WUFFS, whereas there are (almost) no programs you can't write in C++. But that actually means we should use WUFFS when we can, because we don't want those programs. The image viewer where PNG files with long rectangular orange areas cause the hard disk to get reformatted, easily possible in C++ and not WUFFS. We don't want that program. The icon maker where a JPEG text comment with a shell script in it executes the script, easily possible in C++, not in WUFFS. Don't want that either.
I personally haven't seen them simply anything yet, but your assumption that verifying things with a dependent type would necessarily result in more complexity than a non-verified program is not trivial and needs support.
And before I get a ton of "ackchyally you can do X" guys, yes you technically can but there's friction and you are implicitly not encouraged to do so.
I like languages that push you to think about X or Y so you can't miss it. Case in point: strong static types in many languages, or lifetimes/ownership in Rust.
Yet most modern languages steer away from checked exceptions, that you cannot miss. And I agree with them, catching UrlMalformedException from a hardcoded static url is unnecessary.
But most importantly, checked exceptions just don't play well with FP, that's why they are getting pushed off the ship.
checked exceptions would be fine id they were not so clunky.
This is why, unless forced or pressured to on a professional level, prefer dabbling in those languages where there is no classes or its truly an option, and not the main (forced) paradigm. Prefer less stress, hidden spaghetti code (pretending it's not), bullying, or shaming. Just way more enjoyable to write in languages where the paradigm that you choose is because it makes sense to solve the problem, not because "that's just what we do around here".
> C++ shepherds you into providing dependencies as header-only libraries.
There’re many C++ libraries provided as DLLs too, like QT. Just because the standard library is mostly header-only, doesn’t mean that’s always a good idea for other libraries.
> Java does not shepherd you into using classes and objects, it pretty much mandates them.
Maybe, but modern C#, despite similar to Java in many regards, doesn’t promote any particular coding style. Classes and objects are working fine, but so is FP or procedural. And there’re decent libraries, both Microsoft’s and third party, implementing reactive and actor approaches.
It does have index() and rindex(), but doesn't have a begins_with(), ends_with(), and other string functions that Python, Ruby, PHP, java, etc, have. You can roll your own, but there are arguably things missing.
The thing is that all of these functions are completely superfluous, because a Perlist will use regexp which are much more compact and allow writing in a nice, functional way (map and grep FTW). In fact, in most languages, manipulation of strings is so cumbersome that I go back to Perl almost every time, because a regexp is almost always the most compact, fast and readable solution.
Also frankly I never heard of anyone trying to parse XML or HTML with regexps this century for anything but a quick throwaway one-liner, so the example is actually quite poor.
"Note that there is nothing about Perl that forces you to [use regexes]...It provides all the tools needed"
I was saying that Perl does sort of force you down the regex path, because things like starts_with() aren't there. You're basically confirming that.
Also, it varies by search string size, version and machine type, but at least for me, index() is significantly faster in many situations...2 to 3x faster.
Benchmark index("abc","ab")==0 vs "abc" =~ /^ab/
So that even multi-year hundred-people dozen-companies project still at least runs.
Of course, I've seen with my own eyes that even that could be broken with enough courage (seen a company ignoring all NPEs in main()), but on average Java projects stay more readable, than, say, Python.
Can we say that is due to shepherding or rather that Java lays it's types bare for you any time you create or pass a variable? You can do this in python with type annotations, but since they aren't required a lot of dev's just don't. This leads to the code being harder to read in my opinion.
Java didn't even have generics when it was released. Any sort of higher-order functions and support for anonymous function weren't introduced until Java 8 in 2014.
Language debuted 1995, generics in 2004 (almost a decade!), and lambda/FP facilities in another 20 years.
In Java, a public class has to have the same name as the file as well.
You had a very constrained design space historically and some non-negotiable enforced conventions.
It's similar to Go code, where most of it more-or-less looks the same and you can pick up most codebases and run with them if you're familiar with the language.
The people writing those lame scripts have the same logic the author does. "Must be hard, probably? So I won't even try to learn, I'll just use this regex"
A Shepherd's job is to keep the flock safe. If the blind are leading the blind, that is a lack of shepherding. It's not shepherding users to use regex. It's letting the sheep roam free and they're falling off the cliff.
Perl's problem has always been that it is too easy to use. 97% of people who write Perl aren't even programmers. It doesn't shepherd you, it lets you write programs even when you don't know what you're doing.
We should require drivers licenses in order to program. Programmers think they are capable even when they're not, and their false confidence and laziness leads to shitty code that causes problems. Same for people who try to drive without passing a driver's test. It just causes harm.
We should set legal engineering requirements for code just like for other trades. Otherwise some idiot is going to assume XML parsers are super annoying without even looking at them and try to use regex.
Third-party frameworks are often used to establish one's self as a leader and establish abusive practices. Once the foothold is firm, it becomes literally "The framework made me do it." Cultural norms mandate the framework, and the framework forces the abuse. If anyone protests about the consequences of abuse, blame is redirected back at the language.
To any onlooker, the consensus seems clear: Your language sucks! Those same onlookers are happy to reinforce this perception because they are busy trying to establish competing cultures & leaders of their own. "You should try my language!" Of course their primary goal is not to help, but to improve their own status by rising the tide and lifting their own boat. If they wanted to really help, they would work on establishing better culture instead of jockeying for popular culture.
Recently I've been frustrated with the Python ecosystem and how many "object oriented" libraries there are which can't easily be tested because of all of the weird shit their constructors do (most recently, the class I'd like to test just builds JSON documents and sends them to an API, but the constructor hard-codes the HTTP client so you have to use magic to swap the hard-coded client with a mock).
Of course, someone will come riding in to defend OOP with "that's not true OOP" and another will come riding in to defend Python with "you can write bad code in any language!", both of which miss the point: this sort of thing is much more prevalent in the Python ecosystem than, say, the Go ecosystem or the Rust ecosystem precisely due to culture. And by the way, I'm sure there are lots of other languages that have a culture of poor programming practices--the intent here isn't to pick on Python (I've used it professionally for 15 years).
https://github.com/jupyterhub/kubespawner/blob/81e6059a14811...
^ I'm also curious to hear from people whether this kind of design is "OOP" or not, or if there are any good terms for talking about this particular antipattern.
That said, I would not consider default mock values in a constructor to be good OOP. In general, "new"ing things in a constructor IMO is a no-go and makes for harder testing and confusing behavior.
At the end of the day I'm not even sure why this class supports mock behavior. I'd expect two separate classes with divorced implementations, instead of an if statement on a boolean param.
The problem with Python and Ruby is that being dynamic it makes them real easy to do highly lightweight OOP. There's just a cultural disconnect in the OO world where once you get past objects wrapping data the design patterns tend to come from the Java world and quickly descend into explaining dependency injection containers that nobody uses in Python or Ruby. There's a bit of a cultural middle ground which is not well defined due to the history of how it all evolved.
Maybe a "shout-out" for Object Pascal in this regard too, as they have advanced records (structs with methods) and Free Pascal has simpler objects (but Delphi's dialect doesn't). Though I will say, Object Pascal's implementation of class-based OOP is a gentler version, versus the "harsher" implementations in other languages. Still, as with all the class-based OOP languages that I've seen, you are going to get forced feeding and forced to use plenty of OOP libraries.
Forcing or pushing class-based OOP is debatably how things can more easily and unnecessarily go bananas. Complexity traded for simplicity. Users are usually trapped in the paradigm, until they like it (programming version of Stockholm syndrome?), and are often scared to go outside of it or think the world might end if they do.
They are different phenomena, but every practical consequence is the same. Including what you must do to avoid the problems.
- LISP dialect devs insisting on using incredibly obscure technical abbreviations for important names, like car/cadr/cdr instead of first/second/rest or something else human readable.
- POSIX-ish shell standardizers and builders making it really difficult to deal with anything but ASCII alphanumeric filenames. Saying "You shouldn't put special characters in filenames!" isn't helpful if I'm trying to make a script to support other people's filenames.
- C-style language makers' insistence on terminating statements with semicolon, so that a tiny fraction of multi-line statements can be shortened to one line.
- Python devs treating time-zone-less datetimes as not an atrocity, for example in utcnow.
- Nix, well, being generally incredibly hard to read and learn, even after about 20 other languages. I hope somebody goes ham on that language and creates a thoroughly backwards incompatible implementation of the fantastic ideas behind it.
I guess I've pissed off the entire internet now, but remember that these are my gripes, not some absolute truths I'm trying to convince y'all of.
Line wrapping would be unacceptable without this.
McCluhan applied his thinking to radio and television, but that idea is applicable to computer software and language platforms.