577 karma · joined May 5, 2021
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).
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.
> 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.
> 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?
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.).
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.
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".
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.
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.
How did you arrive at the conclusion that OP doesn't understand margins?
So what happens if exit isn't called?
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.
Your claims are correct, but it feels like you're missing the point they are (ineffectively) making.
* 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?)
Why do you think the only way to read from an API is with a function call? Have you not heard of monads?
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.
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).
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.
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.
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).
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).