> Local-first was the first kind of app. Way up into the 2000s, you'd use your local excel/word/etc, and the sync mechanism was calling your file annual_accounts_final_v3_amend_v5_final(3).xls
To be precise, these apps where not local-_first_, they where local-_only_. Local-first implies that the app first and foremost works locally, but also that it, secondly, is capable of working online and non-locally (usually with some syncing mechanism).
I never understood why people are so keen to do that in TypeScript. With that definition a `UserID` can still be silently "coerced" to a `string` everywhere. So you only get halfway there to an encapsulated type.
I think it's a much better idea to do:
type UserID = { readonly __tag: unique symbol }
Now clients of `UserID` no longer knows anything about the representation. Like with the original approach you need a bit of casting, but that can be neatly encapsulated as it would be in the original approach anyway.
Yes, absolutely. I did programming competitions back in high-school (around 10 years ago) and common folklore was that back in the days knowing dynamic programming could win you a medal, but today it was just basic expected knowledge.
There's a lot of variety in DP. Knowing about DP doesn't help much with solving DP problems. I'm sure you've seen all the fun problems on codeforces or leetcode hards. The one quote I remember from Erik Demaine's lecture on DP is where he comments, "How do we solve this with DP ? - With difficulty."
I think it's because access to knowledge has become a lot easier, so contestants know more; plus, the competitions themselves have become more organized.
For example, last I checked, the IOI had a syllabus outlining the various algorithms contestants needed to know. During my time, it was more of a wild west.
> I guess Google’s years of experience led to the conclusion that, for software development to scale, a simple type system, GC, and wicked fast compilation speed are more important than raw runtime throughput and semantic correctness.
I'm a fan of Go, but I don't think it's the product of some awesome collective Google wisdom and experience. Had it been, I think they'd have come to the conclusion that statically eliminating null pointer exceptions was a worthwhile endeavor, just to mention one thing. Instead, I think it's just the product of some people at Google making a language they way they wanted to.
And indeed they did come to that conclusion - for Dart 2.
Go is the product of like 3 Googlers' tastes. It isn't some perfect answer born out of the experience of thousands of geniuses.
I think they got a lot right - fantastic tooling, avoiding glibc, auto-formatting, tabs, even the "no functional programming so you have to write simple code" thing is definitely a valid position. But I don't think anyone can seriously argue that Go's handling of null is anything but a huge mistake.
But those people at Google were veteran researchers who wanted to make a language that could scale for Google's use cases; these things are well documented.
For example, Ken Thompson has said his job at Google was just to find things he could make better.
They also built a language that can be learned in a weekend (well, now two) and is small enough for a fresh grad hire to learn at the job.
Go has a very low barrier to entry, but also a relatively low ceiling. The proliferation of codegen tools for Go is a testament of its limited expressive power.
It doesn't mean that Go didn't hit a sweet spot. For certain tasks, it very much did.
> A quick Google search with "flutter setstate is not refreshing" reveals a struggle that you will face quite often when running Flutter. It sounds like an easy fix, but the nature of Flutter using a bunch of nested Widgets creates, naturally, lasagna code that makes it hard to reason about this.
Can you expand on this OP? I've never had problems with `setState` nor "lasagna code" in Flutter. From a quick search I mostly seem to find questions from people who are still learning Flutter and getting basic things wrong.
State management in flutter can be done in so many ways so I get why it could feel complex, we’ve used Hooks a lot and it simplified a ton of stuff. Can also go the whole BLOC route too. Having issues with setState means you’re doing something that’s an anti-pattern
Yeah I think the point is that you have to become a state management expert in Flutter... even if the end result is not very complex there are so many options and so many pitfalls you still have to do a ton of thinking and learning to get there.
With egui you pretty much don't have to think about it at all.
How often do you break your phone that you've save sooo much? Mine is at least 2 years older (I got it 2 years before the Fairphone 4 was released) and I've spend 0$ dollars repairing it.
RAID is not backup, but in some circumstances it's better than a backup. If you don't have RAID and your disk dies you need to replace it ASAP and you've lost all changes since your last backup. If you have RAID you just replace the disk and suffer 0 data loss.
That being said, the reason why I'm afraid of not using RAID is data integrity. What happens when the single HDD/SSD in your system is near its end of life? Can it be trusted to fail cleanly or might it return corrupted data (which then propagates to your backup)? I don't know and I'd be happy to be convinced that it's never an issue nowadays. But I do know that with a btrfs or zfs RAID and the checksuming done by these file systems you don't have to trust the specific consumer-grade disk in some random laptop, but instead can rely on data integrity being ensured by the FS.
You should not propagate changes to your backup in a way that overwrites previous versions. Otherwise a ransomware attack will also destroy your backup. Your server should be allowed to only append the data for new versions without deleting old versions.
Also, if you're paranoid avout drive behavior, run ZFS. It will detect such problems and surface it at the OS level (ref "Zebras All The Way Down" by Bryan Cantrill)
I was a bit confused by the remark that comptime is referentially transparent. I'm familiar with the term as it's used in functional programming to mean that an expression can be replaced by its value (stemming from it having no side-effects). However, from a quick search I found an old related comment by you [1] that clarified this for me.
If I understand correctly you're using the term in a different (perhaps more correct/original?) sense where it roughly means that two expressions with the same meaning/denotation can be substituted for each other without changing the meaning/denotation of the surrounding program. This property is broken by macros. A macro in Rust, for instance, can distinguish between `1 + 1` and `2`. The comptime system in Zig in contrast does not break this property as it only allows one to inspect values and not un-evaluated ASTs.
Yes, I am using the term more correctly (or at least more generally), although the way it's used in functional programming is a special case. A referentially transparent term is one whose sub-terms can be replaced by their references without changing the reference of the term as a whole. A functional programming language is simply one where all references are values or "objects" in the programming language itself.
The expression `i++` in C is not a value in C (although it is a "value" in some semantic descriptions of C), yet a C expression that contains `i++` and cannot distinguish between `i++` and any other C operation that increments i by 1, is referentially transparent, which is pretty much all C expressions except for those involving C macros.
Macros are not referentially transparent because they can distinguish between, say, a variable whose name is `foo` and is equal to 3 and a variable whose name is `bar` and is equal to 3. In other words, their outcome may differ not just by what is being referenced (3) but also by how it's referenced (`foo` or `bar`), hence they're referentially opaque.
Those are equivalent, I think. If you can replace an expression by its value, any two expressions with the same value are indistinguishable (and conversely a value is an expression which is its own value).
This is honestly really cool! I've heard praises about Zig's comptime without really understanding what makes it tick. It initially sounds like Rust's constant evaluation which is not particularly capable. The ability to have types represented as values at compilation time, and _only_ at compile time, is clearly very powerful. It approximates dynamic languages or run-time reflection without any of the run-time overhead and without opening the Pandora's box that is full blown macros as in Lisp or Rust's procedural macros.
> Wait, if I recall correctly, covariance has long been established as a mistake.
Perhaps you're just missing some words here, but, just for clarity: it doesn't make any sense to say that covariance is a mistake. Covariance applied in specific places, like Java's mutable covariant arrays which leads to unsoundness, can be a mistake, but covariance itself in fine and essential in languages with subtyping. Function parameters should be covariant, function returns should be contravariant, mutable data structures should be invariant, etc.
> I'm not very familiar with these relations, but shouldn't function returns be covariant? `String => Cat` is a subtype of `String => Animal`?
You're right :) I mixed up covariance and contravariance for function parameters and return value in my comment.
> For function parameters, doesn't it depend on how the parameter is used?
I don't think so, but maybe there's specific circumstances I don't know of? Function parameter types is a constraint on _input_ to the function. Changing that to a subtype amounts to the function receiving arguments that satisfies a stronger constraint. That seems that something that would hold no matter how the parameter is used?
> > For function parameters, doesn't it depend on how the parameter is used?
> I don't think so, but maybe there's specific circumstances I don't know of?
I don't know specific circumstances either, but I presume they exist because of things like Dart's `covariant` keyword [0], which makes function parameters covariant instead of contravariant.
Copy(adest, asrc); // No! - How would Copy know how to copy `A` values when it only knows about `B`?
Copy(adest, bsrc); // No! - src argument is OK, but how can it downcast them to `A`?
Copy(adest, csrc); // No! - Same as above, and src elements must be at least `B`s`.
Copy(bdest, asrc); // Ok. - Any `A` in the src are interpreted as `B`s.
Copy(bdest, bsrc); // Ok, - all values are interpreted as `B`s.
Copy(bdest, csrc); // No! - Argument elements must be at least `B`s`.
Copy(cdest, asrc); // Ok - values are interpreted as `B`s in src, and as `C`s in dest.
Copy(cdest, bsrc); // Ok
Copy(cdest, csrc); // No! Argument elements must be at least `B`s`.
If the `dest` argument were contravariant, it would permit invalid copies and forbid valid ones.
Maybe this is intentionally introducing logical unsoundness into Dart's type system for pragmatic purposes, perhaps supplemented with some implicit dynamic checks?
However, outside of the function, when calling it, the opposite is true.
let result = foo (arg)
;; String <: arg
;; result <: Animal
It's easy to get them mixed up when looking at it from the wrong perspective - but we should be looking at functions from the outside, not the inside - so yes, parameters should be contravariant and return types covariant.
To be precise, these apps where not local-_first_, they where local-_only_. Local-first implies that the app first and foremost works locally, but also that it, secondly, is capable of working online and non-locally (usually with some syncing mechanism).