HNHacker News
TopNewBestAskShowJobs

ebingdom

577 karma · joined May 5, 2021

submissionscomments
ebingdom··on Ask HN: Teach me something new
That's not what undefined behavior means.
ebingdom··on Functional programming with bananas, lenses, envelopes and barbed wire [pdf] (1991)
> The modern definition of a lens is

When you say "the modern definition of X is Y", it tends to suggest that the older definition is no longer relevant or useful, even though I know that's not what you mean. So to clarify for everyone here, and to reinforce your first sentence, these are just unrelated concepts that happen to use the same term "lens". The lenses in this paper are still as relevant as ever, but these days they are usually called "unfold" (or "anamorphism" for the category theorists).

ebingdom··on Is Rust Web Yet?
> I sometimes wonder whether people adopt Rust/Haskell/ReasonML for front-end work because they want it to feel like more of an effort than it otherwise would.

That's ridiculous. There's a lot of garbage to unpack in that sentence, but for example Rust's borrow checker provides valuable guarantees about your code and eliminates entire classes of bugs. It's not just wasted time. Haskell's purity and lazy semantics allows for equational reasoning and more composable abstractions. ReasonML makes it really hard to accidentally write a bug that crashes the program. Etc.

Just because you don't see why those things are valuable doesn't mean they aren't.

ebingdom··on When to use generics
That's a strange take considering generics can provide important type safety benefits in many situations. For example, without generics, there's no way to implement the `MapKeys` example from the article unless you want to give up type safety and resort to unsafe casts. The performance impact is likely to be negligible in most code unless you're calling generic functions in a tight loop and you've exhausted every other optimization opportunity and you're in a position that requires such micro-optimization. That's a lot of ifs.
ebingdom··on AWS S3: Sometimes you should press the $100k button
So if I have a bunch of objects whose names are hashes like 2df6ad6ca44d06566cffde51155e82ad0947c736 that I expect to access randomly, is there any performance benefit to introducing artificial delimiters like 2d/f6/ad6ca44d06566cffde51155e82ad0947c736? I've seen this used in some places.
ebingdom··on AWS S3: Sometimes you should press the $100k button
Thanks for the clarification. But now I'm confused about the limits:

> 3,500 PUT/COPY/POST/DELETE requests per second per prefix

> 5,500 GET/HEAD requests per second per prefix

Most of those APIs don't even take a delimiter. So for these limits, does the prefix get inferred based on whatever delimiter you've used for previous list requests? What if you've used multiple delimiters in the past?

Basically what I'm trying to determine is whether these limits actually mean something concrete (that I can use for capacity planning etc.), or whether their behavior depends on heuristics that S3 uses under the hood.

I'm fine with S3 optimizing things under the hood based on access my patterns, but not if it means I can't reason about these limits as an outsider.

ebingdom··on AWS S3: Sometimes you should press the $100k button
I'm confused about prefixes and sharding:

> The files are stored on a physical drive somewhere and indexed someplace else by the entire string app/events/ - called the prefix. The / character is really just a rendered delimiter. You can actually specify whatever you want to be the delimiter for list/scan apis.

> Anyway, under the hood, these prefixes are used to shard and partition data in S3 buckets across whatever wires and metal boxes in physical data centers. This is important because prefix design impacts performance in large scale high volume read and write applications.

If the delimiter is not set at bucket creation time, but rather can be specified whenever you do a list query, how can the prefix be used to influence where objects are physically stored? Doesn't the prefix depend on what delimiter you use? How can the sharding logic know what the prefix is if it doesn't know the delimiter in advance?

For example, if I have a path like `app/events/login-123123.json`, how does S3 know the prefix is `app/events/` without knowing that I'm going to use `/` as the delimiter?

ebingdom··on GitHub Actions by Example
If you're looking for a way to reproduce your CI locally that isn't tied to a particular CI system (but which has a nice integration with GitHub Actions), there's also Toast: https://github.com/stepchowfun/toast

Toast lets you use whatever base image you want (even when running with GitHub Actions), and it has some extra features for local development (e.g., caching, bind mounts, tasks with dependencies between them, etc.).

ebingdom··on The λ-Cube
> I just want you to realize that you are in the extreme minority here

I'm in the minority because I've spent an unusual amount of time investing in my understanding of programming languages and their features, not because I have some fringe unjustified opinion.

> if I'm prototyping a project, looking for product-market fit, the last thing I want to care about is that my typeability is recursively enumerable or what-have-you.

With this comment you've lost your credibility in my mind. No one actually goes through this train of thought. I don't wonder about recursive enumerability whenever I start a project. I just use tools that help me build high quality software, such as principled static type systems.

ebingdom··on The λ-Cube
> Straight-up incorrect. Language designers think long and hard about how typings work in their languages. Rob Pike, Ken Thompson, and Russ Cox deliberated generics for like a decade[1] before finally allowing them in Go.

I think that kind of proves their point. Parametric polymorphism is one of the most well-understood, least contentious extensions to the lambda calculus. It's formalized by System F and has been implemented in programming languages 40 years ago with great type inference for an important subset (Hindley-Milner). Yet generics was highly controversial in the Go community. And now since none of the standard library was designed with generics in mind, it's full of unsafe patterns that involve essentially dynamic typing (e.g., the `Value` method of `Context`).

Despite Rob Pike et al. designing one of the most popular languages today (Go), I consider them more experts in systems rather than programming languages.

> I'm not sure exactly what you mean by "proper" -- but "rigid" type systems are extremely cumbersome to use practically. (Typed) λ-calculus is an academic example; Haskell is a real-world example.

I find Haskell a joy to use, and I cringe at having to use languages like Java and Go, which are a minefield of error-prone programming patterns (like using products instead of sums to represent "result or error"). Generally speaking, my Haskell code is shorter, less buggy, and more reusable than my Go code, so I'm not sure what you mean by "cumbersome".

ebingdom··on Dockerizing a Programming Language
OP is using Docker + Make in a similar way to how I was a few years ago, before I started using Toast (https://github.com/stepchowfun/toast). Toast lets you define tasks like you would with Make (without all the hairy gotchas of Makefiles), but it runs them inside Docker containers for better portability/reproducibility.
ebingdom··on UUIDs are popular, but bad for performance (2019)
> Let’s begin by the base64 notation. The cardinality of each byte is 64 so it takes 3 bytes in base64 to represent 2 bytes of actual value.

Wait, what? I thought it takes 4 base-64 digits to represent 3 bytes of data. Not 3 base-64 digits to represent 2 bytes of data.

ebingdom··on How programmers make sure that their software is correct
Coq requires thinking like a functional programmer, and most programmers are resistant to that paradigm for some reason.
ebingdom··on How programmers make sure that their software is correct
> But from a scientific standpoint, do you think a software can be 100% correct?

Sure it can. I've written 100% correct code using Coq. For example, I wrote a relatively simple program (~1.7k LoC) to interpret a simple programming language, but it was definitely correct (certified by a machine-checked proof).

Of course, for larger programs it's much harder to do that. But it's just a matter of how much time you're willing to invest.

ebingdom··on Use spacer components instead of CSS margins (2020)
> because you don't fully understand its purpose

How did you arrive at the conclusion that OP doesn't understand margins?

ebingdom··on Types and Programming Languages (2002)
I'd say it's the recommended book for type systems. For type theory, I'd recommend "Certified Programming with Dependent Types" by Adam Chlipala or "Programming Language Foundations in Agda" by Philip Wadler.
ebingdom··on Types and Programming Languages (2002)
Nice to see my favorite book on HN! I highly recommend this book if you want to really learn programming language theory (especially operational semantics and type systems).
ebingdom··on Linux x86 program start up – How the heck do we get to main()? (2011)
> It can't be returned from, the exit system call must be issued before execution terminates.

So what happens if exit isn't called?

ebingdom··on Knowledge Graphs
I absolutely love that book. Category theory has changed the way I think about so many things. Maybe the most prominent way category theory has influenced my thinking is that I always ask "what is the dual situation?" now, often leading to productive insights.
ebingdom··on Tricks I wish I knew when I learned TypeScript
The standard reference, if there is one, is Benjamin Pierce's "Types and Programming Languages" book.
ebingdom··on Tricks I wish I knew when I learned TypeScript
> Barely typed languages like C made rigorously typed languages like C++ and Java seem appealing. The boilerplatiness of those languages made duck typing seem appealing.

Eh, I consider Java to be barely typed too. If you have a variable of type Foo, the type system doesn't even guarantee that you have a Foo in there (it might be null). The whole point of a type system, in my mind, is to guarantee that I have that Foo!

> Writing anything nontrivial with duck typing made more elaborate type systems seem appealing.

In my mind, this makes type inference seem appealing, not duck typing (which is not well-defined, but most people associate it with dynamic typing).

> Needing a PhD in category theory to produce a side effect will no doubt make some other paradigm seem appealing in the future.

This oft-repeated exaggeration needs to stop. Using monads does not require a PhD in category theory. If you can understand Promises in JavaScript, then you can grasp how IO works in Haskell.

ebingdom··on Things Go needs more than generics
I'm not who you're responding to, but the fact that interfaces/pointers (among other things) are nullable and there is no way to make them non-nullable is a problem with Go. A lot of bugs in Go programs are due to calling methods on those types and getting a null pointer error.

Your claims are correct, but it feels like you're missing the point they are (ineffectively) making.

ebingdom··on Things Go needs more than generics
> while crediting Rust for things it didn't implement.

* invent

(Other than this minor typo, I'm not sure why you're being downvoted. It's crazy that people in 2021 think non-nullable types are novel/"crazy". Why is nullability the default in most people's brains?)

ebingdom··on Building with Nix on Replit
> I think this is not true. If it was this way, there would be no way to read data from an API.

Why do you think the only way to read from an API is with a function call? Have you not heard of monads?

ebingdom··on Abstraction, intuition, and the “monad tutorial fallacy” (2009)
> The problem is that there isn’t a tidy image (at least I haven’t come up with one) that combines all of it.

I don't think you should expect there to be an image that shows all the levels of abstraction at once. The image should only show you the level of abstraction that you're currently concerned with: in this case, endofunctors and the natural transformations between them.

What you're asking for is akin to asking for an architecture diagram of a distributed system that somehow also shows you the circuits inside the CPUs.

The point of abstraction is to package up details into a box and forget about them, for the purpose of managing the complexity as you reason at a higher level.

ebingdom··on Abstraction, intuition, and the “monad tutorial fallacy” (2009)
> The statement “category of endofunctors” is a good example; for someone with a very visual intuition like myself it feels like something that simply cannot be visualized, or is akin to visualizing a 5d hypercube.

Follow this recipe and don't skip any steps:

1. Understand what a category is. Objects and arrows between the objects, composition of arrows, identity arrows.

2. Understand what a functor is. Associate each object of one category with a corresponding object in a second category. Do the same for arrows.

3. Understand that an endofunctor is just a functor where the first and second categories are actually the same category. These are the functors that show up in programming: Maybe/Option, List, etc. The objects of the category are types and the arrows between them are functions.

4. Understand what a natural transformation is. In programming terms, a natural transformation is like a generic (in the OOP sense) function. Example: length : List<T> -> Int is a common natural transformation that takes a list and returns its length, and it works for any type of list (i.e., for all T). This particular natural transformation goes from the List functor to the constant functor that maps every type to Int (see how T doesn't show up in the return type in this example).

5. If natural transformations go between functors, consider making a category where the objects are functors and the natural transformations are arrows. This is the "category of endofunctors" you're looking for. If you have trouble visualizing it, any category can be visualized as a directed graph: the nodes in the graph are endofunctors like List, Option/Maybe, etc. and the edges are natural transformations like "length".

Each step will require you to Google something, stare at its definition, and spend time with examples. The one mistake that everyone makes is thinking that they should understand the phrase without first learning the constituent words. There's nothing mysterious or clever or tricky going on. You just have to know how to break it down into an incremental curriculum (which I've done for you here).

ebingdom··on Category Theory
Who is upvoting this? I love category theory and use it to reason about types and programs, but what even is this article. OP's glossary entry on "Kernel Category" makes me feel that they are just making up nonsense.

I would like to discuss the problem of how so many programmers are turned off by category theory despite the fact that they could benefit from learning it, but I don't know that this article really provides the prompt for such a discussion.

ebingdom··on An Introduction to Type Level Programming in Haskell
> I don't write tests at early stages, do you?

I definitely do. In my experience, it's much easier to adopt a discipline of testing (and static typing) early on than it is to try to retroactively add that to an existing system, which may or may not be written in a way that is even testable.

But I do appreciate that viewpoints can differ on this topic. Regarding types, I studied type theory academically, so types are natural to me and don't really add any extra cognitive work (and perhaps they eliminate some). So I might as well use and benefit from them if they basically cost me nothing. But for someone who thinks of static typing as just trying to make the compiler happy (perhaps because they don't really understand the type system or because the type system is not ergonomic), I can see why they might have a more pessimistic view of it.

ebingdom··on New horizons for SPJ
> It is great but I stand by my statement that the community's obsession with complexity (even if it's simpler from a category or maths sense) is the issue. Haskell's community is almost obsessed with increasing cognitive load IMO.

Personally, I consider it more complex to have to think about what side effects every piece of code has and what order things are evaluated in. The fact that I don't have to worry about these things in Haskell is a breath of fresh air for me. Also, not having to deal with the complexity of OOP when an ordinary function will do.

I think it's a misconception that Haskell is more complex than whatever popular OOP language people are using. It just looks more complex because it's less familiar (which I think is a problem with what we teach students in school).

ebingdom··on An Introduction to Type Level Programming in Haskell
> I don't do that any more. simply because I'm very lazy

I'm lazy too, but that's exactly why I use static types. So that when I refactor code, I can let the type checker tell me all the places that need to be updated instead of trying to piece that together from test failures (and praying that the tests didn't miss anything).

← PreviousPage 3 of 5Next →