Not the person who wrote that reply but their reply immediately clicked for me.
Here in Europe basically everyone uses WhatsApp. So you might as well just place a WhatsApp status update writing something like "We're doing X this afternoon? Wanna join? We'll leave from [location] at [time.]" and basically everybody in your contacts can see that update.
It's not perfect (different privacy model, a lot more people can see the update unless you have a very curated contacts list) but no friction of getting people to install another app.
Edit: Also no notification for status update, so slightly different model that could solve the same need.
Ah, yes, that makes sense! I think that's a great solution.
I think perhaps one sort of semantic/slight difference is that Holler is hyper-local. If you live in LA and your friend lives in London, they obviously aren't going to the local farmer's market with you. I'd say the vast majority of my WhatsApp friends aren't local (I'm based in the States.)
BUT I can totally see that being an easy way to do things if your local friend group is on WhatsApp!
One of my questions would be whether anyone (strangers?) can see that or not. And again this is a US-centric concern, but one of the things I think a lot about is ICE and the government tracking non-white people, and so if there's any semblance of a public "I'll be at X place at Y time," then I'd rather not make that public. So Holler solves that.
But yeah, that's a creative use case for WhatsApp I hadn't considered! Very neat. Thank you for explaining.
I do recognize your use case and think it is a better solution than a WhatsApp status update. However (at least in my friend group) the friction of getting people to install yet another app is also very high.
Having said that, once there's an Android version too I'm definitely going to try if I can get a bunch of friends to try it.
Misery is trying to retrofit "bool", True/False, and nil/null to a language. C had to do that. Python had to do that. Getting those wrong is one of the classic language design mistakes. It seems like treating "True" as a value that equates to 1 will work, but then the special cases get you. Like being able to perform arithmetic on True.
Common language design boners:
- Not building in strings. That's now in the past. Everybody has strings. (Well, C...)
- Not building in multidimensional arrays of the numeric types. Everything that number-crunches needs them, and having multiple definitions is Not Fun and may lead to expensive re-copying between different libraries.
This is an enormous blind spot in language design. It's one of the reasons FORTRAN, which has good multidimensional numeric arrays, is still often used for number-crunching.
- Not standardizing the small vectors (vec2, vec3, vec4) and their matrix friends. Graphics code depends on these, and it's really annoying if there are multiple slightly incompatible implementations. Especially since GPUs have hardware for those types, and you want CPU and GPU to use the same representations.
- Not having arrays of bits. Pascal had PACKED ARRAY[0..N] of BOOLEAN but that was lost in later languages. It's useful to have that as a language construct, because most modern CPUs have good hardware for dealing with bit strings, and you'd like the compiler to use it.
Most useful languages acquire these features, but, when they come in late, there are multiple similar implementations, and libraries made incompatible by depending on different implementations.
(Amusingly, when Second Life switched from Linden Scripting Language to Luau, they initially had True, TRUE, and true all in use, as different types with different semantics. I was able to persuade the devs to unify the boolean types.)
You list a few absences but absences aren't the end of the world, I say it's worse when designers make a booboo where the language semantics are wrong. In C++ there are so many of these it's not sporting but a recurring example from the garbage collected languages would be the for-each loop mistake.
Several times now†, people make a language where the way a for-each loop (for each Goose in Geese ...) works is that there's a single variable Goose and each time around the loop we change which value is referred to by the Goose variable. This seems intuitively like a reasonable way to do this. But it's wrong and eventually your programmers will get nasty surprises. What you actually should deliver is an implementation where each time around the loop there's a new variable named Goose, that variable goes away at the end of that iteration and will be replaced by the next one, with the same exact name.
> What you actually should deliver is an implementation where each time around the loop there's a new variable named Goose, that variable goes away at the end of that iteration and will be replaced by the next one, with the same exact name.
Is this because a closure inside a loop will capture a reference to `Goose`?
I think that this is a capture problem not a variable problem. The closure should always do the right thing and capture the value of all variables (not just ones inside the loop), instead of capturing the reference to the variables.
Then the general problem is fixed to match what developers expect, instead of a specific instance of that class of problems being fixed and working differently to how other captured variables work.
The interior of a for-loop is only a scope, not a closure. In most non-dynamic languages you can't package up the state and hold onto it beyond the life of the loop, which is what closures are for.
Most trouble in this area came from the iteration variable outliving the loop. That's not good when the iteration variable is a pointer. In C, it often is, and at the end of the loop, it points to an invalid address. It was a change to C (when?) to make the iteration variable go out of scope before code after the loop could get at it.
> I think that this is a capture problem not a variable problem. The closure should always do the right thing and capture the value of all variables (not just ones inside the loop), instead of capturing the reference to the variables.
Now your "lalanthran closures" can't mutate the world because they work exclusively with copies not references, if they try to mutate something then whatever they're touching was just a copy not the real thing.
> Now your "lalanthran closures" can't mutate the world because they work exclusively with copies not references, if they try to mutate something then whatever they're touching was just a copy not the real thing.
That is true. I still don't like the idea of "Here is a general rule. It applies everywhere but $HERE." Whether that general rule is "All captures are by value" or "All captures are by reference", the rule should not have exceptions based on context in the code.
A better tradeoff would be to have the general rule (whatever it is) apply everywhere, along with syntax for capturing (or not, depending what the default is). I'd rather have it grab everything by value, and for those things that are susceptible to race conditions (because more than one closure is modifying it), explicitly annotate it with a sigil (`&`, or a keyword, or similar).
I mean, in pseudocode, when I see:
... variables x, y and z are declared and used in this scope ...
return (x, y, x) => { ... }
I don't want to have to examine the surrounding scope to know whether or not `y` is susceptible to a race. I'd rather just see:
... variables x, y and z are declared and used in this scope ...
return (x, &y, x) => { ... }
An alternative viewpoint is that many languages have immutable variables and they seem to be getting along just fine without needing mutation on variables, shared or otherwise.
> the rule should not have exceptions based on context in the code.
But the rules didn't and still don't have any such exceptions.
> A better tradeoff would be to have the general rule (whatever it is) apply everywhere, along with syntax for capturing (or not, depending what the default is)
This "solution" is how it works in C++. We can thus castigate the programmer for writing the wrong runes in their captures list and never for a moment doubt that we got it right when we introduced so very many footguns...
Tony Hoare's observation applies "One way is to make the program so simple, there are obviously no errors. The other is to make it so complicated, there are no obvious errors."
> An alternative viewpoint is that many languages have immutable variables and they seem to be getting along just fine without needing mutation on variables, shared or otherwise.
Sure, and one of the astonishing things in C# or Go before they fixed this is that you can indeed have immutable variables which change, even though that's silly - the language can decide that you mustn't change Goose, but it doesn't need to obey its own rules because it will change it for each loop iteration.
> Not having arrays of bits. Pascal had PACKED ARRAY[0..N] of BOOLEAN but that was lost in later languages. It's useful to have that as a language construct, because most modern CPUs have good hardware for dealing with bit strings, and you'd like the compiler to use it.
I'm not sure exactly which features are responsible (I'm inclined to blame templates), but C++'s std::vector<bool> is a rough edge. For those unfamiliar, the standard specifies this vector template in a way that's not compatible with other vectors.
They should have made `std::bitset<std::dynamic_extent>` what todays `std::vector<bool>` is (actually maybe not, `std::bitset` is fixed sized, just compile time fixed size). While at it also make `std::array<std::dynamic_extent>` a runtime fixed size array.
Vectors & multidimesional arrays are something I'm 100% adding to my language's core.
It kind of started with vectors as the very first feature (I was sick & tired of libraries reinventing their own `Point`/`VectorN` in incompatible ways).
Oh wow, I had no idea that SL did another language change after migrating LSL to Mono. Surprising considering that happened late '00s/early '10s?
I'd consider LSL to have been foundational in my ultimate interest/career in software engineering. The strict typing, very usable compile/runtime errors, and good documentation/examples made it so easy to pick up as a teen. Not to mention as long as you didn't edit/save a script again it would always run the same regardless of updates.
Strings are a really weird data type. I'm not sure you can do much better than C strings without implicitly requiring dynamic memory allocation, which C deliberately does not do.
Definitely agree on multidimensional arrays. I feel like efficient arrays in general are underrated in high-level language design.
> Strings are a really weird data type. I'm not sure you can do much better than C strings without implicitly requiring dynamic memory allocation, which C deliberately does not do.
The thing you want is what Rust delivers in the box, &str a string slice reference type, in Rust's case the "string" is UTF-8 encoded text. On the bare metal the way to represent this type is as a "fat pointer" typically a pair of registers, one with the address of the first byte of the string and the other with a length.
C should have fat pointers, they were proposed, for IIRC C89 but the proposal was rejected. That's pretty sad, the fat pointer is expensive to the point of maybe feeling extravagant on a PDP-11, but by 1989 that's long gone.
More ridiculously C++ didn't get this type (which it eventually called std::string_view and provides in its standard library not as a built-in) until 2017, years after Rust 1.0 shipped. In the meanwhile C++ just did not have a sensible way to do this, strings are hard apparently.
The string buffer feature, allowing you to actually make strings is less important, as you say it will need an allocator and so on very bare metal you might not have this - but the string slice reference doesn't need an allocator.
I think it's worth delivering the basic "it's a growable array type, duh" implemenation of the string buffer type, which is what Rust's String type is, but C++ chooses to ship an oddly specific small-string optimized version as std::string right from the offset.
Hmm. Having read your post, surely the bug is having a function where
set(foo, false)
removes foo entirely. What if you want foo to have the value False? Even besides the unintended behaviour where 0 is coerced to a boolean value, this function seems poorly designed.
Yes I remember a friend doing university marking for a beginners programming course years ago and some student had managed to swap True and False making their assignment very wonky.
I stopped my 1Password subscription last year and started using Apple Passwords. The user experience is great if you switch to Safari with fingerprint login.
When visiting Ayers Rock in Australia I stayed in Alice Springs. While I was there I learnt that Alice Springs exists because it was a repeater station for a telegraph line that stretched from Southern Australia all the way to London. There would be people listening to morse code, and tapping it out again to the next repeater station. Blew my mind that there was a wire that went all the way to London from Australia!
In the late-1700s/early-1800s the Admiralty Telegraph was used to relay messages between London and Portsmouth (70 odd miles apart) using a semaphore type system with repeater stations every 10 miles or so.
Yes, the Uk (southern England in particular) is dotted with "Semaphore Hill"s or "Telegraph Hills"s. There's one very close to where I'm sitting now, a few miles NE of Portsmouth.
In Tasmania, you can still see at least one semaphore station on Mt Nelson, which is above several suburbs on the south of the city of Hobart. I believe there was a semaphore route from the capital to Port Arthur (convict prison) and possibly other routes over the state too.
To think it was done even 1000s of years prior to that with just smoke and fire! Granted, the ability to communicate through the rain would be a necessity for the British.
My home country the Netherlands became a republic after a long war with the Spanish that controlled the territory from Spain after having inherited it via various wars and conflicts that divided up the remains of the Carolian empires. The Austrians ended up with a lot of states across what is now Germany and Belgium. France emerged as well as a country.
The Netherlands was too far away from the courts in Spain for them to govern effectively. Travel time was measured in weeks. So, remote regions like that necessarily had a large degree of autonomy. That became the basis for power to centralize around Amsterdam as it was favorably located for for trading. There were a lot of grievances with religious issues (Catholicism vs. Protestantism), taxation, etc. But the Spanish failure to project power from a distance had everything to do with the centralized nature of their empire and long communication channels.
In the so called golden century (17th century), the Netherlands got filthy rich on global trade and expansion. Information and knowledge flowed to and from Amsterdam from all over the world.
The Dutch naval forces dominated the North Sea for quite some time and it's only later that the British emerged as the better/bigger empire. Navies and ships were the fastest way to move information around at the time. Until the British finally upgraded to cables and telegrams which enabled them to have colonies on all continents. They really nailed command and control across their empire for a while.
The Romans had their roads to move armies and information. Shipping and navigation technology leveled that up from the 1400s or so. These days, low latency communication is a commodity of course.
Bitcoin doesn't take into account that humans are forgetful.
If you forget your bank card pin code they would post you a new card and pin, if you phone them up. I've done this a couple times in the past pre-smartphone banks.
If you forget your bitcoin password you are never getting access back.
Sorry, this is going to be a slightly longer reply since this is a really interesting question to ask!
Elixir (and anything that runs on the BEAM) takes an entirely different perspective on concurrency than almost everything else out there. It still has concurrency gotchas, but at worst they result in logic bugs, not violations of the memory model.
Stuff like:
- forgetting to update a state return value in a genserver
- reusing an old conn value and/or not using the latest conn value in Plug/Phoenix
- in ETS, making the assumption nothing else writes to your key after doing a read (I wrote a library to do this safely with compare-and-swap: https://github.com/ckampfe/cas)
- same as the ETS example, but in a process: but doing a write after doing a read and assuming nothing else has altered the process state in the interim
- leaking processes (and things like sockets/ports), either by not supervising them, monitoring them, or forgetting to shut them down, etc. This can lead to things like OOMs, etc.
- deadlocking processes by getting them into a state where they each expect a reply from the other process (OTP timeouts fix this, try to always use OTP)
- logical race conditions in a genserver init callback, where the process performs some action in the init that cannot complete until the init has returned, but the init has not returned yet, so you end up with a race or an invalid state
- your classic resource exhaustion issues, where you have a ton of processes attempting to use some resource and that resource not being designed to be accessed by 1,000,000 things concurrently
- OOMing the VM by overfilling the mailbox of a process that can't process messages fast enough
Elixir doesn't really have locks in the same sense as a C-like language, so you don't really have lock lifetime issues, and Elixir datastructures cannot be modified at all (you can only return new, updated instances of them) so you can't modify them concurrently. Elixir has closures that can capture values from their environment, but since all values in Elixir are immutable, the closure can't modify values that it closes over.
Elixir really is designed for this stuff down to its core, and (in my opinion) it's evident how much better Elixir's design is for this problem space than Go's is if you spend an hour with each. The tradeoff Elixir makes is that Elixir isn't really what I'd call a general purpose language. It's not amazing for CLIs, not amazing for number crunching code, not amazing for throughput-bound problems. But it is a tremendous fit for the stuff most of us are doing: web services, job pipelines, etc. Basically anything where the primary interface is a network boundary.
reply