<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hacker News: hutao</title><link>https://news.ycombinator.com/user?id=hutao</link><description>Hacker News RSS</description><docs>https://hnrss.org/</docs><generator>hnrss v2.1.1</generator><lastBuildDate>Sat, 30 May 2026 22:25:07 +0000</lastBuildDate><atom:link href="https://hnrss.org/user?id=hutao" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[New comment by hutao in "Algebraic Effects for the Rest of Us"]]></title><description><![CDATA[
<p>Here is an article by Andrej Bauer answering the exact question, "What is algebraic about algebraic effects and handlers?": <a href="https://arxiv.org/abs/1807.05923v2" rel="nofollow">https://arxiv.org/abs/1807.05923v2</a><p>Contrary to the claim from the comment you are replying to, according to Andrej Bauer, "algebraic" does <i>not</i> refer to the composition of different algebraic effects via composing their handlers.<p>When you see "algebra," think "algebraic structure," i.e., a set of operations and a set of equational laws on those operations.<p>An algebraic effect consists of a set of effectful operations. In that sense, each algebraic effect (reader, state, etc.) defines its <i>own</i> algebraic structure. In theory, the operations of an effect should also be related to each other by laws. Here is an example for the state effect from Andrej Bauer's paper:<p><pre><code>    lookup(ℓ,λs.lookup(ℓ,λt.κst)) = lookup(ℓ,λs.κss)
    lookup(ℓ,λs.update((ℓ,s),κ)) = κ()
    update((ℓ,s),λ_.lookup(ℓ,κ)) = update((ℓ,s),λ_.κs)
    update((ℓ,s),λ_.update((ℓ,t),κ)) = update((ℓ,t),κ)
</code></pre>
Therefore, the state effect has an algebraic structure.<p>However, monads that cannot be defined in this equational style cannot be translated to algebraic effects. An example is the continuation monad: <a href="https://old.reddit.com/r/haskell/comments/44q2xr/is_it_possible_to_make_a_generic_handler_for_the/czs9ch8/" rel="nofollow">https://old.reddit.com/r/haskell/comments/44q2xr/is_it_possi...</a> (I think you are involved in the Reddit comment chain that I'm linking to...)<p>AFAIK, another example is the "exception" monad, because the way that you interact with it is through the handler itself. I once saw a thread on r/Haskell discussing this, but can't find it.</p>
]]></description><pubDate>Sat, 30 May 2026 15:54:48 +0000</pubDate><link>https://news.ycombinator.com/item?id=48337572</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=48337572</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48337572</guid></item><item><title><![CDATA[New comment by hutao in "Stack Overflow’s forum is dead but the company’s still kicking"]]></title><description><![CDATA[
<p>I'm curious about the other Stack Exchange sites. Have they seen the same decline as Stack Overflow?<p>Stack Overflow was the "flagship" product of the Stack Exchange company, and if the company pivots to AI, I wonder what the future holds for the other Q&A sites on the SE network.</p>
]]></description><pubDate>Tue, 26 May 2026 23:27:21 +0000</pubDate><link>https://news.ycombinator.com/item?id=48287405</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=48287405</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48287405</guid></item><item><title><![CDATA[New comment by hutao in "What color is your function? (2015)"]]></title><description><![CDATA[
<p>I was not looking to disagree with your point, I only wanted to make additional commentary. Sorry if my comment came across the wrong way.<p>I do think "There are no function colors in Go in the way being discussed," versus "all functions [in Go] are red" are two slightly different ways of formulating the same set of facts, and the distinction between them is insightful, so that was what I wanted to touch upon. Namely, I wanted to point out that there is an "implicit" color within the programming language itself.</p>
]]></description><pubDate>Tue, 26 May 2026 23:01:04 +0000</pubDate><link>https://news.ycombinator.com/item?id=48287195</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=48287195</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48287195</guid></item><item><title><![CDATA[New comment by hutao in "What color is your function? (2015)"]]></title><description><![CDATA[
<p>Go (and other language with threads) implicitly run inside the "async IO monad." In the function color analogy, what this means is that all functions are red, and the "ordinary" function call corresponds to "await" in languages such as JavaScript or C#.<p>Async/await is one implementation of <i>cooperative concurrency</i>, where the programmer must explicitly annotate the points where a context switch may occur. However, one can imagine a program transformation that marks every function as async, and makes every function call an await. After making that transformation, the async/await annotations would no longer be necessary. The end result is <i>pre-emptive concurrency</i>, where the runtime may potentially interrupt the active thread at any function call.<p>To make another analogy, Haskell requires all IO actions to run in the explicit IO monad, while most languages (C, Java, JavaScript, etc.) do not distinguish between "pure" and "impure" functions. Therefore, C, Java, and JavaScript could all be said to implicitly run in the IO monad.<p>Async IO is also an instance of a monad. In JavaScript, all async functions must run inside the explicit async IO monad, while Go does not distinguish between async and sync functions. Therefore, Go implicitly runs in the async IO monad. This is similar to the aforementioned distinction between cooperative (made explicit to the programmer) and pre-emptive (handled implicitly by the runtime) concurrency.<p>In fact, Eugenio Moggi, the PL theorist who realized monads could describe programming languages, was not looking for a programmer-facing abstraction. Rather, he was trying to describe the "implicit" monad in a programming language's semantics (such as the IO monad in most programming languages, or the async IO monad in Go).</p>
]]></description><pubDate>Tue, 26 May 2026 22:43:23 +0000</pubDate><link>https://news.ycombinator.com/item?id=48287010</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=48287010</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48287010</guid></item><item><title><![CDATA[New comment by hutao in "What color is your function? (2015)"]]></title><description><![CDATA[
<p>> Propagating errors up the stack is not the same, because the top-level function is not developing an error return because of the 10-level-nested function. It is developing one because the function it called has one, and apparently, it needs to return it to its local caller. It's a local consideration ...<p>> By contrast, in a function coloring situation, if the color is wrong 10 layers down, you must change the calling function. It's a non-local consideration. You don't get to decide not to change it. You can't encapsulate it. You don't get a choice. It pollutes the entire stack, forcibly.<p>I think this is an interesting perspective, where I would raise a counterpoint. Both result types and async/await are instances of monads (the abstraction which approximates the article's idea of a function color, since you mentioned Haskell, I assume you know this). Just as you can "eliminate" the result type by explicitly handling the success and error cases, you could, theoretically, "eliminate" the async function by blocking on it. Doing so would treat the entire async subprogram, at the top-level function boundary, as synchronous IO, while the async subprogram would still benefit from concurrency internal to the function.<p>Compare Example #1:<p><pre><code>    int topLevel() {
      return match fallibleSubprogram() {
        Ok(()) => 0,
        Err(_) => 255,
      };
    }

    Result<(), Err> fallibleSubprogram() {
      let x = f()?;
      let y = g()?;
      return h(x, y);
    }
</code></pre>
Compare Example #2:<p><pre><code>    int topLevel() {
      block_on(asyncSubprogram);
      return 0;
    }

    async void asyncSubprogram() {
      let promiseX = f();
      let promiseY = g();
      let [x, y] = await Promise.all([promiseX, promiseY]);
      return await h(x, y);
    }
</code></pre>
In the above pseudo-code, you have the same program "structure," but the first uses results and the second uses promises. In the latter example, asyncSubprogram() gets called as if it were synchronous, but you still benefit from asynchronicity because f() and g() can execute concurrently within its body.<p>The main difference is that compared to pattern matching on Result types, programming languages typically make it unidiomatic to block on a promise. There are various reasons why this is the case, but my point is that Result types and async/await are more similar than they may initially appear.</p>
]]></description><pubDate>Tue, 26 May 2026 22:01:03 +0000</pubDate><link>https://news.ycombinator.com/item?id=48286611</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=48286611</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=48286611</guid></item><item><title><![CDATA[New comment by hutao in "“Why not just use Lean?”"]]></title><description><![CDATA[
<p>To clarify: ML started out as a scripting language for Robin Milner's proof assistant, LCF. The formal system, or "logic," is implemented in a minimal, trusted kernel, and the proof data structure is protected as an abstract data type that can only be constructed through the trusted kernel. On top of the kernel, tactic scripts may be defined to manipulate proof objects and facilitate proof search/automation.<p>Then, ML grew into a general-purpose programming language (both OCaml and Standard ML are dialects).</p>
]]></description><pubDate>Tue, 28 Apr 2026 05:00:06 +0000</pubDate><link>https://news.ycombinator.com/item?id=47930592</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47930592</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47930592</guid></item><item><title><![CDATA[New comment by hutao in "A case against currying"]]></title><description><![CDATA[
<p>One language that uses the tuple argument convention described in the article is Standard ML. In Standard ML, like OCaml and Haskell, all functions take exactly one argument. However, while OCaml and Haskell prefer to curry the arguments, Standard ML does not.<p>There is one situation, however, where Standard ML prefers currying: higher-order functions. To take one example, the type signature of `map` (for mapping over lists) is `val map : ('a -> 'b) -> 'a list -> 'b list`. Because the signature is given in this way, one can "stage" the higher-order function argument and represent the function "increment all elements in the list" as `map (fn n => n + 1)`.<p>That being said, because of the value restriction [0], currying is less powerful because variables defined using partial application cannot be used polymorphically.<p>[0] <a href="http://mlton.org/ValueRestriction" rel="nofollow">http://mlton.org/ValueRestriction</a></p>
]]></description><pubDate>Sun, 22 Mar 2026 15:29:41 +0000</pubDate><link>https://news.ycombinator.com/item?id=47478529</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47478529</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47478529</guid></item><item><title><![CDATA[New comment by hutao in "Ask HN: What breaks first when your team grows from 10 to 50 people?"]]></title><description><![CDATA[
<p>This seems to be a plagiarized paraphrase of @le-mark's comment, which was posted one hour earlier: <a href="https://news.ycombinator.com/item?id=47424625">https://news.ycombinator.com/item?id=47424625</a><p>@dang I think the account that I'm replying to might be a bot?</p>
]]></description><pubDate>Thu, 19 Mar 2026 03:18:46 +0000</pubDate><link>https://news.ycombinator.com/item?id=47434483</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47434483</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47434483</guid></item><item><title><![CDATA[New comment by hutao in "The Cost of Indirection in Rust"]]></title><description><![CDATA[
<p>One of the unwritten takeaways of this post is that async/await is a leaky abstraction. It's supposed to allow you to write non-blocking I/O as if it were blocking I/O, and make asynchronous code resemble synchronous code. However, the cost model is different because async/await compiles down to a state machine instead of a simple call and return. The programmer needs to understand this implementation detail instead of pretending that async functions work the same way as sync functions. According to Joel Sposky, all non-trivial abstractions are leaky, and async/await is no different. [0]<p>The article mixes together two distinct points in a rather muddled way. The first is a standard "premature optimization is the root of all evil" message, reminding us to profile the code before optimizing. The second is a reminder that async functions compile down to a state machine, so the optimization reasoning for sync functions don't apply.<p>[0] <a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/" rel="nofollow">https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...</a></p>
]]></description><pubDate>Thu, 12 Mar 2026 21:56:01 +0000</pubDate><link>https://news.ycombinator.com/item?id=47357788</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47357788</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47357788</guid></item><item><title><![CDATA[New comment by hutao in "Parse, Don't Validate and Type-Driven Design in Rust"]]></title><description><![CDATA[
<p>Note that addition also won't overflow if one addend is greater than half the range, but the other addend is still small enough (e.g. for the range -128 to 127, adding 65 + 12 will not overflow, even though 65 is greater than half of 127).<p>Your intention of having the restricted domain "NonNegativeLessThanHalfMaxValue" is probably so that <i>both</i> addends have the <i>same</i> domain. If you go down that route, perhaps you'll also want the closure property, meaning that the range should also belong to the same set. However, then you have to deal with the overflow problem all over again...<p>The real point is that when adding two N-bit integers, the range must be N+1 bits, because the "carry bit" is also part of the output. I think this is a scenario where "Parse, Don't Validate" can't easily help, because the validity of the addition is <i>intrinsically</i> a function of both inputs together.</p>
]]></description><pubDate>Sun, 22 Feb 2026 19:29:20 +0000</pubDate><link>https://news.ycombinator.com/item?id=47113854</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47113854</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47113854</guid></item><item><title><![CDATA[New comment by hutao in "Parse, Don't Validate and Type-Driven Design in Rust"]]></title><description><![CDATA[
<p>The phrasing that I hear more often is "make illegal states unrepresentable"; both the submitted article and Alexis King's original article use this phrase. At least according to <a href="https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/" rel="nofollow">https://fsharpforfunandprofit.com/posts/designing-with-types...</a>, it originates from Yaron Minsky (a programmer at Jane Street who is prominent in the OCaml community).<p>EDIT: Parent comment was edited to amend the "impossible/unrepresentable" wording</p>
]]></description><pubDate>Sat, 21 Feb 2026 22:43:29 +0000</pubDate><link>https://news.ycombinator.com/item?id=47105668</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47105668</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47105668</guid></item><item><title><![CDATA[New comment by hutao in "Parse, Don't Validate and Type-Driven Design in Rust"]]></title><description><![CDATA[
<p>Note that the division-by-zero example used in this article is not the best example to demonstrate "Parse, Don't Validate," because it relies on encapsulation. The principle of "Parse, Don't Validate" is best embodied by functions that transform untrusted data into some data type which is <i>correct by construction</i>.<p>Alexis King, the author of the original "Parse, Don't Validate" article, also published a follow-up, "Names are not type safety" [0] clarifying that the "newtype" pattern (such as hiding a nonzero integer in a wrapper type) provide weaker guarantees than correctness by construction. Her original "Parse, Don't Validate" article also includes the following caveat:<p>> Use abstract datatypes to make validators “look like” parsers. Sometimes, making an illegal state truly unrepresentable is just plain impractical given the tools Haskell provides, such as ensuring an integer is in a particular range. In that case, use an abstract newtype with a smart constructor to “fake” a parser from a validator.<p>So, an abstract data type that protects its inner data is really a "validator" that tries to resemble a "parser" in cases where the type system itself cannot encode the invariant.<p>The article's second example, the non-empty vec, is a better example, because it encodes within the type system the invariant that one element must exist. The crux of Alexis King's article is that programs should be structured so that functions return data types designed to be correct by construction, akin to a parser transforming less-structured data into more-structured data.<p>[0] <a href="https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-type-safety/" rel="nofollow">https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-...</a></p>
]]></description><pubDate>Sat, 21 Feb 2026 21:30:25 +0000</pubDate><link>https://news.ycombinator.com/item?id=47105006</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47105006</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47105006</guid></item><item><title><![CDATA[New comment by hutao in "Learning Lean: Part 1"]]></title><description><![CDATA[
<p>> With Prop, I think what you need to dig into is ‘non-computational’ not ‘non-computable’.<p>Here's another way to explain this:<p>As you state, Prop has to do with proof-irrelevance. When doing constructive mathematics, proofs are programs (meaning they carry computational content), but sometimes it's useful to treat any two proofs of the same proposition as equal. As a consequence, proofs cannot be inspected or run as programs, and you get back the Law of Excluded Middle from classical mathematics.<p>Decidable has to do with decidability. This means that given some proposition P, there is an algorithm that can either produce a proof of P, or a proof of ~P. This is usually useful when P is a predicate, so that at each x, P(x) either has a proof or a disproof.<p>In classical mathematics, the Law of Excluded Middle holds for all propositions. In constructive mathematics, the Law of Excluded Middle only holds for decidable propositions. If P is decidable, it is safe to constructively assume P or ~P because an algorithm can produce the answer.</p>
]]></description><pubDate>Wed, 18 Feb 2026 22:57:11 +0000</pubDate><link>https://news.ycombinator.com/item?id=47067570</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=47067570</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=47067570</guid></item><item><title><![CDATA[New comment by hutao in "Simplifying Vulkan one subsystem at a time"]]></title><description><![CDATA[
<p>This is my point of view as someone who learned WebGPU as a precursor to learning Vulkan, and who is definitely not a graphics programming expert:<p>My personal experience with WebGPU wasn't the best. One of my dislikes was pipelines, which is something that other people also discuss in this comment thread. Pipeline state objects are awkward to use without an extension like dynamic rendering. You get a combinatorial explosion of pipelines and usually end up storing them in a hash map.<p>In my opinion, pipelines state objects are a leaky abstraction that exposes the way that GPUs work: namely that some state changes may require some GPUs to recompile the shader, so all of the state should be bundled together. In my opinion, an API for the web should be concerned with abstractions from the point of view of the programmer designing the application: which state logically acts as a single unit, and which state may change frequently?<p>It seems that many modern APIs have gone with the pipeline abstraction; for example, SDL_GPU also has pipelines. I'm still not sure what the "best practices" are supposed to be for modern graphics programming regarding how to structure your program around pipelines.<p>I also wish that WebGPU had push constants, so that I do not have to use a bind group for certain data such as transformation matrices.<p>Because WebGPU is design-by-committee and must support the lowest common denominator hardware, I'm worried whether it will evolve too slowly to reflect whatever the best practices are in "modern" Vulkan. I hope that WebGPU could be a cross-platform API similar to Vulkan, but less verbose. However, it seems to me that by using WebGPU instead of Vulkan, you currently lose out on a lot of features. Since I'm still a beginner, I could have misconceptions that I hope other people will correct.</p>
]]></description><pubDate>Wed, 11 Feb 2026 00:40:48 +0000</pubDate><link>https://news.ycombinator.com/item?id=46969249</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46969249</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46969249</guid></item><item><title><![CDATA[New comment by hutao in "Parse, Don't Validate (2019)"]]></title><description><![CDATA[
<p>Typed functional programming has the perspective that types are like propositions and their values are proofs of that proposition. For example, the product type A * B encodes logical conjunction, and having a pair with its first element of type A and its second element of type B "proves" the type signature A * B. Similarly, the NonEmpty type encodes the property that at least one element exists. This way, the program is "correct by construction."<p>This types-are-propositions persoective is called the Curry-Howard correspondence, and it relates to constructive mathematics (wherein all proofs must provide an algorithm for finding a "witness" object satisfying the desired property).</p>
]]></description><pubDate>Tue, 10 Feb 2026 22:29:23 +0000</pubDate><link>https://news.ycombinator.com/item?id=46967873</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46967873</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46967873</guid></item><item><title><![CDATA[New comment by hutao in "The Monad Called Free (2014)"]]></title><description><![CDATA[
<p>A great way to understand monads is as a "design pattern," because they pop up extremely often in practical programming. Consider functions with a type signature that looks like `A -> T<B>`, such as `A -> Promise<B>` or `A -> Optional<B>`.<p>If you have some function fetchResponse that returns a Promise<Response>, and a function processJSON that takes a Response as an argument, you cannot compose them the usual way, as `processJSON(fetchResponse(x))`. Instead, you need to do `fetchReponse(x).then(processJSON)`, and the entire expression has to return another Promise. Ditto for functions that return an Optional.<p>All data types that implement this design pattern have the structure of a monad. A monad consists of a generic type that is "covariant" in its type parameter (such as Promise or Optional), a way to embed a singular value into the data type, and a "then" method to compose the data type with a callback. Lists also implement the monad design pattern, and the "then" method for lists is flatmap. A monad basically lets you compose functions with a type signature that looks like `A -> T<B>`.<p>Furthermore, each of these data types (Promises, Optionals, Lists) can be viewed as the output of some computation. Promises are produced whenever a function performs asynchronous IO, Optionals are produced when computations may return some "null" value, and Lists are returned if an algorithm may produce multiple solutions.<p>Just like Promises have async/await syntactic sugar, similar syntactic sugar can be devised for other "monadic" types. For Optionals, the equivalent of async/await is null propagation (some languages have a `?` operator for this). For Lists, the equivalent of async/await is list comprehension, which "picks" each element from the list to build up a new list. Async/await, null propagation, and list comprehensions all have the same underlying structure, called a "monad."<p>A "free monad" is a monad that does not implement any specific computation, but instead builds up an abstract syntax tree that needs to be interpreted. Free monads are useful because other monad instances can be implemented in terms of the free monad.</p>
]]></description><pubDate>Fri, 06 Feb 2026 19:20:29 +0000</pubDate><link>https://news.ycombinator.com/item?id=46916954</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46916954</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46916954</guid></item><item><title><![CDATA[New comment by hutao in "The Monad Called Free (2014)"]]></title><description><![CDATA[
<p>It's serendipitous that I'm seeing this blog post on the front page today, because I'm currently writing an article discussing the free monad.<p>In addition to the free monad presented in this post, there is a variant, called the "freer" monad, based on the "bind" operation instead of the "join" operation:<p><pre><code>    data Freer f a where
      Pure :: a -> Freer f a
      Bind :: f a -> (a -> Freer f b) -> Freer f b
</code></pre>
I believe this definition originates from the following paper by Oleg Kiselyov and Hiromi Ishii: <a href="https://okmij.org/ftp/Haskell/extensible/more.pdf" rel="nofollow">https://okmij.org/ftp/Haskell/extensible/more.pdf</a><p>When thinking of monads as giving the semantics of some computational strategy, it's easier to define them in terms of "bind" instead of "join." This way of defining monads is sometimes called a "Kleisli triple" because it is better suggestive of "Kleisli arrows," or functions of the signature `a -> m b`. The "bind" operation defines how to compose a monadic computation with its continuation, and from this perspective, the "freer" monad resembles an abstract syntax tree.<p>Originally, Eugenio Moggi proposed monads as a technique for specifying the denotational semantics of programming languages. All Java programs "really" happen in the IO + Either monads, because all Java programs may perform IO and throw exceptions. To my understanding, free monads are the monad that OCaml 5 runs in, because they give the semantics for effect handlers (or resumable exceptions).</p>
]]></description><pubDate>Fri, 06 Feb 2026 18:45:23 +0000</pubDate><link>https://news.ycombinator.com/item?id=46916528</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46916528</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46916528</guid></item><item><title><![CDATA[New comment by hutao in "My fast zero-allocation webserver using OxCaml"]]></title><description><![CDATA[
<p>Over the past few years, OCaml has seen an abundance of new language features and standard library additions. OCaml 4.08 added let-binding operators (syntactic sugar for continuation-passing style and monadic programming), OCaml 5 implemented a multicore runtime and effect handlers, OCaml 5.3 finally added dynamic arrays to the standard library, and OCaml 5.4 added labeled tuples.<p>With unboxed types, I believe OCaml would achieve similar granularity over memory allocations as C#: garbage-collected, but supporting "structs" which are allocated on the stack (or inline the same heap allocation when part of a reference type). I think there is an unexplored space for "soft" systems programming languages that retain a garbage collector by default, while also allowing the programmer to tightly control memory allocations in performance-critical code.<p>If OCaml hits this sweet spot in abstraction, what domains would adopt it? Could OCaml potentially compete with C#, or Swift?</p>
]]></description><pubDate>Tue, 03 Feb 2026 00:50:40 +0000</pubDate><link>https://news.ycombinator.com/item?id=46864704</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46864704</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46864704</guid></item><item><title><![CDATA[New comment by hutao in "Kotlin's rich errors: Native, typed errors without exceptions"]]></title><description><![CDATA[
<p>This is the difference between functions and effect handlers, to my understanding:<p>Functions map inputs to outputs, with a type signature that looks like A -> B. Functions may be composed, so if you have f: A -> B and g: B -> C, you have gf: A -> C. Function composition corresponds with how "ordinary" programming is done by nesting expressions, like g(f(x)).<p>Sometimes, the function returns something like Option<B> or Future<B>. "Ordinary" function composition would expect the subsequent function's input type to be Future<B>, but frequently you need that input to have type B. Therefore, optionals or futures require "Kleisli composition," where given f: A -> Future<B> and g: B -> Future<C>, you have gf: A -> Future<C>. Kleisli composition corresponds with "monadic" programming, with "callback hell" or some syntactic sugar for it, like:<p><pre><code>    let y = await f(x);
    g(y)
</code></pre>
Effect handlers allow you to express the latter, "monadic" code, in the former, "direct style" of ordinary function calls.</p>
]]></description><pubDate>Sat, 24 Jan 2026 15:20:55 +0000</pubDate><link>https://news.ycombinator.com/item?id=46744309</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46744309</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46744309</guid></item><item><title><![CDATA[New comment by hutao in "Kotlin's rich errors: Native, typed errors without exceptions"]]></title><description><![CDATA[
<p>You are right, in Haskell type constructors may be partially applied. In my opinion, this feature has less to do with any fundamental difference between `Either` in Haskell and `Result` in other languages, and more to do with Haskell's more powerful type system. In the same way, the pair type (a, b) in Haskell is also different from the pair types in other languages. This feature is called "higher-kinded types."<p>In particular, higher-kinded types are necessary to abstract over functors (or functions from types to types, * -> *). The list type constructor is a functor, and the partially applied type constructor `Either a` is also a functor. However, in languages without higher-kinded types, type variables can only be "ground types" (of kind *).<p>I don't agree with this statement:<p>> This means that Result <E, T> type effectively has a single type argument, namely pair of types. The Either type has two type arguments and can be partially applied.<p>The Result<T, E> type still takes two type arguments. The main distinction, in my view, is that Haskell allows types to be "higher-order." In fact, to be really pedantic, you could argue that the `Either` type in Haskell really takes one type argument, and then returns a function from types to types (currying).<p>This is kind of like the type-level equivalent to how many programming languages support some notion of function or procedure (and functions may have multiple arguments), but only more modern languages support higher-order functions, or allow variables to be functions.</p>
]]></description><pubDate>Sat, 24 Jan 2026 05:38:13 +0000</pubDate><link>https://news.ycombinator.com/item?id=46741348</link><dc:creator>hutao</dc:creator><comments>https://news.ycombinator.com/item?id=46741348</comments><guid isPermaLink="false">https://news.ycombinator.com/item?id=46741348</guid></item></channel></rss>