TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

Serialization Is the Secret

278 点作者 borromakot8 个月前

15 条评论

akoboldfrying8 个月前
In most other languages, your newScore() example would indeed race as you claim, but in JS it actually won&#x27;t. JS uses a single-threaded event loop, meaning asynchronous things like timers going off and keys being pressed pile up in a queue until the currently executing call stack finishes, and are only processed then.<p>In your example, this means profile.score will remain at 3 every time. Interestingly this would still be the case even if setTimeout() was replaced with Promise.resolve(), since the sole &quot;await&quot; desugars to &quot;.then(rest-of-the-function)&quot;, and handlers passed to .then() are always added to the job queue, even if the promise they are called on is already settled [0].<p>To fix this (i.e., introduce an actual race), it would be enough to add a single &quot;await&quot; sometime after the call to newScore(), e.g., &quot;await doSomethingElse()&quot; (assuming doSomethingElse() is async). That would cause the final &quot;profile.score&quot; line to appear in the job queue at some indeterminate time in the future, instead of executing immediately.<p>[0]: <a href="https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;JavaScript&#x2F;Reference&#x2F;Global_Objec1ts&#x2F;Promise#:~:text=An%20action%20can%20be%20assigned%20to%20an%20already%20settled%20promise.%20In%20this%20case%2C%20the%20action%20is%20added%20immediately%20to%20the%20back%20of%20the%20job%20queue%20and%20will%20be%20performed%20when%20all%20existing%20jobs%20are%20completed" rel="nofollow">https:&#x2F;&#x2F;developer.mozilla.org&#x2F;en-US&#x2F;docs&#x2F;Web&#x2F;JavaScript&#x2F;Refe...</a>
评论 #41720208 未加载
LegionMammal9788 个月前
I&#x27;d note that &#x27;immutability everywhere&#x27; isn&#x27;t the only way to solve the issue of uncontrolled observation of mutations, despite that issue often being cited as a justification. You can also design a language to just directly enforce static restrictions on who may mutate a referenced value and when. Rust with its aliasing rules is easily the most famous implementation of this, but other languages have continued to experiment with this idea.<p>The big benefit is that you can still have all the usual optimizations and mental simplicity that depend on non-observability, while also not having to contort the program into using immutable data structures for everything, alongside the necessary control flow to pass them around. (That isn&#x27;t to say that they don&#x27;t have their use cases in a mutable language, especially around cross-thread data structures, but that they aren&#x27;t needed nearly as frequently in ordinary code.)
评论 #41720671 未加载
评论 #41726037 未加载
gr4vityWall8 个月前
This article is exceptionally well written. The author did a good job at making the subject approachable.<p>I disagree with this phrase:<p>&gt; By forcing the mutation of state to be serialized through a process’s mailbox, and limiting the observation of mutating state to calling functions, our programs are more understandable<p>My experience is quite the opposite - that&#x27;s a mental model for programs that goes against how most people I know reason about code.<p>The examples in Elixir all looked more complicated for me to understand, generally, although I understand the value brought by that model. The cognitive load seemed fairly higher as well.
评论 #41722539 未加载
评论 #41721834 未加载
评论 #41722580 未加载
评论 #41722979 未加载
评论 #41726567 未加载
skybrian8 个月前
I can see this being useful for the same reason that it&#x27;s useful that non-async functions in JavaScript can&#x27;t be interrupted - it&#x27;s as if you held a lock for the entire function call.<p>Between any two event handlers, anything could change. Similarly for await calls in JavaScript. And to get true parallelism, you need to start a separate worker. Concurrency issues can still happen, but not in low-level operations that don&#x27;t do I&#x2F;O.<p>I don&#x27;t see anything wrong with mutating a local variable in cases when it&#x27;s purely a local effect. It&#x27;s sometimes cleaner, since you can&#x27;t accidentally refer to an obsolete version of a variable after mutating it.
Closi8 个月前
So VB6 had it right all along?
评论 #41721216 未加载
评论 #41720390 未加载
hansvm8 个月前
&gt; The first is an actual mutation of memory. The second is an allocation of new memory. The old memory is unaffected. One could argue that this is a form of mutation. This mutation, however, is syntactic.<p>It&#x27;s not purely syntactic. You aren&#x27;t mutating the referent of a pointer. You&#x27;re mutating the referent of a scoped variable name. That saves you from certain swathes of bugs (in concurrent code), but it&#x27;s still something you want to use sparingly. Reasoning about any kind of mutability, even in single-threaded code, isn&#x27;t the easiest thing in the world.
evilotto8 个月前
The talk about immutabilty and serialization makes me think of tcl, where &quot;everything is a string&quot; also means &quot;everything is serializable&quot; and you don&#x27;t copy references around, you copy strings around, which are immutable. What&#x27;s neat is that it always looks like you are just copying things, but you can mutate them, just so long as you can&#x27;t possibly tell that you have mutated them, meaning that the old value isn&#x27;t visible from anywhere. With the caveat that sometimes you have to jump through hoops to mutate something.
tromp8 个月前
&gt; One of the major elements that sets Elixir apart from most other programming languages is immutability.<p>It&#x27;s interesting to compare Elixir to that other immutable programming language: Haskell.<p>In Elixir, a binding<p><pre><code> counter = counter + 1 </code></pre> binds counter to the <i>old</i> value of counter, plus 1. In Haskell it instead binds counter to the <i>new</i> value plus 1.<p>Of course that doesn&#x27;t make sense, and indeed this causes an infinite loop when Haskell tries to evaluate counter.<p>BUT it does make sense for certain recursive data structures, like an infinite list of 1s:<p><pre><code> ones = 1 : ones </code></pre> We can check this by taking some finite prefix:<p><pre><code> ghci&gt; take 5 ones [1,1,1,1,1] </code></pre> Another example is making a list of all primes, where you don&#x27;t need to decide in advance how many elements to limit yourself to.<p>Can you define such lazy infinite data structures in Elixir?
评论 #41718163 未加载
评论 #41718239 未加载
评论 #41718150 未加载
评论 #41719382 未加载
prerok8 个月前
What I don&#x27;t understand about immutability is performance. How do these languages achieve small memory footprints and avoiding continuous allocations of new object versions, because a single property changed?<p>I mean, all I see are small scale examples where there are only a few properties. The production Rust code I did see, is passing copies of objects left and right. This makes me cringe at the inefficacy of such an approach.<p>Disclaimer: I have 0 experience in immutable languages, hence the question :)
评论 #41722112 未加载
评论 #41722829 未加载
评论 #41722198 未加载
评论 #41722368 未加载
评论 #41722508 未加载
评论 #41727065 未加载
评论 #41729178 未加载
nmadden8 个月前
Rebinding is nicer than mutation, but neither are referentially transparent.
评论 #41719807 未加载
评论 #41720320 未加载
fracus8 个月前
This was very enlightening for me on the subject of immutability.
yawnxyz8 个月前
FYI in JS if you really need an immutable object, check out the immer library. it&#x27;s pretty awesome
cies8 个月前
I&#x27;m much stricter when it comes to what means immutable.<p><pre><code> counter = counter + 1 </code></pre> vs<p><pre><code> counter += 1 </code></pre> Are exactly the same to me. In both cases you bind a new value to counter: I don&#x27;t care much if the value gets updated or new memory is allocated. (sure I want my programs to run fast, but I dont want to be too much worried about it, the compiler&#x2F;interpreter&#x2F;runtime should do that &quot;good enough&quot; most of the times)<p>In the absence of type safety immutability --IMHO-- becomes a bit of a moot point. This is valid Elixir:<p><pre><code> x = 10 y = 25 z = x + y y = &quot;yeahoo&quot; IO.puts &quot;Sum of #{x} and #{y} is #{z}&quot; </code></pre> Trying to add another line &quot;z = x + y&quot; to the end, and you have a runtime error.<p>The &quot;feature&quot; of Elixir that allows scoped rebinding to not affect the outer scope, looks frightening to me. Most of the IDEs I&#x27;ve worked with in the past 15 years warn me of overshadowing, because that easily leads to bugs.<p>Haskell was already mentioned. There we can see real immutability. Once you say a = 3, you cannot change that later on. Sure sometimes (in many programs this can be limited) you need it, and in those cases there&#x27;s Haskell&#x27;s do-notation, which is basically syntactic sugar for overshadowing.
评论 #41719428 未加载
评论 #41719285 未加载
评论 #41721349 未加载
mrkeen8 个月前
&gt; I would argue that, from the perspective of our program, it is not more or less mutable than any other thing. The reason for this, is that in Elixir, all mutating state requires calling a function to observe it.<p>Are you never not inside a called function?<p>This just sounds like pervasive mutability with more steps.
评论 #41718061 未加载
评论 #41717899 未加载
评论 #41717876 未加载
评论 #41720284 未加载
sailorganymede8 个月前
I really enjoyed reading this because it explained the topic quite simply. It was well written !
评论 #41719670 未加载