FWIW, this is addressed in the article and the paper it covers: the slowdown involved reduced "proactive policing" -- "systematic and aggressive enforcement of low-level violations" -- but the observed drop is in major crimes like burglary and assault. The authors also do some work to control for the overall crime reporting rate.
I'm a bit confused... Everyone is assuming that the police wouldn't respond to calls during this period, but the article doesn't suggest that this was the case.
Even looking through articles in 2014 and 2015 doesn't provide me with any evidence that police weren't showing up to crime scenes... just that they weren't doing the "proactive policing" thing.
Worth mentioning that, while the `git bisect` algorithm is pretty easy to run by hand if you have a linear history, `bisect` also handles very complex branching and merging histories as well. Very clever and worth using!
The issue being pointed out here isn't that the computation is out of sync with the outside world... it's that it's our of sync with _itself_! It will return answers that are not just stale, but inaccurate for any time in history.
This might still be fine, depending on your needs, but IMO a legitimate distinction.
Based on that description of Pijul, it does seem to be a semilattice: it has a merge operation that's associative and commutative, and while it doesn't explicitly state that it's idempotent it's hard to imagine merging two states and getting anything else back than the same state.
I am one of the authors of Pijul. It is indeed a semilattice, but not exactly "commutative": the only operations that commute are operations that could be done in parallel. For example, adding a file doesn't commute with editing it.
But unlike in Darcs, where conflicts are messy and not super well-defined in all cases (and not to mention, sometimes cause Darcs to run for a time exponential in the size of the repo's history), two conflicting edits always commute in Pijul.
The main points of Pijul were (1) to solve the soundness issues with conflicts and (2) to fix the exponential problems.
Of course, there really is something special about 0, 1, and Inf -- they're the kind of numbers that tend to arise from theory, not just from tuning. Tuning is hugely important for real systems, so an allergy to 'arbitrary' numbers is not useful; on the other hand, this rule is often a nice heuristic to keep in mind when you're picking which numbers to expose in your config files.
Also, people do use this stuff to think about their computer systems, so I'm a little confused at the author's point of instinct and intuition. Most engineers are trained in analysis of the infinite (calculus), which also includes rigorous analysis of 0 and 1.
It's worth noting that green threads are not 'enough' -- in any language with concurrency, you still need to be explicit about what work can run concurrently and what needs to be sequential. In Go, for example, the default is sequential operation; if you want to run a bunch of code concurrently, you need to do extra work to launch a bunch of new goroutines / wait for everyone to finish. In Haskell, which also (typically) has green threads, there's a popular `async` package that supplies a promise-like construct -- it's quite useful when you want to be explicit about concurrency and ordering.
Let me just clarify one point: lightweight[1] threads are not an abstraction; threads are. Lightweight threads are simply threads whose implementation is such that blocking has no (or negligible) overhead. When you have threads, you're better off using blocking queues and blocking futures, which are the "pull" duals of the "push" mechanisms described in the article.
[1]: I don't like the term green threads because traditionally it's been used to describe threads that do not support parallelism.
It's an odd definition of personal privilege you have, where the privileged person suffers two years of harassment and abuse, and the person without privilege immediately returns to a lucrative career.
It's also an odd definition of professional victim, where publicly being a 'victim' results in you getting booted out of the profession.
I don't want to comment on the event itself here -- but I think that the aftermath, and the continued reaction to it, say a lot of sad things about the tech community.
>"Why she was booted from her profession was because she misused her position,"
And if you take that at face-value, it's ironically, the very thing that feminists are supposedly fighting against in terms of sexism in the workplace.
- Some arbitrary type of thing. (Lists, sets, strings, numbers, whatever.) Let's call it 'A'.
- A value of type A. Let's call it the 'empty' value.
- A function that takes two A values as arguments, and returns a single one. Let's call it 'combine' function.
Here are some examples of monoids:
- String concatenation: 'combine' is string concatenation, and 'empty' is the empty string.
- Integer plus: 'combine' is the usual + operation, and 'empty' is the value 0.
- Set union: 'combine' is the union of two sets, and 'empty' is the empty set.
You can come up with monoids for all sorts of things: maps, lists, functions, bloom filters, futures / promises, ...
There are, however, a couple rules:
- It doesn't matter which order you combine things in: `("hello" + "world") + "!"` has the same result as `"hello " + ("world" + "!")`; and `1 + (2 + 3)` has the same result as `(1 + 2) + 3`.
- You can add the 'empty' value to either end without changing anything. `"" + "hello"` is the same as `"hello" + ""` and plain old `"hello"`. Likewise, `3 + 0 == 0 + 3 == 3`.
That's it! To get an intuition, you might want to pick a couple more types from the list above and pick a good 'empty' / 'combine' function that satisfies the rules.
Of course, why you'd want to do this is another question. (And one I'd be happy to answer, if you're curious.)
This is probably the clearest explanation of what a monoid is that I've seen. Basically, a monoid is a reduction function that takes multiple inputs of type A and returns a single output of type A. The only caveats on top are that type A must support the concept of an empty value, combining the empty value with x results in x, and that combinations are associative.
- Any monoid operation is trivially parallelizable: take the dataset, split it into chunks, combine all the elements in each chunk together, then combine those results together as a final step.
- If I'm updating a row in my database with a monoid operation, I can always 'pre-aggregate' a batch of values together on the client side before taking that result and combining it with the stored value.
- If I store some statistics for every day of the year, I can calculate the monthly statistics very cheaply -- as long as those stats are a monoid.
The monoid abstraction seems weird at first, because it's so dang general, but it ends up hitting a bit of a sweet spot: the rules are just strong enough to be useful for a bunch of things, but simple enough that be applied to all kinds of different situations. You can think of it kind of like a hub-and-spokes thing -- this interface connects all kinds of different data types to all kinds of different cool situations, so you get a lot of functionality with a lot less typing and thinking.
The introduction of stream processing in Java 8 is a concrete example - it can auto-parallelize computations. It was not immediately clear to me why an identity value was needed in this function:
A recent post, was about "How to Build Your Distributed Database" (1).
The word 'monoid' is not used (and the word 'commutative' is even misused for 'associative')
but this post shows well why monoids matter and how they can be used.
Thank you for the great explenation; I read the article,a nd if it only had taken it out of the pure abstract and given the int example; it would have been 100% more relatable. I always get the feeling that they fear that giving an actual example would hurt the article and that the pure abstract is to be preferred.