Shared posts

08 Jul 16:39

Production of software may continue to be craft based

by Derek Jones

Andrew Carnegie made his fortune in the steel industry, and his autobiography is a fascinating insight into the scientific vs. craft/folklore approach to smelting iron ore. Carnegie measured the processes involved in smelting; he tracked the input and outputs involved in the smelting process, and applied the newly available scientific knowledge (e.g., chemistry) to minimize the resources needed to extract iron from ore. Other companies continued to treat Iron smelting as a suck-it-and-see activity, driven by personal opinion and the application of techniques that had worked in the past.

The technique of using what-worked-last-time can be a successful strategy when the variability of the inputs is low. In the case of smelting Iron there was a lot of variability in the Iron ore, Limestone and Coke fed into the furnaces. The smelting companies in Carnegie’s day ‘solved’ this input variability problem by restricting their purchase of raw materials to mines that delivered material that worked last time.

Hiring an experienced chemist (the only smelting company to do so), Carnegie found out that the quality of ore (i.e., percentage Iron content) in some mines with a high reputation was much lower than the ore quality of some mines with a low reputation; Carnegie was able to obtain a low price for high quality ore because other companies did not appreciate its characteristics (and shunned using it). Other companies were unable to extract Iron from high quality ore because they stuck to using a process that worked for lower quality ore (the amount of Limestone and Coke added to the smelting process has to be adjusted based on the Iron content of the ore, otherwise the process may deliver poor results, or even fail to produce Iron; see chapter 13).

When Carnegie’s application of scientific knowledge, and his competitors’ opinion driven production, is combined with being a good businessman, it’s no surprise that Carnegie made a fortune from his Iron smelting business.

What are the parallels between iron smelting in Carnegie’s day and the software industry?

An obvious parallel is the industry dominance of opinion driven processes. But then, the lack of any scientific basis for the processes involved in building software systems would seem to make drawing parallels a pointless exercise.

Let’s assume that there was a scientific basis for some of the major processes involved in software engineering. Would any of these science-based processes be adopted?

The reason for using science based knowledge and mechanization is to reduce costs, which may lead to increased profits or just staying in business (in a Red Queen’s race).

Agriculture is an example of a business where science and mechanization dominate, and building construction is a domain where this has not happened. Perhaps building construction will become more mechanized when unknown missing components become available (mechanization was available for agricultural processes in the 1700s, but they did not spread for a century or two, e.g., threshing machines).

It’s possible to find parallels between software engineering and the smelting process, agriculture, and building construction. In fact, it parallels can probably be found between software engineering and any other major business domain.

Drawing parallels between software engineering and other major business domains creates a sense of familiarity. In practice, software is unlike most existing business domains in that software products are one-off creations of an intangible good, which has (virtually) zero cost of reproduction, while the economics of creating tangible goods (e.g., by smelting, sowing and reaping, or building houses) is all about reducing the far from zero cost of reproduction.

Perhaps the main take-away from the history of the production of tangible goods is that the scientific method has not always supplanted the craft approach to production.

08 Jul 16:38

The scientists hired by big oil who predicted the climate crisis long ago | Environment

mkalus shared this story from The Guardian.

As early as 1958, the oil industry was hiring scientists and engineers to research the role that burning fossil fuels plays in global warming. The goal at the time was to help the major oil conglomerates understand how changes in the Earth’s atmosphere may affect the industry – and their bottom line. But what top executives gained was an early preview of the climate crisis, decades before the issue reached public consciousness.

What those scientists discovered – and what the oil companies did with that information – is at the heart of two dozen lawsuits attempting to hold the fossil fuel industry responsible for their role in climate change. Many of those cases hinge on the industry’s own internal documents that show how, 40 years ago, researchers predicted the rising global temperatures with stunning accuracy. But looking back, many of those same scientists say they were hardly whistleblowers out to take down big oil.

Some researchers later testified before Congress, using their insider knowledge to highlight the ways in which the oil industry misled the public. Others say they have few qualms with how the petroleum giants handled their research.

Few, however, could have predicted the imprint their work would have on history in efforts to hold the fossil fuel industry accountable for our climate emergency. The Guardian tracked down three of those scientists to see how they view their role today.

Dr Martin Hoffert, 83, physicist and Exxon consultant from 1981 to 1987

When I started consulting for Exxon, I had already begun to understand that the Earth’s climate would be affected by carbon dioxide. There were only a small number of people in the world who were actively working on this problem because the global warming signal had not yet manifested itself in the data. So I was invited to join a research group at Exxon and one of my conditions to join was that we would publish our scientific research in peer-reviewed journals. It was a bunch of geeks trying to figure out how the planetary atmosphere works.

We were doing very good work at Exxon. We had eight scientific papers published in peer-reviewed journals, including a prediction of how much global warming from carbon dioxide buildup would be 40 years later. We made a prediction in 1980 of what the atmospheric warming would be from fossil fuel burning in 2020. We predicted that it would be about one degree celsius. And it is about one degree celsius.

It never actually occurred to me that this was going to become a political problem. I thought: “We’ll do the analyses, we’ll write reports, the politicians of the world will see the reports and they’ll make the appropriate changes and transform our energy system somehow.” I’m a research scientist. In my field, if you discover something and it turns out to be valid, you’re a hero. I didn’t realize how hard it would be to convince people, even when they saw objective evidence of this happening.

Back in 1980, there was a guy working for Exxon and he was one of the inventors of the lithium battery, which electric cars now use. This guy won the Nobel prize in chemistry for his work on lithium batteries. Just imagine if Exxon management had taken our prediction seriously! They could have easily built huge factories to make lithium batteries to facilitate the transition to electric cars. Instead, they fired this guy. They shut down all their energy work. And they started funding climate deniers.

Very often people will ask me: “How much time do we have left before we can prevent this problem?” We don’t have any time left. It’s already happening.

Ken Croasdale, 82, researcher and engineer at Imperial Oil from 1968 to 1992

When I was working for Imperial Oil in the late 1980s, I was heading up a small group responsible for the research and development that we were doing in relation to the Arctic. My specialty was in building offshore structures in the Arctic region. In the early 90s, I did an assessment: if we did have temperatures rise in the Arctic, what might we expect in terms of ice conditions and how would those changes influence how we operate?

I was looking specifically at offshore operations. When we look at engineering structures, we’re interested in how thick the ice is. One of the issues was: how much thinner might the ice be in a warming world? How would that affect how we design our platforms?

Climate research wasn’t a big deal for the company, at that time. There was a lot of uncertainty, so people would shrug their shoulders a little bit. You’d say, “you need to look at this,” and they’d say “maybe we do, maybe we don’t.” It wasn’t looming big as an issue at that time.

My personal view is that climate change is occurring. But the primary driver is population and consumption. When my grandfather was born, the world population was about 1.3 billion. When I was born it was 2.2 billion, and today it is 7.5 billion. The UN predicts a population of about 10 billion by 2055. In my opinion this is the primary driver of everything relating to our worsening environment.

I personally don’t have any discomfort having worked for the oil companies. All the people I worked with were just as honest and ethical as people I’ve worked with in other organizations. I don’t feel like I’m helping the “evil empire” – I don’t feel any shame. I’m just helping a company that produces a product that is still massively consumed worldwide.

Steve Lonergan, 71, Exxon consultant from 1989 to 1990

I was involved in research on the social and economic impacts of climate change on Canada’s north in the late 1980s and early 1990s. At the time, there weren’t a lot of people doing this kind of work. Exxon Canada asked me if I would provide an assessment of how this would affect their operations in the north.

The models were regional at best, and could only provide general projections under different levels of carbon dioxide, or CO2. This was a technical group, and I have no idea whether they had any influence on Exxon’s senior management. There were a few engineers who were concerned about the issue of global warming. Whether they spoke up is another question.

Most of the scientists at the time accepted that these types of changes in CO2 emissions were going to affect temperature and precipitation. The public did not, of course, and the industries did not, and the governments generally did not. But most of the scientific community was close to unanimous. It was nothing really new to any of us.

At that time, the models were very general, but they did give you a sense that the farther north you go, the greater the warming is going to be. And the main reason for that is that the ice will melt. The question was, “What does it mean in terms of permafrost? What does it mean for ice breakup?”

My partner and I were interested in looking not just at average temperature or average precipitation, but the variability, the extremes. We started trying to figure out how we could model extremes in temperature and precipitation. This is important for the north because there are communities where their refrigeration is just a crate outside in the winter. So you can put reindeer meat in it and it freezes naturally.

But if you get extremes with above-freezing temperatures in January, that poses a problem for food supply. We did some modeling and our conclusion was that if CO2 levels doubled, the probability was 50% that on any given day in January, a place that was normally -32 degrees would actually get above freezing.

Six or seven years later, every day for two weeks was above freezing, and all the reindeer meat thawed out. I didn’t expect it would happen that quickly. That was the biggest shock.

For a long time, I wasn’t a member of the Sierra Club or the Western Canadian Wilderness Society and so on, because I wanted to be seen as an objective observer. I wanted to be seen as somebody whose advocacy was through their research. Climate change is a very important environmental issue and so we need good research behind it.

We have people like Greta Thunberg, and we absolutely need them. But we also need the scientific community to show the evidence for some of the changes that were happening. That’s the role I felt I played.

This story is published as part of Covering Climate Now, a global collaboration of news outlets strengthening coverage of the climate story

08 Jul 16:37

A Reckoning With Our Inhumanity

by Dave Pollard


child prisoners of the Kamloops Roman Catholic Indian Residential School, which operated 1890-1960; 215 children’s bodies have been found through ground-penetrating radar photographs on the grounds of this “school” alone; photo by Library and Archives Canada

My friend PS Pirro recently wrote a post called ‘Hiding the Dead’, cataloguing some of the stories of how our species, with official sanction and impunity, has regularly murdered, and anonymously and unceremoniously buried, its most powerless — notably indigenous peoples, “slaves”, “minorities”, police detainees, prisoners of war, inmates of “detention centres” and other institutions, the poor and sick, and women and children.

The recent discovery, using new ground-penetrating radar technology, of the unmarked remains of thousands of young children torn from their homes and forced to live in brutal conditions in religious brainwashing “residential schools” —  their bones found right beneath the “school” grounds — is just the latest example.

We are also, seemingly, taking the first, wobbly steps towards reckoning with the legacies of slavery, apartheid, caste-ism in all its variations, and genocide, and the long-ignored questions of reparations, and “truth and reconciliation” are now being asked more openly, with long-suppressed outrage.

I wrote in response to Peggy’s post:

Perhaps the big surprise to me is not that these atrocities were happening and we never knew (because we didn’t want to know, didn’t want to believe it was true), but rather that now, when everything around us is in free fall, we are for some reason suddenly willing to start to acknowledge that they happened. The interesting question to me is “Why now?”

As extreme weather events, pandemics, wildfires, infrastructure collapses, proxy wars over land and resources, water shortages, and inequality all continue to accelerate towards inevitable economic, ecological and civilizational collapse, this would seem an odd time to be reflecting upon past and continuing injustices.

But is it? Is our global civilization culture now recognizing, and sensing instinctively, that these are end times for our culture if not our species, and beginning a kind of death-bed repentance for what we have done that has led to the point we’re now at?

Perhaps it’s just wishful thinking on my part, but my sense is that this is what is happening — a reckoning with our inhumanity as our tyrannical reign draws to a close.

In The Waste Land, Eliot writes: “Shall I at least set my lands in order? | London Bridge is falling down falling down falling down”, an allusion to the biblical proclamation “Set thine house in order: for thou shalt die, and not live,” and to the song about a bridge that falls down despite all attempts to prop it up. I’ve known quite a few people (including many politicians) who have, late in life, acknowledged that while it is too late or they are now incapable of correcting their errors, they admit quite remarkably and ruefully to their folly.

And yes, it is mostly too late to undo, mitigate or make amends for these egregious behaviours. The damage is done and cannot be undone. We will be hard put to make use of what remains of our shattering civilization’s remains* to prevent extreme hardship to the 7.8 billion people completely dependent on our vulnerable global systems for their survival, let alone atone financially to those who have been oppressed in past and continue to be so today.

But that doesn’t mean we can’t reckon with it, and admit to it. We can still tell the truth, admit our culpability, and genuinely grieve for the victims (human and more-than-human) of our ill-advised, run-amok civilization — even including ourselves in that list of victims to some extent. To admit culpability is not to admit intention to do harm, regardless of what the $^%# lawyers might tell you. “We were really doing what we thought was best”, is not an excuse, but it is an explanation.

We can at least attempt to understand why we were so misguided as to believe our atrocious behaviour was “best”, and convey that to its victims. We can at least listen to what that behaviour has caused for its victims, and acknowledge its truth, and attempt to learn from it. What matters most is the listening, the acknowledgement, and the learning.

And, while short of making reparations, there are opportunities for restoration — to return some of the lands and property that were egregiously stolen to their owners and their heirs. That is the least we can do.

A death-bed repentance is not necessarily a plea for forgiveness or even understanding. It may even be a completely selfish act. But once we give up the belief that we can and must “progress” forward, and that we dare not admit the weakness of past errors for fear of retribution, we can at least come to grips with what we’ve done more fully and honestly. There is some evidence, in the ruins of past civilizations, that such a broad-culture acknowledgement of what went wrong (perhaps in the hope future generations would not repeat the errors) has been made and documented.

I would like to see our global industrial civilization culture create such an acknowledgement, a collective, full and truthful reckoning of our mostly-unintentional folly. When our feeble and useless attempts to mitigate climate, ecological and economic collapse utterly fail in the next few decades, such an acknowledgement may be all that is left of use to those our crumbled civilization leaves behind.

At the end of her post, PS introduces her word of the day: Procrustean, named after a mythical ancient Greek abuser who stretched or amputated his victims to fit his torture bed. The word means “violently making conformable to standard, producing uniformity by deforming force or mutilation.”

PS concludes brilliantly by summing up the true meaning of this word: “We made your bed. Go lie in it”.

Xenophobia, which is now the lifeblood of conservative politicians just about everywhere, means “fear of what is strange or different”. In times of great upheaval, change happens faster and becomes strange and different more quickly. There is therefore much more for xenophobes to fear.

That is what, I think, is happening now. We are not a very resilient or naturally adaptable species. We are physically suited to a very narrow range of ecosystems and ways of living, so whenever we move outside that comfort zone, our approach is to conquer, to destroy, to terraform, to make the world and its “strangers” fit what we want, and live in a fragile, prosthetic world of our own making, rather than adapting ourselves to the world and its “strangers”. We want them to lie in our Procrustean bed.

That is perhaps why, since our species left our birthplace in the trees of the tropics, we have become so violent, war-mongering, territorial and destructive, everywhere we go and in everything we do. We desperately have to make the world “fit” to our narrow definition of what is liveable, right and good.

But I don’t think that’s our true nature. I don’t believe our slow, chisel-toothed, clawless species was ever meant to stray beyond our homes in the trees, to leave behind our bonobo-like passive, easy, lethargic lifestyle, or our ancient vegan diet (there is evidence that for our first million years, the largest single component of the human diet was — figs). Our modern human behaviours are a sudden aberration.

We are, as I keep saying, not well. So yes, we are cruel, but that is a symptom of our disease, not our genes. And this disease is rapidly burning itself, and us, out. I would like to believe that we could one day acknowledge that, and leave our reckoning with our inhumanity, with at least a trace of humility, as our epitaph.


* As an aside, I am increasingly convinced that we are quickly heading towards a war between the richest 1% (and their propagandized lackeys), and the rest of us. As our crisis deepens, it is inconceivable that the current obscene level of inequality can hold. During the Great Depression, taxes on the rich and on corporations jumped to 90% to finance the various New Deals that redistributed wealth dramatically when it was needed. We’re going to see that in the next decade or so, I am convinced. It’s not going to be easy, or pleasant. And this war has already begun.

08 Jul 16:35

There is transfer between programming and other subjects: Skills overlap, but it may not be causal

by Mark Guzdial

A 2018 paper by Ronny Scherer et al. “The cognitive benefits of learning computer programming: A meta-analysis of transfer effects” was making the rounds on Twitter. They looked at 105 studies and found that there was a measurable amount of transfer between programming and situations requiring mathematical skills and spatial reasoning. But here’s the critical bit — it may not be casual. We cannot predict that students learning programming will automatically get higher mathematics grades, for example. They make a distinction between near transfer (doing things that are very close to programming, like mathematics) and far transfer, which might include creative thinking or metacognition (e.g., planning):

Despite the increasing attention computer programming has received recently (Grover & Pea, 2013), programming skills do not transfer equally to different skills—a finding that Sala and Gobet (2017a) supported in other domains. The findings of our meta-analysis may support a similar reasoning: the more distinct the situations students are invited to transfer their skills to are from computer programming the more challenging the far transfer is. However, we notice that this evidence cannot be interpreted causally—alternative explanations for the existence of far transfer exist.

Here’s how I interpret their findings. Learning program involves learning a whole set of skills, some of which overlap with skills in other disciplines. Like, being able to evaluate an expression with variables, once you know the numeric value for those variables — you have to do that in programming and in mathematics. Those things transfer. Farther transfer depends on how much overlap there is. Certainly, you have to plan in programming, but not all of the sub-skills for the kinds of planning used in programming appear in every problem where you have to plan. The closer the problem is to programming, the more that there’s an overlap, and the more we see transfer.

This finding is like a recent paper out of Harvard (see link here) that shows that AP Calculus and AP CS both predict success in undergraduate computer science classes. Surprisingly, regular (not AP) calculus is also predictive of undergraduate CS success, but not regular CS. There are sub-skills in common between mathematics and programming, but the directionality is complicated.

We have known for a long time that we can teach programming in order to get a learning effect in other disciplines. That’s the heart of what Bootstrap does. Sharon Carver showed that many years ago. But that’s different than saying “Let’s teach programming, and see if there’s any effect in other classes.”

So yes, there is transfer between programming and other disciplines — not that it buys you much, and the effect is small. But we can no longer say that there is no transfer.

08 Jul 16:34

Twitter Favorites: [JustynTyme_] Whoa. I guess one dude quit: https://t.co/zl23f5RNo4

JustynTyme @JustynTyme_
Whoa. I guess one dude quit: pic.twitter.com/zl23f5RNo4
08 Jul 16:34

Infinidash goes viral, despite being fictional

by Rui Carmo

I have to say that this was the most fun I’ve had online in a while. Some of the discussions were hilarious, and the impromptu webcasts and Twitter Spaces featuring key tech influencers who just had to join the fun were nothing short of spectacular.

That said, it’s amazing how much disinformation a small number of highly influential geeks can generate – especially as this is still going on, nearly a week after it got started.

I blame the fact that everyone involved could relate to the key point Joe Nash was trying to make: recruiting for technical roles is a completely broken process, and too subject to whims of fancy and hype cycles.

And, to be honest, it’s not the recruiters’ fault.


08 Jul 16:31

Why you should write a strategy document — even if it’s just for yourself

by Josh Bernoff

Writing a strategy document is the best way to make sure you, your business, or your organization move forward in a thoughtful way. It’s a discipline you should follow. What is a strategy document? “What is strategy?” is a tricky question, addressed by everyone from Michael Porter to every boss on the planet. But there’s … Continued

The post Why you should write a strategy document — even if it’s just for yourself appeared first on without bullshit.

08 Jul 16:31

What OpenAI and GitHub’s “AI pair programmer” means for the software industry

Ben Dickson, TechTalks, Jul 05, 2021
Icon

There was a bit of a splash made last week with the announcement of 'your AI pair programmer'. In computer science, 'pair programming' is where two people work on one computer to program something; the idea is that they're constantly talking about what they're doing. So what if an AI plays the role of the observer? "If you know a bit about what you’re asking Copilot to code for you, and you have enough experience to clean up the code and fix the errors that it introduces, it can be very useful and save you time." There is, of course, no reason to limit this capacity to computer programming. Anyone developing any sort of content - even detailed and technical content - will need to take note.

Web: [Direct Link] [This Post]
08 Jul 16:10

Continuously Doing a Thing

by Mark Finkle

Practice makes perfect — Anonymous Parent

A theme that keeps popping up in my world is the idea of how often an action is done being correlated to how well the action is done

  • Deploying application and system code
  • Releasing application distributions
  • Triaging issues
  • Testing product behavior
  • Creating objectives
  • Running experiments
  • Executing migrations

A lot has been written about high-performing engineering teams. Accelerate is a great resource for exploring the behaviors of such teams. Frequent deploys is one of the leading indicators and was chosen by the authors as a key metric. With enough practice, deploys become low-risk and low-stress.

Small batches are another trait of successful teams. Performing a deployment more frequently usually means there are fewer changes happening each time. These small batches can actually improve overall quality because fewer changes happen in each cycle.

Rotating a large group of people through activity shifts, like handling issue triage or the application release process, allows the group to share the burden, but there are downsides too. If the activity isn’t part of the group’s primary deliverable, it’s likely not a priority. If there are long stretches of time between any given person taking on the activity there might only be enough time to just do the work, but never think about how to improve the process or tooling. There is no time to become good at the process.

The DevOps Handbook talks a lot about the benefits of shorter feedback loops across many different aspects of engineering organizations. In most situations, shorter feedback loops happen when an activity becomes a more continuous process.

If you have an area that could be improved, maybe you could ask yourself if the process could happen more often.

08 Jul 16:07

Elon Musk says self-driving tech is hard, pushes back Tesla software update again

by Brad Bennett

Elon Musk has once again taken to Twitter to tweet out a tease about full-self-driving technology coming to Tesla cars. This isn’t the first time he’s told the world that Full Self Driving Version 9 (FSD 9) is coming soon, and it likely won’t be the last.

Since 2018, the eccentric CEO has promised Tesla owners and fans that the company is on the verge of rolling out autonomous car technology and that full self-driving Version 9 will change everything. So far, he’s about three years behind schedule on his promise, and from what we’ve seen so far, full Self-Driving isn’t ready for the masses.

In his most recent tweet, Musk responds to a user wh0 named their car “Two Weeks” (a joke about how long it’s taken for the FSD software to come out). He said, “Haha, FSD 9 beta is shipping soon, I swear! Generalized self-driving is a hard problem, as it requires solving a large part of real-world AI. Didn’t expect it to be so hard, but the difficulty is obvious in retrospect. Nothing has more degrees of freedom than reality.”

To be fair, I’m glad that Telsa isn’t rushing out this tech since it is hazardous and could have serious implications, but I am getting just a tiny bit annoyed with all of the times that Elon has promised something and then reneged on it.

Source: The Verge 

The post Elon Musk says self-driving tech is hard, pushes back Tesla software update again appeared first on MobileSyrup.

07 Jul 05:37

Headless React

Cover Image

Live

It is actually pretty easy to build a mediocre headless React today, i.e. an implementation of React that isn't hooked directly into anything else.

react-reconciler is an official package that lets you hook up React to anything already. That's how both React-DOM and React-Native share a run-time.

Most third-party libraries that use it (like react-three-fiber) follow the same approach. They are basically fully wrapped affairs: each notable Three.js object (mesh, geometry, material, light, ...) will tend to have a matching node in the React tree. Three.js has its own scene tree, like the browser has a DOM, so react-reconciler will sync up the two trees one-to-one.

The libraries need to do this, because the target is a retained in-memory model. It must be mutated in-place, and then re-drawn. But what would it look like to target an imperative API directly, like say 2D Canvas?

You can't just call an imperative API directly in a React component, because the idea of React is to enable minimal updates. There is no guarantee every component that uses your imperative API will actually be re-run as part of an update. So you still need a light-weight reconciler.

Implementing your own back-end to the reconciler is a bit of work, but entirely doable. You build a simple JS DOM, and hook React into that. It doesn't even need to support any of the fancy React features, or legacy web cruft: you can stub it out with no-ops. Then you can make up any <native /> tags you like, with any JS value as a property, and have React reconcile them.

Then if you want to turn something imperative into something declarative, you can render elements with an ordinary render prop like this:

<element render={(context) => {
  context.fillStyle = "blue";
  context.drawRect(/*...*/);
}} />

This code doesn't run immediately, it just captures all the necessary information from the surrounding scope, allowing somebody else to call it. The reconciler will gather these multiple "native" elements into a shallow tree. They can then be traversed and run, to form a little ad-hoc program. In other words, it's an Effect-like model again, just with all the effects neatly arranged and reconciled ahead of time. Compared to a traditional retained library, it's a lot more lightweight. It can re-paint without having to re-render any Components in React.

You can also add synthetic events like in React-DOM. These can be forwarded with conveniences like event.stopPropagation() replicated.

I've used this with great success before. Unfortunately I can't show the results here—maybe in the future—but I do have something else that should demonstrate the same value proposition.

React works hard to synchronize its own tree with a DOM-like tree, but it's just a subset of the tree it already has. If you remove that second tree, what's left? Does that one tree still do something useful by itself?

I wagered that it would and built a version of it. It's pretty much just a straight up re-implementation of React's core pattern, from the ground up. It has some minor tweaks and a lot of omissions, but all the basics of hook-driven React are there. More importantly, it has one extra superpower: it's designed to let you easily collect lambdas. It's still an experiment, but the parts that are there seem to work fine already. It also has tests.

Yeet Reduce

As we saw, a reconciler derives all its interesting properties from its one-way data flow. It makes it so that the tree of mounted components is also the full data dependency graph.

So it seems like a supremely bad idea to break it by introducing arbitrary flow the other way. Nevertheless, it seems clear that we have two very interesting flavors just asking to be combined: expanding a tree downstream to produce nodes in a resumable way, and yielding values back upstream in order to aggregate them.

Previously I observed that trying to use a lambda in a live DFG is equivalent to potentially creating new outputs out of thin air. Changing part of a graph means it may end up having different outputs than before. The trick is then to put the data sinks higher up in the tree, instead of at the leaves. This can be done by overlaying a memoized map-reducer which is only allowed to pass things back in a stateless way.

yeet reduce

The resulting data flow graph is not in fact a two-way tree, which would be a no-no: it would have a cycle between every parent and child. Instead it is a DFG consisting of two independent copies of the same tree, one forwards, one backwards, glued together. Though in reality, the second half is incomplete, as it only needs to include edges and nodes leading back to a reducer.

chain of fibers in the forwards direction turns down and back to yield values in the backwards direction

Thus we can memoize both the normal forward pass of generating nodes and their sinks, as well as the reverse pass of yielding values back to them. It's two passes of DFG, one expanding, one contracting. It amplifies input in the first half by generating more and more nodes. But it will simultaneously install reducers as the second half to gather and compress it back into a collection or a summary.

When we memoize a call in the forward direction, we will also memoize the yield in the other direction. Similarly, when we bust a cache on the near side, we also bust the paired cache on the far side, and keep busting all the way to the end. That's why it's called Yeet Reduce. Well that and yield is a reserved keyword.

when 'yeeting' a value, you throw it into a bin on the far side and knock everything down after it

What's also not obvious is that this process can be repeated: after a reduction pass is complete, we can mount a new fiber that receives the result as input. As such, the data flow graph is not a single expansion and contraction, but rather, many of them, separated by a so-called data fence.

This style of coding is mainly suited for use near the top of an application's data dependency graph, or in a shallow sub-tree, where the number of nodes in play is typically a few dozen. When you have tons of tiny objects instead, you want to rely on data-level parallelism rather than mounting each item individually.

Horse JS

I used to think a generalized solution for memoized data flow would be something crazy and mathematical. The papers I read certainly suggested so, pushing towards the equivalent of automatic differentiation of any code. It would just work. It would not require me to explicitly call memo on and in every single Component. It should not impose weird rules banning control flow. It would certainly not work well with non-reactive code. And so on.

There seemed to be an unbridgeable gap between a DFG and a stack machine. This meant that visual, graph-based coding tools would always be inferior in their ability to elegantly capture Turing-complete programs.

Neither seems to be the case. For one, having to memoize things by hand doesn't feel wrong in the long run. A minimal recomputation doesn't necessarily mean a recomputation that is actually small and fast. It feels correct to make it legible exactly how often things will change in your code, as a substitute for the horrible state transitions of old. Caching isn't always a net plus either, so fully memoized code would just be glacial for real use cases. That's just how the memory vs CPU trade-off falls these days.

That said, declaring dependencies by hand is annoying. You need linter rules for it because even experienced engineers occasionally miss a dep. Making a transpiler do it or adding it into the language seems like a good idea, at least if you could still override it. I also find <JSX> syntax is only convenient for quickly nesting static <Components> inside other <Components>. Normal JS {object} syntax is often more concise, at least when the keys match the names. Once you put a render prop in there, JSX quickly starts looking like Lisp with a hangover.

When your Components are just resources and effects instead of widgets, it feels entirely wrong that you can't just write something like:

live (arg) => {
  let [service, store] = mount [
     Service(...),
     Store(...),
  ];
}

Without any JSX or effect-like wrappers. Here, mount would act somewhat like a reactive version of the classic new operator, with a built-in yield, except for fiber-mounted Components instead of classes.

I also have to admit to being sloppy here. The reason you can think of a React component as an Effect is because its ultimate goal is to create e.g. an HTML DOM. Whatever code you run exists, in theory, mostly to generate that DOM. If you take away that purpose, suddenly you have to be a lot more conscious of whether a piece of code can actually be skipped or not, even if it has all the same inputs as last time.

This isn't actually as simple as merely checking if a piece of code is side-effect free: when you use declarative patterns to interact with stateful code, like a transaction, it is still entirely contextual whether that transaction needs to be repeated, or would be idempotent and can be skipped. That's the downside of trying to graft statelessness onto legacy tech, which also requires some mutable water in your immutable wine.

I did look into writing a Babel parser for a JS/TS dialect, but it turns out the insides are crazy and it takes three days just to make it correctly parse live / mount with the exact same rules as async / await. That's because it's a chain of 8 classes, each monkey patching the previous one's methods, creating a flow that's impractical to trace step by step. Tower of Babel indeed. It's the perfect example to underscore this entire article series with.

It also bothers me that each React hook is actually pretty bad from a garbage collection point of view:

const memoized = useMemo(() => slow(foo), [foo]);

This will allocate both a new dependency array [foo] and a new closure () => slow(foo). Even if nothing has changed and the closure is not called. This is unavoidable if you want this to remain a one-liner JS API. An impractical workaround would be to split up and inline useMemo into into its parts which avoid all GC:

// One useMemo() call
let memoized;
{
  useMemoNext();
  useMemoPushDependency(foo);
  memoized = useMemoSameDependencies() ? useMemoValue() : slow(foo);
}

But a language with a built-in reconciler could actually be quite efficient on the assembly level. Dependencies could e.g. be stored and checked in a double buffered arrangement, alternating the read and write side.

I will say this: React has done an amazing job. It got popular because its Virtual DOM finally made HTML sane to work with again. But what it actually was in the long run, was a Trojan horse for Lisp-like thinking and a move towards Effects.

No-API

So, headless React works pretty much exactly as described. Except, without the generators, because JS generators are stateful and not rewindable/resumable. So for now I have to write my code in the promise.then(…) style instead of using a proper yield.

I tried to validate it by using WebGPU as a test case, building out a basic set of composable components. First I hid the uglier parts of the WebGPU API inside some pure wrappers (the makeFoo(...) calls below) for conciseness. Then I implemented a blinking cube like this:

export const Cube: LiveComponent<CubeProps> = memo((fiber) => (props) => {
  const {
    device, colorStates, depthStencilState,
    defs, uniforms, compileGLSL
  } = props;

  // Blink state, flips every second
  const [blink, setBlink] = useState(0);
  useResource((dispose) => {
    const timer = setInterval(() => {
      setBlink(b => 1 - b);
    }, 1000);
    dispose(() => clearInterval(timer));
  });

  // Cube vertex data
  const cube = useOne(makeCube);
  const vertexBuffers = useMemo(() =>
    makeVertexBuffers(device, cube.vertices), [device]);

  // Rendering pipeline
  const pipeline = useMemo(() => {
    const pipelineDesc: GPURenderPipelineDescriptor = {
      primitive: {
        topology: "triangle-list",
        cullMode: "back",
      },
      vertex: makeShaderStage(
        device,
        makeShader(compileGLSL(vertexShader, 'vertex')),
        {buffers: cube.attributes}
      ),
      fragment: makeShaderStage(
        device,
        makeShader(compileGLSL(fragmentShader, 'fragment')),
        {targets: colorStates}
      ),
      depthStencil: depthStencilState,
    };
    return device.createRenderPipeline(pipelineDesc);
  }, [device, colorStates, depthStencilState]);

  // Uniforms
  const [uniformBuffer, uniformPipe, uniformBindGroup] = useMemo(() => {
    const uniformPipe = makeUniforms(defs);
    const uniformBuffer = makeUniformBuffer(device, uniformPipe.data);
    const entries = makeUniformBindings([{resource: {buffer: uniformBuffer}}]);
    const uniformBindGroup = device.createBindGroup({
      layout: pipeline.getBindGroupLayout(0),
      entries,
    });
    return ([uniformBuffer, uniformPipe, uniformBindGroup]
         as [GPUBuffer, UniformDefinition, GPUBindGroup]);
  }, [device, defs, pipeline]);

  // Return a lambda back to parent(s)
  return yeet((passEncoder: GPURenderPassEncoder) => {
    // Draw call
    uniformPipe.fill(uniforms);
    uploadBuffer(device, uniformBuffer, uniformPipe.data);

    passEncoder.setPipeline(pipeline);
    passEncoder.setBindGroup(0, uniformBindGroup);
    passEncoder.setVertexBuffer(0, vertexBuffers[0]);
    passEncoder.draw(cube.count, 1, 0, 0);
  });
}

This is 1 top-level function, with zero control flow, and a few hooks. The cube has a state (blink), that it decides to change on a timer. Here, useResource is like a sync useEffect which the runtime will manage for us. It's not pure, but very convenient.

All the external dependencies are hooked up, using the react-like useMemo hook and its mutant little brother useOne (for 0 or 1 dependency). This means if the WebGPU device were to change, every variable that depends on it will be re-created on the next render. The parts that do not (e.g. the raw cube data) will be reused.

This by itself is remarkable to me: to be able to granularly bust caches like this deep inside a program, written in purely imperative JS, that nevertheless is almost a pure declaration of intent. When you write code like this, you focus purely on construction, not on mutation. It also lets you use an imperative API directly, which is why I refer to this as "No API": the only wrappers are those which you want to add yourself.

Notice the part at the end: I'm not actually yeeting a real draw command. I'm just yeeting a lambda that will insert a draw command into a vanilla passEncoder from the WebGPU API. It's these lambdas which are reduced together in this sub-tree. These can then just be run in tree order to produce the associated render pass.

What's more, the only part of the entire draw call that actually changes regularly is the GPU uniform values. This is why uniforms is not an immutable object, but rather an immutable reference with mutable registers inside. In react-speak it's a ref, aka a pointer. This means if only the camera moves, the Cube component does not need to be re-evaluated. No lambda is re-yeeted, and nothing is re-reduced. The same code from before would keep working.

Therefor the entirety of Cube() is wrapped in a memo(...). It memoizes the entire Component in one go using all the values in props as the dependencies. If none of them changed, no need to do anything, because it cannot have any effect by construction. The run-time takes advantage of this by not re-evaluating any children of a successfully memoized node, unless its internal state changed.

The very top of the (reactive) part is:

export const App: LiveComponent<AppProps> = () => (props) => {
  const {canvas, device, adapter, compileGLSL} = props;

  return use(AutoCanvas)({
    canvas, device, adapter,
    render: (renderContext: CanvasRenderingContextGPU) => {

      const {
        width, height, gpuContext,
        colorStates, colorAttachments,
        depthStencilState, depthStencilAttachment,
      } = renderContext;

      return use(OrbitControls)({
        canvas,
        render: (radius: number, phi: number, theta: number) =>

          use(OrbitCamera)({
            canvas, width, height,
            radius, phi, theta,
            render: (defs: UniformAttribute[], uniforms: ViewUniforms) =>

              use(Draw)({
                device, gpuContext, colorAttachments,
                children: [

                  use(Pass)({
                    device, colorAttachments, depthStencilAttachment,
                    children: [

                      use(Cube)({device, colorStates, depthStencilState, compileGLSL, defs, uniforms}),

                    ]
                  })

                ],
              })
          })
      });
    }
  });
};

This is a poor man's JSX, but also not actually terrible. It may not look like much, but, pretty much everyone who's coded any GL, Vulkan, etc. has written a variation of this.

This tree composes things that are completely heterogeneous: a canvas auto-sizer, interactive controls, camera uniforms, frame buffer attachments, and more, into one neat, declarative structure. This is quite normal in React-land these days. The example above is static to keep things simple, but it doesn't need to be, that's the point.

The nicest part is that unlike in a traditional GPU renderer, it is trivial for it to know exactly when to re-paint the image or not. Even those mutable uniforms come from a Live component, the effects of which are tracked and reconciled: OrbitCamera takes mutable values and produces an immutable container ViewUniforms.

You get perfect battery-efficient sparse updates for free. It's actually more work to get it to render at a constant 60 fps, because for that you need the ability to independently re-evaluate a subtree during a requestAnimationFrame(). I had to explicitly add that to the run-time. It's around 1100 lines now, which I'm happy with.

Save The Environment

If it still seems annoying to have to pass variables like device into everything, there's the usual solution: context providers, aka environments, which act as invisible skip links across the tree:

export const GPUDeviceContext = makeContext();
export const App: LiveComponent<AppProps> = () => (props) => {
  const {canvas, device, adapter, compileGLSL} = props;

  return provide(GPUDeviceContext, device,
    use(AutoCanvas)({ /*...*/ })
  );
}

export const Cube: LiveComponent<CubeProps> = memo((fiber) => (props) => {
  const device = useContext(GPUDeviceContext);
  /* ... */
}

You also don't need to pass one variable at a time, you can pass arbitrary structs.

In this situation it is trickier for the run-time to track changes, because you may need to skip past a memo(…) parent that didn't change. But doable.

Yeet-reduce is also a generalization of the chunking and clustering processes of a modern compute-driven renderer. That's where I got it from anyway. Once you move that out, and make it a native op on the run-time, magic seems to happen.

This is remarkable to me because it shows you how you can wrap, componentize and memoize a completely foreign, non-reactive API, while making it sing and dance. You don't actually have to wrap and mount a <WebGPUThingComponent> for every WebGPUThing that exists, which is the popular thing to do. You don't need to do O(N) work to control the behavior of N foreign concepts. You just wrap the things that make your code more readable. The main thing something like React provides is a universal power tool for turning things off and on again: expansion, memoization and reconciliation of effects. Now you no longer need to import React and pretend to be playing DOM-jot either.

The only parts of the WebGPU API that I needed to build components for to pull this off, were the parts I actually wanted to compose things with. This glue is so minimal it may as well not be there: each of AutoSize, Canvas, Cube, Draw, OrbitCamera, OrbitControls and Pass is 1 reactive function with some hooks inside, most of them half a screen.

I do make use of some non-reactive WebGPU glue, e.g. to define and fill binary arrays with structured attributes. Those parts are unremarkable, but you gotta do it.

If I now generalize my Cube to a generic Mesh, I have the basic foundation of a fully declarative and incremental WebGPU toolkit, without any OO. The core components look the same as the ones you'd actually build for yourself on the outside. Its only selling point is a supernatural ability to get out of your way, which it learnt mainly from React. It doesn't do anything else. It's great when used to construct the outside of your program, i.e. the part that loads resources, creates workloads, spawns kernels, and so on. You can use yeet-reduce on the inside to collect lambdas for the more finicky stuff, and then hand the rest of the work off to traditional optimized code or a GPU. It doesn't need to solve all your problems, or even know what they are.

I should probably reiterate: this is not a substitute for typical data-level parallelism, where all your data is of the exact same type. Instead it's meant for composing highly heterogeneous things. You will still want to call out to more optimized code inside to do the heavy lifting. It's just a lot more straightforward to route.

For some reason, it is incredibly difficult to get this across. Yet algorithmically there is nothing here that hasn't been tried before. The main trick is just engineering these things from the point of view of the person who actually has to use it: give them the same tools you'd use on the inside. Don't force them to go through an interface if there doesn't need to be one.

The same can be said for React and Live, naturally. If you want to get nerdy about it, the reconciler can itself be modeled as a live effect. Its actions can themselves become regular nodes in the tree. If there were an actual dialect with a real live keyword, and WeakMaps on steroids, that would probably be doable. In the current implementation, it would just slow things down.

Throughout this series, I've used Javascript syntax as a lingua franca. Some might think it's insane to stretch the language to this point, when more powerful languages exist where effects fit more natively into the syntax and the runtime. I think it's better to provide stepping stones to actually get from here to there first.

I know that once you have gone through the trouble of building O(N2) lines of code for something, and they work, the prospect of rewriting all of them can seem totally insane. It probably won't be as optimized on the micro-level, which in some domains does actually still matter, even in this day and age. But how big is that N? It may actually be completely worth it, and it may not take remotely as long as you think.

As for me, all I had to do was completely change the way I structure all my code, and now I can finally start making proper diagrams.

Source code on GitLab.

07 Jul 05:35

Responsible Data Collection is Good, Actually (Ubisoft Data Summit 2021)

by chuttenc

In June I was invited to talk at Ubisoft’s Data Summit about how Mozilla does data. I’ve given a short talk on this subject before, but this was an opportunity to update the material, cover more ground, and include more stories. The talk, including questions, comes in at just under an hour and is probably best summarized by the synopsis:

Learn how responsible data collection as practiced at Mozilla makes cataloguing easy, stops instrumentation mistakes before they ship, and allows you to build self-serve analysis tooling that gets everyone invested in data quality. Oh, and it’s cheaper, too.

If you want to skip to the best bits, I included shameless advertising for Mozilla VPN at 3:20 and becoming a Mozilla contributor at 14:04, and I lose my place in my notes at about 29:30.

Many thanks to Mathieu Nayrolles, Sebastien Hinse and the Data Summit committee at Ubisoft for guiding me through the process and organizing a wonderful event.

:chutten

07 Jul 05:22

Changes to Audacity

Doug Peterson, doug — off the record, Jul 06, 2021
Icon

As Doug Peterson says, "I’m still at a loss to understand how a company can acquire an Open Source piece of software and then inject their own terms and conditions." So am I. Anyhow, this post discusses the new terms and conditions for Audacity, the audio editing application I have used for more than a decade. The new license "gives the company the right to collect various pieces of data," and it "preclude it from being used by anyone under the age of 13" (presumably so it can collect data). For more, you should read this article titled 'Audacity is now a Possible Spyware, Remove it ASAP'. There's a fork being developed to roll back the changes. Stay tuned.

Web: [Direct Link] [This Post]
04 Jul 01:24

Bluetooth Headphones Don’t Always Play Nice With Computers. Here’s Why.

by Lauren Dragan
Bluetooth Headphones Don’t Always Play Nice With Computers. Here’s Why.

Bluetooth headphones have transcended their original purpose of merely delivering sound to the ears. They can reduce background noise, track movement, survive underwater, measure heart rate, and even enhance hearing. When you consider all that they can do, it becomes even more confusing and frustrating when some Bluetooth headphones seem unable to perform a simple task: connect reliably to a computer.

Dismiss
04 Jul 01:22

The art of asking nicely

The art of asking nicely

CLIP+VQGAN Is a GAN that generates images based on some text input - you can run it on Google Collab notebooks, there are instructions linked at the bottom of this post. Janelle Shane of AI Weirdness explores tricks for getting the best results out of it for "a herd of sheep grazing on a lush green hillside" - various modifiers like "amazing awesome and epic" produce better images, but the one with the biggest impact, quite upsettingly, is "ultra high definition free desktop wallpaper".

Via janellecshane

04 Jul 01:22

Apple’s employee letter is less effective because it’s a rambling mess

by Josh Bernoff

In June, Apple CEO Tim Cook told Apple employees that starting in the fall, they’d need to return to the office on Mondays, Tuesdays, and Thursdays. Many employees signed a rambling and disjointed 1300-word letter of protest. I’ll show how it could be better. The employee plea isn’t effective Consider a ROAM analysis of the … Continued

The post Apple’s employee letter is less effective because it’s a rambling mess appeared first on without bullshit.

04 Jul 01:20

Inarguably Freckled

by peter@rukavina.net (Peter Rukavina)

Sometimes I forget that I’m freckled. But then I sit for a close-up, and it’s inarguable. Witness:

Me, wearing an AirPod Pro in my ear.

I am modelling my new AirPods Pro, which I’ve been using for a week now, after a recommendation from my friend Sosi pushed me from cart, where they’d been languishing for months, to checkout. They arrived a few days later, and they are now seldom out of my ears.

While I like them as a music and podcast delivery system, the thing I like about them the absolute most is their noise-cancelling magic, magic that’s enough to have me wear them for that. I’ve come to learn, as a result, that there’s a lot of background noise in my life, slowly chipping away at my sanity. Cutting out that noise has mean measurable improvements in my work productivity and sense of general calm both.

04 Jul 01:19

Developer finds a way to run Windows 11 on an old Nokia Lumia 950 XL

by Karandeep Oberoi

We already know that once Windows 11 releases, it’ll feature deep integration with Android Apps and will run smartphone applications on the OS, but what about the other way round? A student developer has found a way to make Windows 11 run on a Windows smartphone.

Gustave Monce, a developer and engineering student, was able to assemble an ARM build of Windows 11 using UUP (Unified Update Platform) images and successfully install the upcoming operating system on a Microsoft Lumia 950 XL.

The Start Menu works, the new taskbar runs as it would on a PC, animations are intact, though a little laggy, and Windows 11 adapts nicely to the smaller 5.7-inch screen, as seen in the video above.

 

Monce was also successful in enabling cellular connectivity in the build by manually importing drivers from Windows 10 Mobile and Windows 10 on ARM, bringing calling and messaging to the desktop operating system.

While what Monce has done is impressive, I don’t see it having any real-world use. However, it does demonstrate that the six-year-old Lumia 950XL is still capable of running modern operating systems.

Image credit: @gus33000

Source: @gus33000

The post Developer finds a way to run Windows 11 on an old Nokia Lumia 950 XL appeared first on MobileSyrup.

04 Jul 01:19

Qualcomm CEO says company can beat Apple’s chips thanks to Nuvia acquisition

by Jonathan Lamont

Qualcomm CEO Cristiano Amon believes the company can have the best chip on the market thanks to a recent acquisition that brought chip architects that formerly worked on Apple’s processors.

In an interview with Reuters, Amon spoke about Qualcomm’s plans for laptop CPUs, which include taking on Apple’s M1 chips while competing with Intel and AMD for market share on Windows. Amon told Reuters that neither Intel nor AMD have chips as energy efficient as Apple’s, but he suggested Qualcomm could.

Amon went on to explain that if Qualcomm were to rival Apple, it would need to move away from using ARM chip blueprints from U.K.-based Arm and create its own custom-designed chips. For that, Qualcomm will rely on the recent $1.4 billion USD (roughly $1.73 billion CAD) acquisition of Nuvia, which includes former Apple chip designers.

“We needed to have the leading performance for a battery-powered device. If Arm, which we’ve had a relationship with for years, eventually develops a CPU that’s better than what we can build ourselves, then we always have the option to license from Arm,” Amon told Reuters.

Qualcomm plans to start selling Nuvia-based laptop chips next year.

For now, the company’s focus remains on laptops. Amon told Reuters that Qualcomm has no plans to build its own chips for other big CPU markets like data centres, although it does plan to licence Nuvia designs to cloud computing companies that want to make their own chips.

Amon also spoke about plans to “go big” in China and take advantage of U.S. sanctions on Huawei. Amon also doesn’t seem worried by Apple’s plans to build its own 5G chips for iPhone (Qualcomm’s 5G modems currently power the iPhone 12 line). He told Reuters that Qualcomm has decades of experience designing modem chips — Amon thinks it will be difficult for any rival to replicate that experience.

Source: Reuters

The post Qualcomm CEO says company can beat Apple’s chips thanks to Nuvia acquisition appeared first on MobileSyrup.

04 Jul 01:18

Spring Stations 2021

by Richard

I've taken in every Winter Stations since 2017. It's an art exhibit usually held on Woodbine Beach during the colder months at the start of the year in Toronto. Generally, the art pieces are large, often inviting people to interact with the art directly by going inside it or using the controls to make something happen. I attended last year's event just as the COVID-19 pandemic started, keeping the streak alive. This year, the exhibition was scaled down in number (but thankfully not in size!) and held in Toronto's Historic Distillery District during May through July, and dubbed Spring Stations for the occasion. I love art you can touch or site on or walk inside, and did so with all of the art exhibits this year.

"Throbber" at Spring Stations 2021

"From Small Beginnings" at Spring Stations 2021

"Arc de Blob" at Spring Stations 2021

"The Epitonium" at Spring Stations 2021

I posted more photos of the exhibition on a set at Flickr, and see also 2018 and 2019.

04 Jul 01:18

The Future of Computers: The Neighborhood and The Nursing Home

by Kyle Rankin

Is your computer your property? Throughout much of the history of the personal computer, owning a computer was like owning a house. The cost of ownership might be high at first, but afterwards you joined a neighborhood of other owners running similar software. Maintaining a computer, like a house, requires some effort and sometimes expertise. […]

The post The Future of Computers: The Neighborhood and The Nursing Home appeared first on Purism.

04 Jul 01:18

Five-minute movie review: “The Tomorrow War”

by sheppy

Awesome family/time travel adventure.

04 Jul 01:17

Taming Windows in a Virtual Machine

by Martin

When most people talk about virtual machines, they mean Linux based VMs in the cloud. But there’s much more that can be done with the technology. Before turning to the cloud, I’ve actually started using virtual machines many years ago on my Linux desktop to have access to a Windows environment to run a number of programs I need for work. It’s nice to have Windows in a VM rather than running it on bare metal, because it’s limited to the files in a configured directory tree and hence, things can be contained very nicely. Recently, I’ve also experimented with using Windows running in remote virtual machines and access them via RDP or VNC. This works great in general but there are a number of things that can be tweaked to improve the behavior when using the system over the Internet. And here are my top 3:

Disable Graphical Effects

Out of the box, Windows uses a number of nice graphical effects when Windows are opened, resized and moved on the desktop. This makes the use of the desktop over the Internet a bit sluggish. Fortunately, most effects can be switched-off, which greatly improves remote use. The screenshot below shows how graphical effects can be switched-off with the ‘sysdm.cpl’ application. Click on the ‘Advanced‘ Tab and then on the the Performance ‘Settings‘ button. When clicking on the ‘Adjust for best performance‘ button, all graphical effects are disabled. ‘Smooth edges of screen fonts‘ should be enabled again, however, as font anti-aliasing is otherwise disabled, which makes everything on the desktop and in applications look rather crude. After applying the settings, the remote desktop suddenly feels much more responsive!

Prevent Immediate Windows Updates After Starting An Instance

As I only use Windows VMs occasionally which are otherwise switched-off, the first thing that usually happens when I launch an instance is that Windows runs wild on the disk, downloads and installs software updates and then typically requires several reboots. Definitely not what I want when I spin-up a VM instance to get some work done. Windows Pro and Enterprise versions, however, can be configured for manual software update downloads and installations. Just what I need! To change the default setting, run ‘gpedit.msc‘ and then go to ‘Computer Configuration –> Administrative Templates –> Windows Components –> Windows Update‘. Then double click on ‘Configure Automatic Updates‘, set to ‘enabled‘ and select setting ‘2 – Notify for download and auto install ‘. A reboot might be required for the changes to take effect.

Disable Windows Defender Background Scans

Another annoying behavior when running Windows in a VM is that whenever it thinks the system is idle, it starts running some background tasks that hog massive amount of CPU and IO resources. Often enough, however, I work in the host OS and hence, guest OS background activity scheduled in ‘idle’ times is getting in the way. One such background task is the Windows defender background scan which can be disabled conveniently as follows:

Run ‘Task Scheduler‘ then select ‘Microsoft -> Windows -> Windows Defender‘. There, you will find 4 ‘Defender Tasks‘ and the one to deactivate is ‘Windows Defender Scheduled Scan‘. While this doesn’t stop all background tasks, it at least helps to stop this task which is frequently run after Windows is restarted and left alone for a few minutes.

So there we go, these are the top 3 improvements that people have told me about over time. If you know any other things that make work with a local or remote Windows installation in a VM even better, please consider leaving a comment. Thanks!

04 Jul 01:17

Climbing Mount Effect

Cover Image

Declarative Code and Effects

This is a series about incrementalism in code and coding. On the one hand, I mean code that is rewindable and resumable. On the other, I mean incremental changes in how we code.

This is not an easy topic, because understanding some of the best solutions requires you to see the deep commonalities in code across different use cases. So I will deliberately start from basic principles and jump a few domains. Sorry, it's unavoidable.

Hopefully by the end, you will look differently at the code you write. If you don't, I hope it's because you already knew all this.

Hello World

If an abstraction is good enough to be adopted, it tends to make people forget why it was necessary to invent it in the first place. Declarative code is such an abstraction.

The meaning of "declarative code" is often defined through contrast: code that is not imperative or Object-Oriented. This is not very useful, because you can use declarative patterns all over imperative code, and get the exact same benefits. It can also be a great way to tame wild code you don't own, provided you can build the right glue.

Declarative + OO however is a different story, and this applies just the same to OO-without-classes. Mind you, this has nothing to do with the typical endless debate between FP vs OO, which is the one about extensibility vs abstract data types. That's unrelated.

While seeing wide adoption in certain niches (e.g. UI), the foundational practices of declarative code are often poorly understood. As a result, coders tend to go by example rather than principle: do whatever the existing code does. If they need to stray from the simple paths, they easily fall into legacy habits, resulting in poorly performing or broken code.

So far the best solution is to use linters to chide them for using things as they were originally intended. The better solution is to learn exactly when to say No yourself. The most important thing to understand is the anti-patterns that declarative code is supposed to remedy. Headache first, then aspirin.

a modernized version of dwarf fortress

For a perfect example, see the patch notes for almost any complex video game sandbox. They often contain curious, highly specific bugs:

  • Fix crouch staying on if used while a bear attacks
  • Fix player getting control-locked if loading an autosave created while lockpicking
  • Fix a bug which lets you immediately cancel a knockdown by swapping ammo types
  • Fixed an issue where leaving and returning after the briefing would block the quest's progress

Some combination of events or interactions corrupts the game's state in some way. Something changed when it shouldn't have, or the other way around. In the best case this results in a hilarious glitch, in the worst case a permanently unfinishable game. Developers can spend months chasing down weird bugs like this. It turns into whack-a-mole, as each new fix risks breaking other things.

This is very different from issues that occur when the game systems do exactly what they should do. Like that time cats were getting alcohol poisoning in Dwarf Fortress, because they had walked through puddles of spilled beer in the pub, and licked themselves clean afterwards. Such a complex chain of causality may be entirely unanticipated, but it's readily apparent why it happens. It's the result of code working too well.

Part of me dies every time I see a game get stuck in the mud instead, when I really want to see them succeed: I have a pretty good idea of the mess they've created for themselves, and why it's probably only going to get worse. Unless they dramatically refactor. Which usually isn't an option.

It's Imperative

So why does this happen?

Imagine an App with 5 views in tabs. Only one view is visible at a time, so there are 5 possible states. Because you can switch from any tab to any other, there are 5 x 4 = 20 possible state changes.

declarative vs imperative - for N states, there are N x (N - 1) possible state transitions

So you might imagine some code that goes:

constructor() {
  this.currentView = HomeView;
}

onSelect(newView) {
  // Hide selected view (exit old state)
  this.currentView.visible = false;
  // Show new view (enter new state)
  newView.visible = true;
  this.currentView = newView;
}

This will realize the arrows in the diagram for each pair of currentView and newView.

But wait. Design has decided that when you switch to the Promotions tab, it should start auto-playing a video. And it needs to stop playing if you switch away. Otherwise it will keep blaring in the background, which is terrible:

onSelect(newView) {
  if (this.currentView == newView) return;

  // Exit old state
  this.currentView.visible = false;
  if (this.currentView == PromoView) {
    PromoView.video.stop();
  }

  // Enter new state
  newView.visible = true;

  if (newView == PromoView) {
    PromoView.video.play();
  }

  this.currentView = newView;
}

No wait, they want it to keep playing in the background if you switch to the Social tab and back.

onSelect(newView) {
  if (this.currentView == newView) return;

  // Exit old state
  this.currentView.visible = false;
  if ((this.currentView == PromoView && newView != SocialView) ||
      (this.currentView == SocialView && newView != PromoView)) {
    PromoView.video.pause();
  }

  // Enter new state
  newView.visible = true;

  if (newView == PromoView) {
    PromoView.video.play();
  }

  currentView = newView;
}

Ok. Now they want to add a Radio tab with a podcast player, and they want it to auto play too. But only if the promo video isn't already playing.

Just look at how the if statements are popping out of the ground like mushrooms. This is the point at which things start to go terminally wrong. As features are piled on, a simple click handler explodes into a bloated mess. It's difficult to tell or test if it's even right, and there will likely be bugs and fixes. Replace the nouns and verbs, and you will get something very similar to what caused the curious bugs above.

The problem is that this code is written as delta-code (*). It describes state changes, rather than the states themselves. Because there are O(N2) arrows, there will potentially be O(N2) lines of code for dealing with N states. Each new feature or quirk you add will require more work than before, because it can interact with all the existing ones.

So imperative code isn't a problem by itself. The issue is code that becomes less maintainable over time. Ironically, a software industry notorious for snooty whiteboard interviews spent decades writing code like an O(N2) algorithm.

The real reason to write declarative code is to produce code that is O(N) lines instead. This work does not get harder over time. This is generally not taught in school because neither the students nor their teachers have spent enough time in one continuous codebase. You need to understand the O(N2) > O(N) part of the curve to know why it's so terrible, especially in the real world.

It probably seemed attractive in the first place, because it seemed economical to only touch the state that was changing. But this aversion to wasted CPU cycles has to be seriously traded off against the number of wasted human cycles.

Better to write code that makes it impossible to reach a bad state in the first place. Easier said than done, of course. But you should start with something like:

let tabs = [
  {
    view: homeView,
  }
  {
    view: promoView,
    video: 'promo-video.mp4',
    autoplayVideo: true,
  },
  {
    view: socialView, 
    backgroundVideo: true,
  },
  {
    view: newsView, 
    video: 'news-video.mp4',
    autoplayVideo: true,
  },
  // ...
]  

If you need to add a new kind of tab quirk, you define 1 new flag. You set it on a constant number of tabs, and then write the code to handle that quirk in 1 place. O(1) work. This code also tells you exactly where and what the special exceptions are, in one place, which means another engineer can actually understand it at a glance.

The declarative approach to visibility is just to blindly show or hide every tab in a plain old loop, instead of caring about what needs to go away and what needs to come in. When somebody inevitably wants the tabs to animate in and out—an animation which can be interrupted—you will discover that is actually what you have to do in the general case anyway.

Same with the video: you declare which one is currently supposed to be playing. Then you compare new with old, to see if you need to replace, pause, or adopt the current video.

The goal is to have most code only declare intent. Then you use other code to reach that target, no matter what the prior state was.

In some circles this is all old hat. But I suspect the people writing the hot frameworks don't quite realize how alien these problems feel from the outside. If the run-time is invisible, how are you ever supposed to figure out how it works, and learn to apply those same tricks? And how do you reach declarative bliss when there isn't a nice, prepackaged solution for your particular domain yet?

Or maybe there is a very capable library, but it's written in a retained style. If you try to fit this into declarative code without a proper adapter, you will end up writing delta-code all over again.

Strange Effects

The underlying challenge here is as banal as it is important and universal: "Have you tried turning it off and on again?"

Code is Turing-complete, so you don't know what it's going to do until you run it. It has the ability to amplify the complexity of its inputs. So if you change some of them, which parts are going to switch on and which will switch off?

Even on the back-end, similar things crop up behind the scenes:

  • connecting and disconnecting to network peers
  • starting and stopping jobs in response to events
  • building indices of data being edited
  • watching file systems for changes
  • allocating and freeing memory for data

These are all state transitions of some kind, which are usually orchestrated by hand.

Take the job of creating and managing a dependent resource inside a class. I swear I have seen this code written in dozens of codebases, each with dozens of files all doing the same thing, including one of my own:

constructor(size) {
  this.size = size;
  this.thing = new Thing(this.size);
}

onUpdate() {
  if (this.thing.size != this.size) {
    this.thing.dispose();
    this.thing = new Thing(this.size);
  }
}

dispose() {
  if (this.thing) this.thing.dispose();
}

This general pattern is:

constructor() {
  // Enter state - on
}

onUpdate() {
  // Exit old state - off
  // Enter new state - on
}

dispose() {
  // Exit state - off
}

You have to write code in 3 separate places in order to create, maintain and dispose of 1 thing. This code must also have access to this, in order to mutate it. Look around and you will see numerous variations of this pattern. Whenever someone has to associate data with a class instance whose lifecycle they do not fully control, you will likely spot something like this.

The trick to fix it is mainly just slicing the code differently, e.g. using a generator:

let effect1 = function* () {
  // Enter state - on
  yield // Wait
  // Exit state - off
}

let effect2 = function* () {
  // Enter state - on
  yield // Wait
  // Exit state - off
}

There are now only 2 identically shaped functions, each of which only refers to 1 state, not the previous nor next. A yield allows the function to be interrupted mid-call. The idea here is to simplify the lifecycle:

From

  • constructor()
  • onUpdate()
  • onUpdate()
  • onUpdate()
  • dispose()

To

  • Call effect
  • Cleanup effect
  • Call effect
  • Cleanup effect
  • Call effect
  • Cleanup effect
  • Call effect
  • Cleanup effect
  • Call effect
  • Cleanup effect

It doesn't have fewer calls, just fewer unique parts. It might seem dramatically less useful, but it's actually mostly the opposite. Though this is not obvious at first.

If you don't have a good mental model of a generator, you can pretend that instead it says:

let effect = () => {
  // Enter state - on
  return {
    // I'm not done yet
    done: false,
    // I don't have a value to return
    value: undefined,
    // Call me 🤙
    next: () => {
      // Exit state - off
      return {done: true, value: undefined}
    }
  };
}

This code could be described as self-rewinding: it creates something and then disposes of it. We can make a function that produces Thing resources this way:

// This creates an effect that describes the lifecycle of a Thing of size
let makeThingEffect = (size) => function* () {
  // Make thing
  thing = new Thing(size);

  yield thing;

  // Dispose thing
  thing.dispose();
};

So let's first talk about just effects as a formal type. They are similar to async/await and promises. But effects and promises are subtly different. A promise represents ongoing work. An effect is merely a description of work.

The difference is:

  • In a promise-based API, fetch(url) => Promise will run a new HTTP request
  • In an effect-based API, fetch(url) => Effect will describe an HTTP request

As a first approximation, you can think of an effect-based API as:

fetch(url) => () => Promise

It won't actually start until you call it a second time. Why would you need this?

Suppose you want to implement an auto-retry mechanism for failed requests. If a fetch promise fails, there is no way for you to retry using the promise itself. Each is one-use-only. You have to call fetch(url) again to get a fresh one. You need to know the specific type of promise and its arguments.

But if a fetch effect fails, then you can retry the same effect just by passing it back to your effect run-time. You don't need to know what effect it is. So in an Effect-based API, you can make a universal attemptRetry(effect, N) in a few lines. To emulate this with Promises, you need to use a () => Promise instead.

However, real Effects are supposed to be chained together, passing data from start to end. This is either a value or an error, to another effect, or back to the calling context. e.g.:

let makeAnEffect = (nextEffect) => function* (input, error) {
  if (!error) {
    output = f(input);
    return [nextEffect, output];
  }
  else return [null, null, error];
}

let combinedEffect = makeAnEffect( makeAnEffect( makeAnEffect() ) );

Here we return a static nextEffect that was decided on effect construction, along with a successful output to pass along. Or, if an error happened, we stop and only return the error. combinedEffect means running it 3 times in a row.

You could also return arbitrary effects on the fly. Below is a retry combinator. If an error happens, it returns another copy of itself, but with the retry count reduced by 1, until it reaches 0:

let attemptRetry = (effect, n) => function* (i, e) {
  // Make request
  const [value, error] = yield [effect, i, e];
  // Success
  if (value) return [null, value];
  // Retry n times
  if (n > 0) return attemptRetry(effect, n - 1);
  // Abort
  return [null, null, error];
}

This is 1 function in 1 place you can use anywhere.

You can focus purely on defining and declaring intended effects, while letting a run-time figure out how to schedule and execute them. In essence, an Effect is a formalization of 🤙 aka (...) =>. It's about the process of making things happen, not the actions themselves.

Whether effects are run serially or parallel depends on the use case, just like promises. Whether an effect should actually be disposed of or undone is also contextual: if it represents an active resource, then disposal is necessary to ensure a clean shutdown. But if an effect is part of a transaction, then you should only be rolling it back if the effect chain failed.

Other people have different definitions, and some of their Effects do different or fancier things. So buyer beware. But know that even just () => Promise, () => Generator and () => void can solve a whole bunch of issues elegantly.

Disposable

Going back to the makeThingEffect above, it might still seem overkill to wrap a "classic OO" class like new Thing this way. What's wrong with just having an interface Disposable that you implement/inherit from? Why should you want to box in OO code with effects even if you don't need to compose them? The difference is subtle.

For reliable operation, it's desirable that completion of one effect or disposal of a resource happens before the next one starts (i.e. to avoid double-allocating memory). But you can't call new on a class without immediately running a constructor. So you can't hold both an old and a new instance in your hands, so to speak, without having already initialized and run the new one. A common workaround is to have an empty constructor and delegate to an init() instead. This means some (or all) of your classes are non-standard to use, and you've reinvented the native new and delete.

Often you wish to retain some resources across a change, for efficiency, which means you need a hand-coded onChange() to orchestrate that. The main way you will take care of a series of possible creations and disposals is to just write new Thing and if (this.thing) and this.thing.foo != this.foo code repeatedly. That's why I've seen this code a thousand times.

While you can easily create a central mechanism for tracking disposals with classic OO, it's much harder to create a mechanism that handles both creation and updates. Somebody has to invoke specific constructors or reallocators, on specific classes, with specific arguments. This is called a Factory, and they pretty much suck.

It might seem like effects don't address this at all, as the pure form forbids any retention of state from one effect to the next, with each thing created in isolation. But it totally works: if every resource is wrapped in its own effect, you can declare the data dependencies per resource too. You can say "only re-allocate this resource if ____ or ____ changed since last time."

Instead of having an init() and an onChange() that you write by hand, you just have a render() method which spawns N resources and their dependencies. The run-time will figure out which ones are stale, clean them up, and create replacements.

How to actually do this depends on your usage. If the resource needs to be available immediately, that is different from whether it only needs to be returned to other parts of the code. In the latter case, you can just return effects which haven't run yet. In the former case, well, maybe we can fix that later.

Building an "Effect run-time" sounds intimidating but 90% of the benefit here comes from giving yourself the ability to put your "exit" code below your "enter" code, where it belongs. Which ever way you can. Then there only needs to be 1 place in your code where "exit" appears above "enter" (i.e. an onUpdate), which you only have to write once. O(1).

At least that's the dream. I know I'm still handwaving a lot. It's entirely on purpose. In practice, unless you have a fixed structure to hang your effects and resources off of, it's actually quite hard to track all the associated state. You simply don't have anywhere specific to put it. Where does yield thing go, actually?

The potential benefits here are so enormous, that it's absolutely worth to figure this out.

More tomorrow.

(*) hat-tip James Crook for this term

04 Jul 01:15

The strangest dream

by Alissa
A cloudy sunset through rain-streaked glass, looking out over Geelong West from the eleventh floor of a high-rise student residence, July 2021. Photograph by the author

I walked into the fancy grocer’s the other day and heard the shop radio playing a familiar song. You know, the football song. ‘I followed orders / God knows where I’ve been / but I woke up alone / all my wounds were clean / I’m still here / And I’m still a fool for the Holy Grail.’ Guess I’d better learn how football works. It’s that kind of town.

It’s been a few weeks since I uprooted myself from the only city I’ve ever lived in, quit my job at a prestigious but thoroughly miserable institution, said goodbye to my family and acquaintances, relocated interstate, and took up a new position as the metadata team leader at a regional academic library. I am beyond exhausted. I have never worked so hard, been to so many meetings, been welcomed so warmly, read so much documentation, been paid so much money, forgotten so many mealtimes (oops). But I have also never once regretted coming here. Moving to Victoria is the best personal and professional decision I have ever made. I hoped it would be. It’s been a long time coming.

My new job is a rare breed: a specialist metadata librarian, leading a team of specialist metadata librarians. Our work has changed significantly in the last couple of decades as academic libraries pivot to e-resources, where boutique item-level cataloguing has been largely replaced by scaled-up data enhancements (I almost wrote ‘enchantments’ here, and look, we are data wizards). The library’s priorities neatly line up with my own: ethical and culturally safe resource description, bibliographic identity management and other forms of authority control, untangling knotty workflows and making the most of our labour. I love working somewhere that cares this much about data quality.

I can’t believe how nice everyone is here. It’s like the whole library is structurally lovely. The ghosts of past experience tell me that I will surely regret this comment, but there are a lot of people at MPOW who have been there for decades—not because they’re unemployable elsewhere, but because it’s a genuinely nice place to work. I’ve had pockets of niceness in other jobs (including the last one), but this really is the first time I’ve experienced such an entirely warm, friendly, welcoming, functional, healthy workplace, amplified by strong, transparent, accountable senior leadership. It’s unreal. It’s amazing. I can’t believe I’ve been missing this all my life.

Some bits are proving as challenging as I expected, particularly the whole team leading thing. I had never managed so much as a sausage sizzle before I started this job, plus it’s not like I came with a lot of people skills built-in. Some of you might be asking ‘But how did you get a supervisory role with no supervisory experience?!’ (and, honestly, same) but I do know that I was hired partly for my technical skill, and partly because I seemed like someone who could learn the other stuff and contribute positively to team culture. I’ve had enough bad managers to know I never want to be like them, but I also have lots of support from my boss, my peer mentor (never had one of those before, it’s great) and my team. I hope I can do okay.

(I was astonished to learn that this blog already featured in my team’s professional reading list on our wiki. Hi team!)

I know this job isn’t the Holy Grail and my vocational awe is probably showing, but it really does feel like I’m living a dream. I think it will feel more real once I find somewhere to live (surprisingly difficult!) and start delving more deeply into the long list of projects that have awaited my arrival. This position had been vacant for close to eighteen months throughout Covid and Victoria’s extended lockdowns. There’s a lot that could use my attention. I can’t wait to get stuck in.

I’ve spent a lifetime trying to get my hands on this thing. I can’t believe I made it here. I can’t believe this is real.

04 Jul 01:12

Burnaby homeowners claim public street spots as their own - Burnaby Now

mkalus shared this story .

Some homeowners also put up fake 'no parking' signs to keep people away
orange cone cones parking street
You can't block parking spots on a public street.Getty Images

My recent blog about a Burnaby homeowner going “berserk” and saying he owned the parking spots out front of his home elicited a lot of stories and reaction from readers.

Some defended what the guy did, not how he did it, giving all sorts of reasons why homeowners should be able to claim the spots in front of their homes even though they are public streets.

"They should be declared mine because it's my house and I take car of the landscaping next to the street," wrote one North Burnaby owner. "Why should I have to look at somebody's dirty car out my front window.

Um, wow, that's really some logic, sir.

Another issue was “monster houses” on their block.

“Although this incident was handled terribly by the complainant I can understand his frustration,” wrote Shirley. “I live in an area of Burnaby where ‘monster houses’ are going up on lots that leave no room for parking. Often these houses have 2 or 3 suites adding to the problem. Where I live, cars park up and down my street like a used car lot, then walk up a lane to where they live. Often these cars are there for 2-3 days at a time. City of Burnaby bylaws state if a car is parked in front of your house for more than 3 hours between 8 AM and 5 PM, they are in violation and can be ticketed.”

OK, sure, some areas have a time limit on them, but insisting that nobody ever park in front of your home is ridiculous.

Also, so what if a house is big. The street is for everyone to park on. If they use a spot, then you’ll just have to park a little farther away from your home.
Several people wrote in about how some homeowners take things so far that they put up fake “no parking” in front of their homes or put out orange road cones to block people from parking in the spot. Apparently these techniques actually fool people.

Um, no.

You can’t do that. It’s a public street, not your private domain.

What’s weird is that many of these homeowners have parking spaces out back or driveways in their front yards – but they still don’t want anyone parking in front of their homes.

Plenty of times I’ve gone to cover a crime on a quiet residential street, park on the street so I can do an interview but have the homeowner give me a dirty look like I shouldn’t be there.

It’s. A. Public. Street.

“I once received an $80-something ticket from the city on a Saturday morning on Victoria day weekend because my girlfriend was parked for more than I think it was 3 hours in front of the neighbour’s house,” wrote someone else. “He didn't need the spot as he had one car and parked in his driveway. Apparently he knew the bylaw person personally and phoned him to drop by. That's Burnaby for you.”

Apparently.

Follow Chris Campbell on Twitter @shinebox44.

04 Jul 01:11

Flickr Favorites: Take a moment to reflect #EverChildMatters

A Great Capture posted a photo:

Take a moment to reflect #EverChildMatters

the #CNTower will be lit orange in solidarity with and support of Indigenous communities across Canada On July 1st 2021.

04 Jul 01:11

Twitter Favorites: [skinnylatte] Laksa bihun for lunch https://t.co/7WPqzHDe33

Adrianna Tan 陈丽珍 @skinnylatte
Laksa bihun for lunch pic.twitter.com/7WPqzHDe33
04 Jul 01:11

Twitter Favorites: [gabesimages] Basically the only reason I would visit #toronto is to be able to bike the opened streets. https://t.co/T964d4wGSk

The Name Is Gabe @gabesimages
Basically the only reason I would visit #toronto is to be able to bike the opened streets. twitter.com/sillygwailo/st…
04 Jul 01:11

Twitter Favorites: [seanorr] Well it finally happened. Saw my first incidence of anti Asian hate. Man harassing 3 women at A&W for wearing masks… https://t.co/hhOK4d4uxY

SEAN ORR @seanorr
Well it finally happened. Saw my first incidence of anti Asian hate. Man harassing 3 women at A&W for wearing masks… twitter.com/i/web/status/1…