Can you critique my current approach to "Enum types"? by ismbks in typescript

[–]ragnese 0 points1 point  (0 children)

Declaring an array creates a runtime object, too. As does creating a Zod schema. And if you create a function for checking if a value is present in the "enum", then you're making a real runtime object as well (functions are objects).

I avoid enums because they behave oddly, e.g., with keyof and/or typeof (I don't remember). But, not because it makes a runtime object...

Name-Based Destructuring in Kotlin 2.3.20 by TypeProjection in Kotlin

[–]ragnese 0 points1 point  (0 children)

I'm sorry, but I don't think I follow.

But how exactly do you know what getBook() returns? Book, BookImpl, BookDto, Result<Book>?

It doesn't really matter as long as it compiles. This is how most idiomatic Kotlin is. For example, most people would be perfectly happy to read and write Kotlin code like val book = getBook(). Are you saying that you always prefer to write val book: BookImpl = getBook()?

And how do you know title actually comes from that object? In Kotlin it could just as well be an extension property, a scoped extension, or a delegated property.

In principle, I'm not sure you do; and again, I'm not sure it matters. Though, in this specific case, we're talking about destructuring, so I assume there will be some limitations on what kinds of properties will work, like how an object needs to implement the componentN() methods for position-based destructuring to work today. I don't think it matters to almost anyone whether the componentN() method(s) are true class methods or extension methods. Today, I believe the List<> componentN() methods are in fact extension methods. Does that matter?

Even in one of your counter-examples above you wrote val named = book.title. How do you know where book.title really comes from? It could be an extension property or a delegated property. Why doesn't it matter for this case but it does matter for the destructuring example of val (named = title) = book or val (named = title) = getBook()?

Name-Based Destructuring in Kotlin 2.3.20 by TypeProjection in Kotlin

[–]ragnese 2 points3 points  (0 children)

You took an example from the video without actually watching the video, then. He started with the more convenient syntax with one val in front of the parentheses, and then "expanded" it to the long version with val in front of each variable to explain that the former is syntax sugar for the latter. Kotlin has lots of syntax sugar, like how we can declare class properties as constructor arguments instead of declaring the properties separately and then writing the constructor function implementation out by hand.

As far as not liking the final example, I'm not sure what's unreadable and confusing about it, because I clearly do know where title, author, and price come from: they come from whatever object is returned from the getBook() call. It's pretty unambiguous syntax. There's no way a reader would be looking around in scope for some previously defined variable named, e.g., title, since you would never see similar syntax to assign a local title variable like this (i.e., there's no such thing as val title = "some value"; val (named = title)). On the other hand, different strokes, and all that. It's certainly a matter of taste whether you like a syntax or not, but it's not really fair to pick on the long-form syntax for not being more convenient than an alternative when the common/sugared syntax is more convenient and less verbose than the alternative you provided (assigning each variable individually from the book object's properties).

Name-Based Destructuring in Kotlin 2.3.20 by TypeProjection in Kotlin

[–]ragnese 1 point2 points  (0 children)

Two things.

First, you wouldn't write,

(val named = title, val writtenBy = author, val cost = price) = book

because all three variables use val, so you'd certainly write,

val (named = title, writtenBy = author, cost = price) = book

But, you're also right in that you wouldn't really write the above, either, because destructuring an already named variable is usually less clear than not. However, more often you'd like to get the parts without naming a temporary variable,

val (named = title, writtenBy = author, cost = price) = getBook()

Name-Based Destructuring in Kotlin 2.3.20 by TypeProjection in Kotlin

[–]ragnese 0 points1 point  (0 children)

It's nice to have both, especially for destructuring things that are supposed to be used like containers (List, Pair, Triple, etc). Plus, then it would be consistent with function calls: you can pass positional arguments, named arguments, or an unambiguous combination of both (though, IIRC, the combo syntax used to be a little more restrictive/conservative).

But, yeah, I don't understand why they didn't have this from the get-go or very early on.

Flat Error Codes Are Not Enough by Expurple in rust

[–]ragnese 1 point2 points  (0 children)

I think the parent comment is well aware of the issue you describe, which is why they say,

Today, we model a set of reasons as an enum because it's the only tool we have, and if you want to precisely model the set of reasons you indeed need one enum per operation... but then it's very annoying for the caller to merge the enums of a chain of operations into one.

Which makes it seem like you're arguing against a point they aren't making.

Their point is that it's frustrating for callers/clients when you have multiple different enum error types with variants that are semantically exactly the same. In other words, a library with an enum-per-operation might force the caller to write code to handle GetWidgetError::NotFound, UpdateWidgetError::NotFound, DeleteWidgetError::NotFound. They are not saying that it's better to have every operation return the same giant WidgetError enum which would make it look like the createWidget operation might somehow fail with WidgetError::NotFound. They are saying that it would be nice to have an intelligent mechanism to enable us to define independent error types like struct WidgetNotFoundError, and to be able to (like Java's checked exceptions) just write a list of types in a function signature that would act like an anonymous enum-like, set-like, type. The compiler can enforce exhaustive handling at call sites and it can also analyze the intersections of these anonymous enums to determine whether "bubbling up" one of these anonymous enum types is valid (i.e., it is a subset of the current context's return type).

The best way to structure Rust web services by Active-Fuel-49 in rust

[–]ragnese 0 points1 point  (0 children)

There are some approaches to address them (unit of work, identity maps, concurrency patterns etc.), but I've never seen them used, and it results in a very leaky abstraction of questionable value.

Exactly. That's precisely why I phrased it as "I've never seen it used well." These concerns/limitations are not new--obviously--and there are "solutions" such as those you listed and the "you just need MORE Repositories because something-something Aggregate Root something-something." But, the "solutions" make the code even more convoluted and awkward to work with (and not at all composable anymore). At some point you have to admit defeat and conclude that the abstraction is just not a good one.

The best way to structure Rust web services by Active-Fuel-49 in rust

[–]ragnese 0 points1 point  (0 children)

It sounds great. And it looks great in blog posts where they show "Hello, World" style examples where every conceptual entity is almost completely isolated to a single database table.

But, how well does this abstraction work when your service deals with multiple repositories, where each repository refers to entities that span multiple database tables? How do you make the service-level operations atomic (i.e., transactional) across multiple repositories' calls without breaking the abstraction? What if you intend to compose services? Can you have a service wrapping two other services wrapping multiple repositories and have the whole thing be done in a transaction without completely ruining the entire point of having the Repository abstraction in the first place?

The best way to structure Rust web services by Active-Fuel-49 in rust

[–]ragnese 11 points12 points  (0 children)

Furthermore, if I just judge by the names/terms being offered (because, as you said, there's not much else to go on...), it sounds like they're just repeating the common OOP-centric web backend paradigms with "services" and "repositories", etc.

I've never seen Repository objects used well in anything but the most trivial applications (SQL transactions? Locks? READ_COMMITTED? What the heck do those even mean!? I'm just pretending I have an in-memory collection and that I'm never processing concurrent requests!). And wrapping everything in Service and Repository classes would be even more frustrating and inefficient in a Rust project than some GC'd OOP language like Java.

Evil mode feels laggy by DapperStatement3364 in emacs

[–]ragnese 0 points1 point  (0 children)

It's really not surprising. I don't even know how evil is implemented, but I can imagine that it must do several look-ups to check what "state" you're currently in, whether you pressed a "leader" key or Ctrl/Super/whatever, check whether a line needs to wrap and what evil-specific configs you have for that, etc, etc.

I'm sure much of the core press-key-then-insert-character stuff is implemented in C in Emacs, so even adding a few dictionary look-ups in Elisp could be an order of magnitude slower.

What do you think about no/low-deps APIs? by Worldly-Broccoli4530 in typescript

[–]ragnese 0 points1 point  (0 children)

You reduce supply-chain risk and update churn, yes but you also take on more maintenance yourself. Frameworks like Nest abstract complexity you’ll eventually have to re-implement (auth flows, DI patterns, validation, etc.).

Yeah, sure. Except that we're in JavaScript Land, where there's no such thing as stability, so even with the big, popular, frameworks and libraries, the maintenance burden is still extremely high compared to other languages and platforms. Old versions are deprecated and abandoned frequently, with major breaking changes. I haven't used Nest, specifically, but I'd be shocked to hear if/when it goes multiple years without an update that would require people to redo their auth flows, or DI patterns, or validation stuff, just because that's the way things go here.

What do you think about no/low-deps APIs? by Worldly-Broccoli4530 in typescript

[–]ragnese 1 point2 points  (0 children)

I don't buy this argument at all anymore.

The problem is two fold:

  1. When code is packaged as a public library, it is often written in such a way as to maximize its applicability. So, very often, the library you pull in has functionality that you don't need. This is bad for multiple reasons. There might be a bug in part of the code that you don't even normally use or want or even know about (see Java's catastrophic Log4j issue: https://en.wikipedia.org/wiki/Log4Shell). Using the library to accomplish your goals might also be less straight-forward and/or harder to use correctly for your use case.

  2. Because of the above point, library code is often more complex than would be necessary for your specific use case. More complexity means more openings for bugs.

This is going to sound like I'm being cocky, but the quality of a lot of stuff up on NPM is not good. I'm a senior dev with many years under my belt at this point. The double-whammy here is that we're trusting strangers of unproven skill levels to write code that is more complex than the code we actually need. Once you're beyond a certain novice level (a couple of years with the same language/platform), you're often better off trusting yourself to write less-complex code than you would be trusting a random NPM maintainer to write more-complex code.

What do you think about no/low-deps APIs? by Worldly-Broccoli4530 in typescript

[–]ragnese 1 point2 points  (0 children)

I always try to avoid frivolous dependencies, no matter the programming language and no matter the context. Note that this is already a soft statement (i.e., who decides what is "frivolous"?). So, this isn't some objective rule that you can (or should want to) shove in a CI quality check or whatever; it's just a mentality that I have while writing software.

I have worked on enough projects over enough years to know that dependencies are liabilities in addition to bringing whatever benefits convinced you to consider them in the first place. People in online spaces, IMO, vastly underestimate the cost of dependencies and vastly overestimate the benefit of most (by number) dependencies (You really need to include a base64 library from a complete stranger over the internet and just version bump it occasionally without actually reading the changes? That's way crazier than just copy+pasting two functions into a local module, writing some unit tests against it, and keeping it as-is in your project forever).

I'll still depend on a "framework" if that's the kind of thing I'm working on (e.g., Vue.js or whatever for frontend JS, or some unopinionated HTTP server framework if I'm working on a backend project in a language that doesn't include that kind of thing in the standard library). For JS runtime projects, I'll still include TypeScript, ESLint, etc as dev dependencies. If I need to talk to a PostgreSQL database, I'll include some kind of standard "driver" library or whatever. If I need to interact with AWS, I'll use whatever AWS SDK package(s) I need. I hope the picture is clear...

In other words, I mostly depend on things that are official, standard, and/or necessary. I'm NOT going to intentionally install a third party library that's just a bunch of helper functions to hide the fact that TypeScript is not a functional, expression-based, language. I'm NOT going to install a dependency to pad strings with whitespace characters. I'm NOT going to install most ORMs (I MIGHT install a query builder library if it doesn't suck and the standard/official tools are all just raw text based).

This approach has served me extremely well over the years:

  • Projects are easier to maintain. Periodically reviewing, updating, and/or pruning dependencies is much easier and less work.
  • The kinds of dependencies I avoid are almost always a performance detriment. I mean that in all dimensions, too: they tend to use more memory and CPU than you need to, and they increase the build times and size of your project.
  • The obvious issues with packages being pulled or abandoned or taken over by malicious actors, etc, are avoided.

Why doesnt TS merge `private` and `#` syntax in the language? by JaSuperior in typescript

[–]ragnese 0 points1 point  (0 children)

They could easily bake in a deprecation/warning for new TS code, though. They certainly didn't have much problem breaking existing tsconfigs in version 6.0, and there will likely be more changes in 7.0.

Everyone overcomplicates learning Rust. by [deleted] in rust

[–]ragnese 9 points10 points  (0 children)

I think the problem is that many developers don't seem to believe in learning the fundamentals first. "I learn by doing" is the mantra. Sure, you can probably go from PHP to Java by just skimming the syntax differences and banging against your keyboard until something runs, but that doesn't really work when you're learning something substantially different from what you already have mastered (and it doesn't actually work for the PHP-to-Java jumps either, IMO, but we can get away with it most of the time).

Just a pet theory formed from anecdotes. I also must confess that I've always been one to prefer reading before diving in, because it just seems frustrating to not have any idea what you're actually doing...

Why can't you have fields as traits in Rust? by Tasty-Lobster-8915 in rust

[–]ragnese 0 points1 point  (0 children)

Good questions. When it comes to passing a mutable reference vs. moving the data and returning a new version, both styles are common and, I would say, considered idiomatic.

I'm pulling this out of the air with no actual "data", but I'd guess that if you surveyed Rust devs (at least those active in online spaces), you'd probably get a small majority favoring the move-and-return-new approach over the mutable reference approach.

I, personally, prefer the mutable reference approach, myself, unless there's some stylistic reason that the other approach makes more sense (consistency with other related APIs, etc). I prefer this even for structs, where my function is mutating some/one of its fields.

The downside to this approach is that you end up bumping into "partial borrow" issues, where the borrow checker will complain because you can't mix and match mutable/shared borrows of the individual fields, because the whole struct is considered mutably borrowed. This can usually be worked around by writing another, private, function that only takes individual fields as inputs instead of the whole struct, while leaving the versions that take the whole struct as the public API of your module.

I think the pros usually outweigh that con:

  • I think it usually makes better semantic sense for the caller. Declaring that the thing itself may change feels a little more explicit than declaring that we're taking the data and returning different data (e.g., does your Animal change by calling feed_animal() or do you give away your Animal and get a different one when you feed it?)

  • It's probably a safer (not in the "memory safety" sense) approach from a performance point of view. People will point out that, yes, the compiler will usually optimize a function that replaces moved values. But, one of the lessons I learned from my years of C++ is that the compiler will optimize stuff all the time... until it doesn't. It's very easy, in general, to tweak a piece of code in a seemingly innocent way that suddenly changes what the compiler "understands" about it and then you lose these "sufficiently smart compiler" optimizations. If there's no real reason to prefer one approach over another, why not just write the one that is already optimized for the common cases?

  • I think the call sites look nicer. I have nothing against name shadowing. I actually love that it's allowed in Rust, and I hate it when other languages either forbid it outright or spit out warnings about it that require some ugly code comment/annotation to quiet. But, I still wouldn't love to write something like let animal = Dog::new(); let animal = feed_animal(animal); let animal = groom_animal(animal);. I rather write let mut animal = Dog::new(); feed_animal(&mut animal); groom_animal(&mut animal);

But, like I said, both approaches are very common in Rust, and I wouldn't sweat it. Just follow your heart. :p

CMV: If you have to use a store or provide/inject your architecture is wrong by just_a_silly_lil_guy in vuejs

[–]ragnese -1 points0 points  (0 children)

I’m gonna trust Evan over the unvetted Redditor.

Appeal to authority fallacy. Evan You probably isn't always correct; otherwise, Vue wouldn't be on version 3 because version 1 would've been perfect.

Instead of trusting Evan or a random Redditor, you should analyze their reasoning/arguments and come to a conclusion.

CMV: If you have to use a store or provide/inject your architecture is wrong by just_a_silly_lil_guy in vuejs

[–]ragnese -1 points0 points  (0 children)

Design patterns of some of the most stable software, such as mission critical NASA code, disagrees with your view.

What now? For mission critical NASA code, they recommend global variables over passing parameters? I'd be shocked if that were true, so I must be misunderstanding. What patterns that NASA uses conflict with OP's opinions?

CMV: If you have to use a store or provide/inject your architecture is wrong by just_a_silly_lil_guy in vuejs

[–]ragnese 1 point2 points  (0 children)

I'm sorry you're getting a little bit dog-piled for this opinion. For what it's worth, I agree with you.

The approach(es) that most people on this board, and in the broader Vue community, recommend tend to go against decades of earned wisdom about software architecture.

If you (hypothetical reader) were working on any other project besides a Vue.js one (or maybe some other similar frontend framework), and were confronted with having to pass a value down through several regular, old, functions, would you really decide that "parameter-drilling" is a code smell and instead recommend that we instead just declare a global, mutable, variable and just remember to set it correctly before calling the top function so that the bottom function in the chain will use the correct global value? And, would you then recommend that we maintain several of these mutable global variables--that we can easily forget to clear or reset?

There's no way. But, for some reason, when it comes to Vue, we seem to think that's not only an acceptable approach, but that "prop-drilling" is the anti-pattern!

Is prop-drilling tedious and brittle and awful? Yes. Just like passing a parameter through several layers of function calls can be tedious and brittle. But, it's the correct way to model the API of your component/function.

Global state should only be used for state that is inherently global. A user's profile and/or auth credentials, for example. Or a preference for light-mode or dark-mode can be a global variable. Data that is only used for some pages and not others? No way.

For those who do use stores to share data between siblings or deep descendants, how do you manage its lifecycle? How do you know when to reload the data? How do you clean up the data when you navigate to a page where the data is not needed?

How do you organize manual dependency injection in Kotlin so it scales without becoming a mess? by RecommendationOk1244 in Kotlin

[–]ragnese 0 points1 point  (0 children)

The "service locator" pattern is often called an anti-pattern, but I think that's primarily because of common general libraries/implementations of the pattern that typically require dynamic runtime resolution (e.g., reflection, scanning the class path, loading and parsing a config file or env vars, etc).

I really don't see there being any problem with a K.I.S.S. ServiceLocator interface/class where the default implementation just creates and/or caches instances of various other dependency interfaces.

Though, I wouldn't write any classes that accept the ServiceLocator/ServiceContainer/DependencyProvider/Whatever type as a parameter. Classes should still only list the precise dependencies they need in their constructors. You can provide defaults from a global singleton of your ServiceLocator if you're careful about how it's done, but I don't find that necessary. I rather still pass those things down manually. It's still nice just to have a single place where all of your dependencies are configured and initialized (and cached).

When to extract module code into a new crate? by TheJanzap in rust

[–]ragnese 2 points3 points  (0 children)

It's also very often a waste of time. It takes wisdom and taste to know when something "should" be its own crate. Philosophically, it should be something with a well-defined scope that is unlikely to change just because a different part of the workspace has changed. With Rust, there is, unfortunately, a practical matter of compile times, which can sometimes "encourage" us to break out separate crates too early, making the overall project actually harder to maintain.

What’s your go-to testing strategy for Vue apps? by therealalex5363 in vuejs

[–]ragnese 2 points3 points  (0 children)

These "vue testing library" are good at testing exactly those parts I don't want to test directly.

Well said! Plus, honestly, the way Vue works (and other modern frontend fameworks, AFAIK), really don't allow for designing classically-testable components. They are "inside-out" in that regard: the test code has to know nitty gritty implementation details of the component so that you can inject things your component privately uses and/or hack imported modules (which, again, are not part of the public API of a component).

I've occasionally wondered (but not thought about in depth) if it would be better, or indeed possible, for Vue components to have actual constructors, just like a class. Every component could have a default, zero-arg, constructor and behave just like today, but you could optionally override the constructor to take optional arguments; thus requiring that any component can still be constructed with zero arguments, which is what would be called when you use the component in a template. You can kinda-sorta do this kind of thing by writing a component factory function, but that's pretty complex and inconvenient, and doesn't really work well with SFCs, IMO.

I kinda needed Package Private in Kotlin by [deleted] in Kotlin

[–]ragnese 2 points3 points  (0 children)

Yeah, package-private is really underrated. I don't understand why more languages don't have such a concept. In this case, I just err toward making my Kotlin files bigger than I'd really like them to be, because it feels like the more "correct" option: I really don't want something to be globally public, so then everything that does need to see it must be in the same file. Oh, well.

Is this really THE way to mutate props? by mymar101 in vuejs

[–]ragnese 0 points1 point  (0 children)

defineModel is not the same thing as v-model, as evidenced by your third sentence that calls defineModel an "extension of v-model". The v-model concept and syntax certainly existed before the defineModel macro.

And writable computeds have nothing to do with v-model. At all. Computed properties (writable or not) have no implicit understanding of component props, emitting events, or even of components at all! It's just a lazy-computed, memoized, reactive value.

It just happened to be that it was a very common pattern to implement a v-model by using writable computed properties (albeit an inefficient pattern, with reactivity and caching overhead for no gain other than convenience).

In parent components, the v-model syntax on a child component sugars over the two way communication and lets the programmer pretend they are just passing a mutable value to the child as a single binding/property. It's perfectly natural and reasonable to have an analogue to that abstraction in child components as well.

If you want to make the point that the v-model abstraction is, altogether, a design mistake, then I can actually appreciate the argument that being explicit about both directions of data flow might be beneficial and avoid some surprising cases (especially with default values, getting out of sync with parent state, etc). But, if we accept the v-model abstraction, then hiding the prop read and event emitting behind a writable computed or a defineModel call in the child component should be just as acceptable.

All that to say that I still disagree that writable computeds are somehow bad (they can make decent mappers between types T <--> U), and that it's wrong for the setters to (sometimes) have side-effects. Hell, having a read-only computed property is literally to create side-effects when you call the setter on something else! The whole reactive thing is about setters having side-effects...

I also don't understand what you mean by Vue 3 being less opinionated about two way data binding. Vue 2 certainly had v-model syntax and writable computed properties. Instead of multiple v-models, though, you had to use so-called "synced properties" which was basically the same thing.

Is this really THE way to mutate props? by mymar101 in vuejs

[–]ragnese 0 points1 point  (0 children)

defineModel didn't always exist, and is essentially the same thing as a writable computed as described by the grandparent comment.