Threads for Widdershin

  1. 13

    Title of the article needs “JavaScript” in it. SSR in other languages isn’t an issue, this is specific to Node.

    1. 5

      I would like a clarification though, there are perfectly nice “SSR” templating languages that have none of these problems. This post is mostly about server-rendering client-side code.

      1. 2

        Agree the title is confusing. I don’t think I can change it here but I will before sharing this more broadly.

      1. 16

        You know, one could capture these “colors” in a type system. One could see the different contexts in which the functions run as, I dunno, “side effects” separate from the intended action of the function. If you had these “wrapper types,” you’d be unable to inadvertently run a function that interacts with Server contexts in a Client context. In cases where either context could provide the same functionality – possibly implemented in different ways – you could implement a kind of “class” for those context types and make your functions generic across their “colors.”

        They’d need some kind of catchy, short name, though. Something that would definitely not scare people.

        1. 9

          Thank you for emphasizing exactly how much the word “monad” has become a fnord. You are describing codensity monads.

          1. 7

            Abilities? :-)

            1. 2

              Nice – I hadn’t looked at Unison before. That’s definitely an interesting way to encode and name these mysterious container types! Thanks for the link.

            2. 3
              1. 2

                Completely agree. I think you could actually have a wonderful experience with this sort of architecture if you had the type system and ecosystem to back it up.

              1. 31

                My initial, empathy-lacking response to this is “if you’re looking for sympathy, you can find it in the dictionary between shit and syphilis.”

                Alternatively, “play stupid games, win stupid prizes.”

                The title’s just wrong, though. Server side rendering was easy. PHP, JSP, jinja, django are all examples of that. The special kind of hell this post describes is just what happens when you try to run javascript on the server, which anyone who thought very much about it knew was a bad idea in the first place.

                The absurd complexity this post is moaning about is 100% opt-in.

                1. 8

                  Not looking for sympathy, just pointing out a problem I rarely see discussed.

                  I’ve also never built a serious app using these technologies and would do my best to discourage their usage in companies I work for, but that doesn’t prevent our clients (other dev teams) from doing so.

                  1. 9

                    FWIW, based on the “soapy taste” and and “while some would decry” commentary, I was not assuming that you were the one who’d personally opted in to this problem. But I stand by my assertion that SSR is the traditional, easy way to do things and that it requires opting into bad ideas to enjoy the pain your post describes.

                    1. 1

                      I see this discussed regularly under the name hydration. It’s the piece of web development that I don’t feel like I know an elegant approach to.

                  1. 33

                    A title describing the same problem from a different angle would be “The mess we’ve gotten ourselves into with single-page-applications

                    1. 6

                      How about “The proliferation of JavaScript and our failure to prevent servers from acquiring vast stockpiles of such code

                      1. 4

                        Can you elaborate? Classic SPAs don’t have this problem because all their functions are “client colored” (to borrow the terminology of the post).

                        1. 7

                          I guess the answer is that Classic SPAs are good until you need some SEO which is probably very common. Hence SSR. Although technically speaking SPA per se don’t need SSR (maybe for performance but shouldn’t be an issue if things were developped correctly by default I’d say).

                          1. 15

                            I was thinking the same thing. The title could easily be “The mess spawned by organizing the web economy around a search monopoly”.

                            1. 9

                              IMO, this is the wrong intuition. Categorically, pages that need to be SEO-opitimized are those that are informational. You don’t need SEO for a desktop app, nor would you need that a web app because a web app is the same thing but distributed through the browser (but sandboxed, not making users require a download executables, and on a highly portable platform available on almost every OS and architecture). These two concepts are not the same thing despite both being delivered through the browser; you shouldn’t use a SPAs tech stack for a basic page because information category pages don’t require the same shared state management and usage of device feature APIs that an application might. I can use Wikipedia from a TUI browser because it’s 95% information. It was the exact same issue in the Flash days of not using the right tech and society has permanently lost some content from its internet archive.

                              So it’s not “when you need SEO”, but SEO should be a requirement from the get-go in helping you choose a static site or dynamic, multipage application where the server always did the rendering.

                              The problem is the tooling. The NPM community instead of having an intuition about the right tool for the job and stating “do not use this tool for your static content”, we have tools that try to solve everything and hide the mountains of complexity that should have scared devs away from the complex solution into the simple one. It should be hard to make a complex pipeline like that of server-side rendering for a SPA. And that easy tooling is riddled with bugs, megabytes of node_modules, and might invite you to start involving more complexity with tech such as ‘cloud workers’, but people don’t find out until they are way too deep in the Kool-Aid. Many don’t seem to see this issue because influencers are pushing this stuff to get GitHub stars and have, ironically, gotten all of the top ranks when searching for a solution (or people were asking the wrong questions without knowing).

                            2. 3

                              Not the poster you’re responding to but it might be because SSR is a fairly natural leap from SPA-style apps. They might also be implying that it’s my fault, which would be nice, but unfortunately isn’t the case.

                          1. 7

                            It’s not quite the SQLite bit, but wow Windows 10 get your shit together.

                            1. 2

                              I wonder if part of this is that apparently Window’s anti-virus blocks on every write call. I’d be interested to see how different these benchmarks are with AV disabled.

                            1. 2

                              I wonder what the legalities are around attacking the bitcoin blockchain? I was having a discussion with a friend of mine, after I posted this comment to an HN thread. My friend’s take was essentially: “You’re flirting with eco-terrorism, I don’t want to see you get arrested.”

                              Of course, flooding the blockchain with illegal content is totally out of the question for someone who wants to keep it legal, and I obviously do. How about other sorts of attacks, be they technical or financial?

                              1. 4

                                Just as an idle musing, I’ve been thinking recently that it would be interesting to fuzz common mining software for buffer overflows, specifically ones exploitable using a payload as part of a transaction. That might escalate into something interesting.

                                The ASICs are the real problem I guess, but it would be interesting to see if they have a similar sort of vulnerability.

                                Entirely hypothetical right now and potentially impossible (I’m not an expert, just a muser), but maybe this sort of attack pathway could be used to force a hard fork, which helps to destabilise it.

                                1. 2

                                  My friend’s take was essentially: “You’re flirting with eco-terrorism, I don’t want to see you get arrested.”

                                  Honestly given the sorts of people who use bitcoin for illegal activities, being arrested wouldn’t be my biggest fear.

                                  That said, I think flooding the network with transactions would make it difficult to use. You’d be burning money in transaction fees though.

                                1. 1

                                  Does nix have something similar to the python context managers, that deletes packages after the operation is done?

                                  For example:

                                  with install(cerbot) as cb:
                                      cb update certificate
                                  # at this point certbot is uninstalled
                                  
                                  1. 9

                                    I have this function in my shell config:

                                    using () {
                                            nix-shell -p $1 --run zsh
                                    }
                                    

                                    That lets me run using nmap in my terminal, and it will be installed and available as long as that terminal is open.

                                    It’s not eagerly uninstalled, but I know that I can run nix garbage-collect if I ever want to cleanup tools that I’m not using regularly, and in the meantime using will pick it up automatically.

                                    I find this super helpful for one-off utilities, it’s effectively npx for the shell in general.

                                    It would be pretty trivial to extend this command to remove the package after zsh exits though!

                                    1. 4

                                      Yes! nix-shell -p python3 python3Packages.requests python3Packages.pip python3Packages.readchar python3Packages.jinja2 will open up a shell that contains python3 and all those libraries, when you exit the shell everything is “uninstalled” from your system. This is exactly what I do to avoid cluttering my machine with binaries and libraries I only use once. It’s also very useful to workaround our universe of conflicting software packages, for example if you have to run some scripts in python2 and others in python3.

                                      I say “uninstalled” because, in reality, there’s a big cache so data isn’t actually deleted for a while.

                                      1. 2

                                        It will not remove it from disk, but it will remove it from the environment (so you will now be able to call it) with nix-shell.

                                      1. 2

                                        Nice!

                                        I have a similar setup, but mine boots straight into my home tmux session, and then I have a hot key bound to open a session switching script in a pane.

                                        The session switcher makes a list of active sessions and combines that with all the folders from my projects directory, which is then piped into fzf.

                                        This is nice because I switch projects a lot, and I enjoy not having to care about the distinction of whether a session is running or not.

                                        I also have a little bash function called clone that takes a URL to a git repo, makes a new session with that name and then clones it into my project folder.

                                        With that setup I’ve found it’s generally faster to clone and run ag to search a repo than to use Github search.

                                        1. 7

                                          I share some of the author’s skepticism about React’s Concurrent Mode.

                                          I have personally shipped schedulers for complex, interactive JavaScript apps. I think it’s a useful technique, but it’s going to be extremely difficult to build something that doesn’t have easily accessible terrifying edge cases.

                                          I think a relevant piece of precedent is the introduction of Hooks to React. Hooks are a completely unsound abstraction that will break if the order of your hook calls change. To mitigate this, the React team recommends using a lint rule that prevents hook calls in asynchronous code or closures, among other places.

                                          However, this lint rule itself still has edgecases, most notably around module boundaries.

                                          In the past, it seems like the React team’s desire to ship shiny new features has outweighed their desire to produce sound abstractions. It wouldn’t surprise me if Concurrent Mode is released alongside a bizarre hack that enables it to function at all, like a babel transform that restructures your render functions into smaller units to enable interruption.

                                          In any case, I wish them all the best. Maybe they have some tricks up their sleeves, like authoring the hard parts in a language with a more useful type system.

                                          1. 3

                                            Hooks are a completely unsound abstraction that will break if the order of your hook calls change.

                                            this!!! I was so confused when hooks were announced. I suppose a pro of hooks is that it consildated the API for bad decisions™ compared to the plethora of state management libraries. but then we just ended up with more plethora on top of hooks!!!

                                            1. 3

                                              I don’t know. Hooks have nuances, but so do class-based components. And I imagine any UI framework does.

                                          1. 22

                                            I agree lots of people don’t, because they never even bother to learn anything past GRANT ALL…

                                            So, who out there has used these features?

                                            We use PG’s row-level security exclusively. It is 100% worth the introduction pain. Every user of our software has their own PG DB login, and that’s how they login to the application.

                                            How did that impact your application/environment?

                                            The application does only 1 thing with access control, what shows up on the menus(well and logging in). 100% of the rest of the app is done via PG RLS, and the app code is a bunch of select * from employees; kind of thing.

                                            What have you used them for?

                                            Everything, always! :) lol. (also see next answer)

                                            Do they provide an expected benefit or are they more trouble than they’re worth?

                                            When we got a request to do a bunch of reporting stuff, we connected Excel to PG, had them login with their user/password to the PG DB, and they were off and running. If the user knows SQL, we just hand them the host name of the PG server, and let them go to town, they can’t see anything more than the application gives them anyway.

                                            When we added Metabase, for even more reporting, we had to work hard, and added a reporting schema, then created some views, and metabase handles the authorization, it sucks. Metabase overall is great, but It’s really sad there isn’t anything in reporting land that will take advantage of RLS.

                                            How did you decide to use them?

                                            When we were designing the application, PG was just getting RLS, we tried it out, and was like, holy cow.. why try to create our own, when PG did all the work for us!

                                            Trying to get access control right in an application is miserable.

                                            Put permissions with the data, you won’t be sorry.

                                            1. 6

                                              Doesn’t this require a lot of open connections? IME, postgres starts to struggle past a couple of hundred open connections. Have you run into that at all?

                                              1. 5

                                                If you run everything inside of transactions you can do some cleverness to set variables that the RLS checks can refer to, emulating lots of users but without requiring more connections.

                                                1. 2

                                                  See my other comment, but you don’t have to work quite that hard, PG has a way now to become another user.

                                                  see: https://www.postgresql.org/docs/12/sql-set-session-authorization.html

                                                  1. 2

                                                    How many users does this support? I might be being overly cautious, but we have been told to look at user counts in the millions.

                                                    1. 2

                                                      We are an internal staff application, we max around 100 live open DB connections, so several hundred users. This runs in a stable VM with 32GB ram and < 1TB of data. We will never be a Google or a Facebook.

                                                      One can get really far by throwing hardware at the problem, and PG can run on pretty big hardware, but even then, there is a max point. Generally I recommend not optimizing much at all for being Google size, until you start running into Google sized problems.

                                                      Getting millions of active users out of a single PG node would be hard to do, regardless of anything else.

                                                2. 2

                                                  In our experience, the struggle is around memory, PG connections take up some memory, and you have to account for that. I don’t remember the amount per connection, but this is what I remember.

                                                  It’s not entirely trivial, but you can re-use connections. You authenticate as a superuser(or equivalent) send AUTH or something like that after you connect, to lazy to go look up the details.

                                                  We don’t currently go over about 100 or so open active connections and have no issues, but we do use pgbouncer for the web version of our application, where most users live.

                                                  EDIT: it’s not AUTH but almost as easy, see: https://www.postgresql.org/docs/12/sql-set-session-authorization.html

                                                3. 3

                                                  How do RLS policies impact performance? The Postgres manual describes policies as queries that are evaluated on every returned row. In practice, does that impact performance noticeably? Were there gotchas that you discovered and had to work around?

                                                  1. 3

                                                    Heavily.

                                                    It is important to keep your policies as simple as possible. E.g. if you mark your is_admin() as VOLATILE instead of STABLE, PG is going to happily call it for every single row, completely destroying performance. EXPLAIN is your best friend.

                                                    But even then, some queries are performed needlessly. Imagine you use transitive ownership. For example Users own Boxes, Boxes contain Widgets. When you want to determine what Widgets can User manipulate, you usually cache the User-Boxes set at the application server level and query “downwards”. With RLS, you need to establish a link between the Widget and User, joining over Boxes “upwards” as there is no cache.

                                                    The real problem here is that with sufficiently large schema, the tools are lacking. It’s really inconvenient to develop within pgAdmin4, away from git, basically within a “live” system with its object dependencies and so on.

                                                    1. 2

                                                      It can, as I mentioned in my other comment in this thread, we have only run into a few instances where performance was an issue we had to do something about.

                                                      As for tools, We use liquibase[0], and our schema’s are in git, just like everything else.

                                                      0: https://www.liquibase.org/

                                                      1. 1

                                                        I’ll check it out.

                                                        1. 1

                                                          How does the Liquibase experience compare to something like Alembic or Django migrations?

                                                          The main difference I see is whether your migrations tool is more tightly coupled to your app layer or persistence layer.

                                                          With Alembic you write migration modules as imperative Python code using the SQL Alchemy API. It can suggest migrations by inspecting your app’s SQL Alchemy metadata and comparing it to the database state, but these suggestions generally need refinement. Liquibase appears to use imperative changesets that do basically the same thing, but in a variety of file formats.

                                                          1. 2

                                                            I’m not very familiar with alembic or django migrations. Liquibase(LB) has been around a long time, it was pretty much the only thing doing schema in VCS back when we started using it.

                                                            Your overview tracks with my understanding of those. I agree LB doesn’t really care about the file format, you can pick whatever suits you best.

                                                            The LB workflow is pretty much:

                                                            • Figure out the structure of the change you want in your brain, or via messing around with a development DB.

                                                            • Open your favourite editor, type in that structure change into your preferred file format.

                                                            • Run LB against a test DB to ensure it’s all good, and you didn’t mess anything up.

                                                            • Run LB against your prod DB.

                                                            • Go back to doing whatever you were doing.

                                                      2. 1

                                                        We actually use an OSS extension veil[0], and while performance can be an issue, like @mordae mentions, if you are careful about your use, it’s not to bad. We have only had a few performance issues here and there, but with explain and some thinking we have always managed to work around it without much hassle. You absolutely want indexes on the things you are using for checking permissions with.

                                                        Veil makes the performance a lot less painful, in our experience.

                                                        0: https://github.com/marcmunro/veil Though note, veil2 is the successor and more relevant for new implementations, that we don’t currently use(and have no experience with): https://github.com/marcmunro/veil2

                                                        Veil2 talks about performance here in 23.1: https://marcmunro.github.io/veil2/html/ar01s23.html

                                                      3. 3

                                                        Same here, I used row level security everywhere on a project and it was really great!

                                                        1. 2

                                                          One mistake I’ve made is copy-pasting the same permission checks on several pages of an app. Later I tried to define the permissions all in one place, but still in the application code (using “django-rules”). But you still had to remember to check those permissions when appropriate. Also, when rendering the page, you want to hide or gray-out buttons if you don’t have permission on that action (not for security, just niceness: I’d rather see a disabled button than click and a get 403).

                                                          With row-level permissions in the DB, is there a way to ask the DB “Would I have permission to do this update?”

                                                          1. 2

                                                            Spitballing but maybe you could try run the query in a transaction and then roll it back?

                                                            Would be a bit costly because you’d have to run the query twice, once to check permissions and then again to execute, but it might be the simplest solution.

                                                            1. 1

                                                              Maybe what you need is to define a view that selects the things you can update and use that view to define the RLS. Then you can check whether the thing you want to update is visible through the view.

                                                              1. 1

                                                                With row-level permissions in the DB, is there a way to ask the DB “Would I have permission to do this update?”

                                                                Yes, the permissions are just entries in the DB, so you can query/update whatever access you want(provided you have the access to view/edit those tables).

                                                                I’m writing this from memory, so I might be wrong in the details… but what we do is have a canAccess() function that takes a row ID and returns the permissions that user has for that record. So on the view/edit screens/pages/etc, we get the permissions as well returned to us. So it’s no big deal to handle.

                                                            2. 1

                                                              Follow question: How did you handle customers (accidentally even) writing expensive sql queries?

                                                              1. 2

                                                                We would admonish them appropriately :) Mostly the issue is making sure they know about the WHERE clause. It hasn’t been much of an issue so far. We have _ui table views, that probably do 90% of what they are wanting anyways, and they know to just use those most of the time. The _ui views flatten the schema out, to make the UI code easier, and use proper where clauses and FK joins, to minimize resource usage.

                                                                If our SQL user count grew enough that we couldn’t handle it off-hand like this, we would probably just spin up a RO slave mirror, and let them battle each other over resources and otherwise ignore the problem until we got complaints enough to upgrade resources again.

                                                            1. 2

                                                              Looks like development on this went from 2010-2013, and no activity since then.

                                                              1. 4

                                                                If you want to see more development, the successor fork (Dark Days Ahead) has over 81,000 commits at the time of writing, from more than 1100 people.

                                                              1. 26

                                                                inheritance is something that makes your code hard to understand. Unlike function, which you can read just line by line, code with inheritance can play “go see another file” golf with you for a long time.

                                                                This isn’t an argument against inheritance, it’s an argument against modularity: Any time you move code out of inline you have the exact same “problem” (to the extent it is a problem) and you can only solve it the same way, with improved tooling of one form or another. ctags, for example, or etags in Emacs.

                                                                1. 31

                                                                  Inheritance has this problem to a much larger degree because of class hierarchies. Tracing a method call on a class at the bottom of the tree, requires checking every parent class to see if its overridden anywhere. Plain function calls don’t have that problem. Theres only a single definition.

                                                                  1. 7

                                                                    Plain function calls don’t have that problem. Theres only a single definition.

                                                                    Unless we start using higher order functions when the function is passed around as a value. Such abstraction creates the exact same problem, only now it’s called “where does this value originate from”.

                                                                    1. 5

                                                                      Yes, which is why higher order functions are another tool best used sparingly. The best code is the most boring code. The most debuggable code is the code that has the fewest extension points to track down.

                                                                      This is, of course, something to balance against debugging complicated algorithms once and reusing them, but it feels like the pendulum has swung too far in the direction of unwise extensibility.

                                                                      1. 4

                                                                        For extra fun, use higher-order functions with class hierarchies!

                                                                      2. 4

                                                                        The best is python code where the parent class can refer to attributes only created in child classes. There are equivalents, but less confusing, in languages like Java.

                                                                        1. 1

                                                                          Isn’t the example in the linked article doing exactly that?

                                                                          1. 2

                                                                            Okaaaay… what’s self.connect doing? Ah, it raises NotImplementedError. Makes sense, back to SAEngine:

                                                                            Not exactly. :)

                                                                            1. 1

                                                                              Check out Lib/_pyio.py (the Python io implementation) in CPython for lots of this.

                                                                          2. 1

                                                                            The overrides is mostly for modularity and reduce code duplication. Without classes, you might either end up with functions with tons of duplicated code, or tons of functions having a call path to simulate the “class hierarchies”. And yes, it’s going to make the code harder to read in some cases, but it also makes the code much shorter to read.

                                                                            1. 6

                                                                              Without classes, you might either end up with functions with tons of duplicated code

                                                                              Why? There is literally no difference in code re-using between loading code through inheritance vs function calls, apart from possibly needing to pass a state to a function, that could otherwise be held in class instances (aka objects). this is certainly less than class definition boilerplates.

                                                                              or tons of functions having a call path to simulate the “class hierarchies”

                                                                              The call chain is there in both cases. It’s just that in the class-based approach it is hidden and quickly becomes a nightmare to follow. Each time you call a method statically or access a class atribute, you are basically pointing to a point in your code that could be hooked to different points in the hierarchy. This is a problem. People don’t think it is a big deal when they write a simple class and know it well, because the complexity is sneaky. Add another class and all of the sudden you brought in a whole other hierarchy into the picture. Each time you read “this” or “instance.something”, you’re up for am hunt. And each other hierarchy you bring into the picture increases complexity geometrically. Before you know, the project is unmanageable, the ones writing it went on to some green field project, doing a similar mess again for some poor soul to struggle with after them.

                                                                              And yes, it’s going to make the code harder to read in some cases, but it also makes the code much shorter to read

                                                                              It doesn’t really. People fall for this because you can instantiate a class and get a bunch of hidden references that are all available to you at will without you need to explicitly pass them to each method, but you only get this through defining a class, which is way more verbose than just passing the references you need.

                                                                              All that said, what classes do offer in most languages is a scope that allows for fine grain control of data lifecycle. If we remove inheritance, then class members are akin to use global variables in non-OOP languages. But you can create as many scopes as you want. I which languages like python would do this as, for the same reason as OP, I suffer from working with OOP codebases.

                                                                              1. 4

                                                                                You make it sound like inheritance is the only way to reduce code duplication. In my experience that is simply not true, you can always use composition instead. E.g. Haskell doesn’t support inheritance or subtyping and you still get very compact programs without code duplication.

                                                                                1. 5

                                                                                  Without classes, you might either end up with functions with tons of duplicated code, or tons of functions having a call path to simulate the “class hierarchies”

                                                                                  This is only true in my experience if you’re trying a functional approach with an OO mindset. There are other ways to solve problems, and many of them are far more elegant in languages designed with functional programming as the primary goal.

                                                                              2. 5

                                                                                When you move a bit of code out of your file it’s not going to call back function from the first file. You going to even make sure this is the case, that there is no circular dependency, because it makes (in cases when a language allows to make you one) code harder to read. In case of inheritance, those games with calling everything around is just normal state of things.

                                                                                Of course, example in the article is small and limited, because pulling a monster from somewhere is not going to make it more approachable, but surely you’ve seen this stuff in the wild.

                                                                                1. 4

                                                                                  You might do that, in the same way that you might carefully document your invariants in a class that allows inheritance, mark methods private/final as needed, etc. But you also might not do that. It sounds a bit as if you’re comparing well-written code without inheritance to poorly written code with it.

                                                                                  Not that there isn’t lots of terrible inheritance based code. And I’d even say inheritance, on balance, makes code harder to reason about. However, I think that the overwhelming issue is your ability to find good abstractions or ways of dividing up functionality–the choice of inheritance vs. composition is secondary.

                                                                                  1. 2

                                                                                    It’s just that without inheritance it’s easier to make good abstractions. Inheritance affords you to do wrong thing easily, without any friction - just read a good article about that few weeks ago.

                                                                                2. 4

                                                                                  Interesting article from Carmack about inlining everything:

                                                                                  http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html

                                                                                  1. 4

                                                                                    This isn’t an argument against inheritance, it’s an argument against modularity: Any time you move code out of inline you have the exact same “problem” (to the extent it is a problem) and you can only solve it the same way, with improved tooling of one form or another. ctags, for example, or etags in Emacs.

                                                                                    Not really, including code via accessing a class or object member forces you to manually go figure out which implementation is used, or where the implementation in a web of nested namespaces. In the case of function, each symbol is non-ambiguous. This is a big deal. If you have types A and B, with A having an attribute of the type B, each of these types containing a 3 level hierarchy, and you call A.b.some_b_method(). That could be defined in 9 different places, and if it is, you need to figure out which that symbol resolves to. This is a real problem.

                                                                                    1. 2

                                                                                      This isn’t an argument against inheritance, it’s an argument against modularity:

                                                                                      Yeah, all code should be in a single file anyway. No more chasing of method definitions across multiple files. You just open the file and it’s all there…

                                                                                      1. 2

                                                                                        Any form of modularity should be there to raise the level of abstraction. ie. Become a building block, that is solid (pun intended) firm and utterly reliable, that you can use to understand the higher layers.

                                                                                        You can peer inside the building block if you need to, but all you need to understand about it, to understand the next level up, is what it does, not how it does it.

                                                                                        Inheritance is there to allow you to know that “all these things IS A that”. ie. I can think of them and treat all of them exactly as I would treat the parent class. (ie. The L in SOLID)

                                                                                        I can utterly rely on the fact that the class invariant for the superclass holds for all subclasses. ie. The subclasses may guarantee other things, but amongst the things they guarantee, is that the super class’s class invariant holds.

                                                                                        I usually write a class invariant check for every class I write.

                                                                                        I then invoke it at the end of the constructor, and the beginning of the destructor, and at the start and end of every public method.

                                                                                        As I become more convinced of the correctness of what I’m doing, I may remove some for efficiency reasons. As I become more paranoid, I will add some.

                                                                                        In subclasses, the class invariant check always invokes the parent classes invariant check!

                                                                                      1. 2

                                                                                        https://teachyourselfcs.com/

                                                                                        Found this site some time ago. It can be helpful for you.

                                                                                        1. 6

                                                                                          Not sure why this would be their first port of call when it essentially covers what they’ve just studied in their degree.

                                                                                          1. 1

                                                                                            Studying it once isn’t enough to act natural when pretending you didn’t memorize your whiteboard questions. Gotta hit that leetcode.com too.

                                                                                            1. 2

                                                                                              Cheers you’ve just reminded me why I have no inclination to throw myself into the big tech meat grinder when I graduate.

                                                                                              1. 2

                                                                                                OK, upon hearing you haven’t graduated yet, I’ll amend my first statement.

                                                                                                I may be exaggerating. I’m a person who has A) done (and failed) at least 50 leetcode-style whiteboard interviews, B) had (so far in ~7 years) rewarding jobs without leetcode interviews, where I had good coworkers and learned lots.

                                                                                                Maybe by the 20th time I had to draw a graph or binary tree in a Google doc (like actually typing out symbols like A—–B—–C to draw edges and vertices) to answer a network flow question in 30 min over a scratchy phone line while “vocalizing my thought process”, it got pretty old.

                                                                                                I know a common feel-good statement around leetcode is that you’ll only get them when you’re trying to get into the best of the best. That’s a joke too, leetcode will surprise you anywhere and everywhere. I’ve had the most absurd questions for run of the mill “DevOps Engineer” jobs at companies that were most definitely not big tech (or even close).

                                                                                                tl;dr: fuck leetcode, hate leetcode, but study leetcode. Just in case

                                                                                                1. 1

                                                                                                  I’ve been working as a developer for six years, I’ve probably interviewed with 20 companies across a few job hunts. I only had to whiteboard a few times and it was always about architecture rather than algorithms.

                                                                                                  Granted I haven’t interviewed with any of the biggest name brand companies, but it’s definitely not a common phenomena for your average interview in my experience.

                                                                                                  Most interviewers are aware of the “invert a b-tree on a whiteboard” meme, those who persist don’t have many excuses.

                                                                                          1. 4

                                                                                            This is cool but more and more I wish functional languages would have a standard library trait/typeclass called something like FromLiteral, with the signature FromLiteral a => String -> Result a.

                                                                                            Then, if you had a string literal in your code but the compiler infers it should be a Regex, it calls the FromLiteral implementation for Regex at compile time, and either rewrites the generated program with the successful output, or throws an error.

                                                                                            1. 1

                                                                                              Using [] for generics instead of <> shuts down this possibility for good, and encourages the use of standard method call syntax for these usecases instead:

                                                                                              Array(1, 2, 3)        /* instead of */   [1, 2, 3]
                                                                                              someList(0)           /* instead of */   someList[0]
                                                                                              array(0) = 23.42      /* instead of */   array[0] = 23.42
                                                                                              map("name") = "Joe"   /* instead of */   map["name"] = "Joe"
                                                                                              

                                                                                              array(0) = 23.42 /* instead of */ array[0] = 23.42 feels like a very weak case to me.

                                                                                              This is not how standard method calls work in most languages, so either this is just another form of syntactic sugar which feels confusing due to overloading with how real functions and methods work, or perhaps some tortured runtime implementation where the array function returns something that is simultaneously a getter and setter depending on how it’s used?

                                                                                              If this was the point they really wanted to make perhaps array.set(0, 23.42) /* instead of */ array[0] = 23.42 would be better.

                                                                                              1. 11

                                                                                                WebAssembly. While it’s still a nascent technology that has yet to win major mindshare, the work that is being done in this area will help bring us faster, safer and more reliable web applications, as well as making a bevy of existing software more useful and portable.

                                                                                                1. 4

                                                                                                  Nice write up! I do understand stringify better now :)

                                                                                                  In the first example, I would agree with @pilif that the regex is a bit of a dangerous solution. I think I’d go for something like this:

                                                                                                  const mapObj = {
                                                                                                      _id: "id",
                                                                                                      created_at: "createdAt",
                                                                                                      updated_at: "updatedAt",
                                                                                                  };
                                                                                                  
                                                                                                  Object.entries(todayILearn).reduce((obj, [key, value]) => {
                                                                                                      key = mapObj[key] || key;
                                                                                                      return {
                                                                                                          ...obj,
                                                                                                          [key]: value,
                                                                                                      };
                                                                                                  }, {});
                                                                                                  
                                                                                                  
                                                                                                  1. 2

                                                                                                    Nice functional solution. I write a lot of JavaScript at the moment that needs to be fast and understandable by people without too much JS experience, so my take would be something like this:

                                                                                                    function replaceKeys(keyMap, obj) {
                                                                                                      const output = {};
                                                                                                    
                                                                                                      for (let key in obj) {
                                                                                                        const newKey = keyMap[key] || key;
                                                                                                    
                                                                                                        output[newKey] = obj[key];
                                                                                                      }
                                                                                                    
                                                                                                      return output;
                                                                                                    }
                                                                                                    

                                                                                                    This wouldn’t handle every case, off the top of my head it wouldn’t handle symbols or a key of zero, but for the author’s usecase it would work fine.

                                                                                                  1. 12

                                                                                                    I’ve come around on Nix and I think I like it now that I really understand it. I’m gonna do some more research and make a write up of the things that changed my mind about it.

                                                                                                    1. 4

                                                                                                      Who are you and what have you done to the real @cadey?!

                                                                                                      Seriously though, I hope you find the time for the write-up. I’ve been following the discussions around nix and friends with interest.

                                                                                                      1. 5

                                                                                                        The real magic comes from tools like niv that allow you to pull in dependencies from other places. For example a tarot reading program in Elm that I still haven’t finished depends on gruvbox-css normally, and it also depends on quickserv when run in a docker image.

                                                                                                        This lets me have a derivation that’s like:

                                                                                                        { system ? builtins.currentSystem }:
                                                                                                        
                                                                                                        let
                                                                                                          pkgs = import <nixpkgs> { inherit system; };
                                                                                                          sources = import ./nix/sources.nix;
                                                                                                          quickserv = import sources.quickserv { };
                                                                                                          callPackage = pkgs.lib.callPackageWith pkgs;
                                                                                                          tarot = import ./default.nix { };
                                                                                                        
                                                                                                          dockerImage = pkg:
                                                                                                            pkgs.dockerTools.buildImage {
                                                                                                              name = "xena/tarot";
                                                                                                              tag = pkg.version;
                                                                                                        
                                                                                                              contents = [ pkg quickserv ];
                                                                                                        
                                                                                                              config = {
                                                                                                                Cmd = [ "/bin/quickserv" ];
                                                                                                                WorkingDir = "/public";
                                                                                                              };
                                                                                                            };
                                                                                                        
                                                                                                        in dockerImage tarot
                                                                                                        

                                                                                                        And then a 39 MB docker image is emitted that is mostly the image assets for the application.

                                                                                                        nix-shell and direnv combined with lorri also are shockingly useful. This lets me have each project use its own tooling (compilers, helper tools, etc) without having to pollute my global environment with them. Consider this shell.nix from a Rust project:

                                                                                                        let
                                                                                                          pkgs = import <nixpkgs> { };
                                                                                                          sources = import ./nix/sources.nix;
                                                                                                        in pkgs.mkShell {
                                                                                                          buildInputs = [
                                                                                                            pkgs.rustc
                                                                                                            pkgs.rustfmt
                                                                                                            pkgs.cargo
                                                                                                            pkgs.openssl
                                                                                                            pkgs.pkg-config
                                                                                                            pkgs.niv
                                                                                                            pkgs.rls
                                                                                                          ];
                                                                                                        }
                                                                                                        

                                                                                                        When combined with direnv-mode, this gives my emacs install access to the nix-provided rustc, rustfmt tool, rust language server and the like without having to download gigabytes of data into ~/.rustup (though if you really want rustup you can nix-env -i rustup).

                                                                                                        The biggest problem this has is documentation and tooling. The command line tools make emerge and pacman look user-friendly. But overall I think it’s a tool that’s worth learning for its own merits. It could be better, but so could nearly all of the tools and the like we use on a regular basis.

                                                                                                        1. 3

                                                                                                          that allow you to pull in dependencies from other places

                                                                                                          You are probably well aware there are many tools that can do this. So my question is: why are you actually using Nix? Just to play and discover? :) I feel like this reason alone is not enough to justify it. Just curious about your thoughts.

                                                                                                          1. 3

                                                                                                            I love this question! It’s one I actually ask myself when I come to frustration when using nixos (purely to learn and play). nix-shell -p nodejs is nice to quickly get a throwaway env but then I’m left thinking I did something the “wrong” way.

                                                                                                            1. 2

                                                                                                              For that, I just use nvm (node version manager)

                                                                                                              1. 3

                                                                                                                Yes but Nix allows you to do this for more than just Node. I’m currently using it for Rust, Go, Elm and Haskell. This integration means that I don’t have to install n tools for all n languages that I use in personal and professional projects.

                                                                                                                1. 2

                                                                                                                  How is this any different from using Docker? The only thing I can think of is duplication at the byte level, but this can be fixed with some deduplication-enabled file system?

                                                                                                                  1. 2

                                                                                                                    Nix is actually less complicated than docker for my use cases. I have a docker image with my setup, but compare this:

                                                                                                                    $ cd ~
                                                                                                                    $ which cargo
                                                                                                                    <exit code 1 because cargo isn't on the path>
                                                                                                                    $ cd ~/code/Xe/withinbot
                                                                                                                    direnv: loading ~/code/Xe/withinbot/.envrc
                                                                                                                    $ which cargo
                                                                                                                    /nix/store/lm64cjp5c5kc772rpmf88q22s8lr2zhi-rustc-1.40.0/bin/cargo
                                                                                                                    

                                                                                                                    And I can pin exact versions of things like nodejs via nix-shell invocations:

                                                                                                                    $ nix-shell -p nodejs-10_x
                                                                                                                    [nix-shell]$ which node
                                                                                                                    /nix/store/n3d6gbfbzbdw65smn5cj83s8rb1jwld8-nodejs-10.19.0/bin/node
                                                                                                                    
                                                                                                                    $ nix-shell -p nodejs-12_x
                                                                                                                    [nix-shell]$ which node
                                                                                                                    /nix/store/5ca56xq1p92pcpmzrw3lwls0pnqg83dx-nodejs-12.15.0/bin/node
                                                                                                                    
                                                                                                                    $ nix-shell -p nodejs-13_x
                                                                                                                    [nix-shell]$ which node
                                                                                                                    /nix/store/0fj8xvbdk7j79pyhif44xz2mkl6vyv0b-nodejs-13.8.0/bin/node
                                                                                                                    

                                                                                                                    Or in a project-local shell.nix:

                                                                                                                    let
                                                                                                                      pkgs = import <nixpkgs> {};
                                                                                                                    in
                                                                                                                    pkgs.mkShell {
                                                                                                                      buildInputs = [
                                                                                                                        pkgs.nodejs-10_x # or pkgs.nodejs-12_x or pkgs.nodejs-13_x
                                                                                                                      ];
                                                                                                                    }
                                                                                                                    
                                                                                                                    $ nix-shell
                                                                                                                    [nix-shell]$ which node
                                                                                                                    /nix/store/n3d6gbfbzbdw65smn5cj83s8rb1jwld8-nodejs-10.19.0/bin/node
                                                                                                                    

                                                                                                                    And then I never have to modify my global $PATH or deal with a version manager, because the package manager handles that for me.

                                                                                                                    1. 1

                                                                                                                      Your first example: I’d just create a Docker image with that cargo version using rustup.

                                                                                                                      Your second example: Again just select any nodejs version and swap between Docker envs.

                                                                                                                      As it is right now, I bet someone could create Nix-like behavior with just some shell and Docker.

                                                                                                                    2. 1

                                                                                                                      Instead of pulling down a full operating system, with Nix you can just pull down the exact cached binaries and dependencies you need, which are stored and accessed in such a way that you know they won’t interfere with other applications using the same deps.

                                                                                                                      So Nix doesn’t actually handle any of the process isolation/network bridging/volume sharing stuff that docker does, it’s aimed at a much simpler layer.

                                                                                                                      Nix can be a really potent tool when combined with Docker, either through generating docker images or using Nix to install and configure packages inside of Docker.

                                                                                                                      While superficially they share some use cases (e.g. I want to run this application and have some other app take care of the details), they operate at very different layers with different ideals in mind.

                                                                                                                      1. 1

                                                                                                                        Superficially?

                                                                                                                        Programs are collections of logic.

                                                                                                                        Docker and Nix will without doubt, have a set of intersecting collections of logic.

                                                                                                                        The question is, which intersects the other more? :)

                                                                                                                        Here I think Docker can do all that Nix can, and more. It just operates at a higher level.

                                                                                                                        Pulling down an operating system is a detail, just like however Nix manages things.

                                                                                                                        As I said: don’t be surprised if we eventually see a Docker-based Nix-like solution written in Python or Bash. There are ways to make a system like this efficient.

                                                                                                                        Interesting to think about :) Thanks

                                                                                                                        1. 1

                                                                                                                          This discussion was interesting to read. The argument for Nix seems like the same argument against node_modules – every use case gets covered if you just unpack an already correct state of affairs into your environment every time you need it (Docker), but arguably it would be way less resource-heavy to not create identical copies of common dependencies every time (Nix). Presumably there’s some middle ground between Docker’s simplicity and Nix’s resource efficiency but other than those factors I can’t see problems with either approach.

                                                                                                              2. 3

                                                                                                                I’m aware I can pull in things with any choice of tools. I’m using Nix for at least because it lets me deduplicate the dependency builds across multiple projects. This also lets me avoid git submodules or similar (I personally see git submodules as more pain than they’re worth).

                                                                                                                If anything, I’m using Nix for this because I can and you can’t stop me (muahahahahahahaha, etc). But really it’s to evaluate it as a building block for deploying my stuff into production as docker images. The docker images Nix makes are really tiny.

                                                                                                        1. 27

                                                                                                          Having menus and toolbars be completely keyboard accessible is huge. Being partially blind (and fine/gross motor impaired), the mouse is an absolute 1000% productivity killer.

                                                                                                          Being able to drive my computing universe from the keyboard as much as possible is critical. This is, FWIW a big reason that I fell in love with the Mac years ago when they actually gave a crap about HID guidelines.

                                                                                                          Linux is getting better and better in this regard, and KDE has always been pretty good.

                                                                                                          1. 1

                                                                                                            Sadly still no support for vertical tabs. :-/

                                                                                                            1. 3

                                                                                                              Every Firefox post feels like groundhog day with this vertical tabs stuff.

                                                                                                              1. 2

                                                                                                                :-)

                                                                                                              2. 3

                                                                                                                I have been using vertical tabs in the latest Firefox for quite a long time now, there was only maybe a week after the release of the breaking version where there was no good solution available. Not sure what else you want?

                                                                                                                1. 1

                                                                                                                  I try those extensions from time to time. They are all terrible.

                                                                                                                  I rely on vertical tabs to de-clutter my UI and free up some vertical space. All the extensions are a complete failure in this regard:

                                                                                                                  • Their horizontal tab bar still takes up space.
                                                                                                                  • Their sidebar headers add additional clutter.
                                                                                                                  • Their visual metaphor is wrong: the location bar shouldn’t “own” the tab bar, it belongs to a tab.

                                                                                                                  Compare my user interface to whatever fresh hell Firefox extensions do these days.

                                                                                                                  1. 1

                                                                                                                    It’s not ideal, but you can hide the horizontal tab bar and the sidebar headers with userChrome.css. The Firefox team has said they are going to provide an API for extensions hiding and showing the tab bar in the future. If I understand correctly, it’s hung up a bit because of the security implications of letting an extension do this.

                                                                                                                    1. 1

                                                                                                                      An API for extensions and struggling with security implications sounds like taking the wrong path. Why not add an autohide option, which should be good enough that you’d turn it on and never show tabs if you use an extension?

                                                                                                                      While at it, take the time to allow rows of tabs as a built-in feature and not care about APIs.

                                                                                                                      But no, none of this will probably ever happen.

                                                                                                                      1. 1

                                                                                                                        It’s not ideal, but you can hide the horizontal tab bar and the sidebar headers with userChrome.css.

                                                                                                                        This possibility will go away.

                                                                                                                        Firefox 69 already requires an opt-in in about:config: toolkit.legacyUserProfileCustomizations.stylesheets: true

                                                                                                                        The Firefox team has said they are going to provide an API for extensions hiding and showing the tab bar in the future.

                                                                                                                        They say a lot, but even if they addressed this issue, there are still multiple other deal-breakers, most of which will never be addressed.

                                                                                                                  2. 2

                                                                                                                    Wasn’t that implemented as an extension in the old XUL days?

                                                                                                                    Actually looks like there’s a new one too: https://addons.mozilla.org/en-US/firefox/addon/vertical-tabs-reloaded/

                                                                                                                      1. 3

                                                                                                                        The extension is little more than a proof-of-concept, but a good demonstration of all the things that still don’t work (and probably never will) since they broke the old API and killed off their own Tab Center experiment.

                                                                                                                        The state of vertical tabs in Firefox is the equivalent of Go’s “those aren’t angle brackets, they’re characters from the Canadian Aboriginal Syllabics block”.

                                                                                                                        1. 2

                                                                                                                          Yeah they lots a lot of things in the transition to the new API, but given the insecure nature of XUL I can understand why they made that difficult call.

                                                                                                                          I’m still pining for the loss of It’s All Text :)

                                                                                                                          1. 2

                                                                                                                            It comes down to Firefox being obsessed with their 100% fit on their mythological “average user” (that doesn’t exist), not their inability to address this issue.

                                                                                                                            In the end I transitioned to Vivaldi, who – despite the much smaller team – seem to manage to simply ship vertical tabs as a fully supported options out of the box.

                                                                                                                            It kinda sucks, because I’m adding +1 to the already concerning market-share of Chromium-based browsers, but after being a loyal Firefox user for almost 15 years – I’m sick and tired of them trying to be a Chrome look-alike.

                                                                                                                            1. 1

                                                                                                                              At least Vivaldi is open source. That’s pretty important.

                                                                                                                              I’m less concerned about the renderer and more concerned about transparency.

                                                                                                                            2. 2

                                                                                                                              In case you’re not aware of them, It’s All Text has several replacements: GhostText, Textern, Tridactyl and a few others the name of which I can’t remember.

                                                                                                                              1. 1

                                                                                                                                Tridactyl is awfully cool but also awfully invasive in that it turns your Firefox into a totally Vim style keyboard driven experience :)

                                                                                                                                Texterm has platform restrictions, and GhostText looks like it has good potential but you can’t invoke it with a keyboard shortcuts on Macs due to some firefox bug or other :)

                                                                                                                                Thanks though!

                                                                                                                            3. 1

                                                                                                                              I’m curious what you feel is missing from the tree-style-tabs extension? It seems to work fine for me, but mostly all I care about is that it’s vertical, can group tabs, and shows me what container they are in. Every now-and-then it gets a little wonky, but usually just hitting F1 to hide and show it fixes it.

                                                                                                                              1. 2

                                                                                                                                I rely on vertical tabs to de-clutter my UI and free up some vertical space. TST is a complete failure in this regard:

                                                                                                                                • Horizontal tab bar still takes up space.
                                                                                                                                • The sidebar header adds additional clutter.
                                                                                                                                • The visual metaphor is wrong: the location bar shouldn’t “own” the tab bar, it belongs to a tab.

                                                                                                                                Compare my user interface to whatever fresh hell Firefox extensions do these days.

                                                                                                                                1. 1

                                                                                                                                  Okay, so it’s non-obvious, but just add this to .mozilla/firefox/[profile.dir]/chrome/userChrome.css:

                                                                                                                                  #TabsToolbar { visibility: collapse; }
                                                                                                                                  #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] > #sidebar-header {
                                                                                                                                    visibility: collapse;  
                                                                                                                                  }
                                                                                                                                  

                                                                                                                                  And you will have something lovely like my user interface

                                                                                                                                  I agree that the location bar location isn’t perfect, but that bothers me less. it’s possible you could drag things around to fix that too, but I haven’t tried.

                                                                                                                                  1. 2

                                                                                                                                    just add this to .mozilla/firefox/[profile.dir]/chrome/userChrome.css

                                                                                                                                    This approach will stop working in the future.

                                                                                                                                    Firefox 69 already requires an opt-in in about:config: toolkit.legacyUserProfileCustomizations.stylesheets: true

                                                                                                                                    You can probably guess what happens next.

                                                                                                                                    1. 1

                                                                                                                                      hrmm, that sucks. given the popularity of this approach, i sure hope they have some migration path for users like me. if not I may have to join you in jumping ship.

                                                                                                                                      1. 1

                                                                                                                                        btw: from the bugzilla and the announcement it doesn’t sound like they plan on having this actually go away.

                                                                                                                                        You may or may not trust mozilla to not take this away, but I’m not quite jaded enough to switch browsers when they are explicitly saying they won’t remove it all together.

                                                                                                                                        1. 1

                                                                                                                                          The name of the config key contains the three things Mozilla hates most: legacy, users and customization. I don’t think it gets more upfront than that.

                                                                                                                                          I wouldn’t give it a year until the option is gone.

                                                                                                                                      2. 1

                                                                                                                                        I played a bit with chromeCSS and things turned out pretty well: https://i.imgur.com/iOgeSdQ.png

                                                                                                                                        Too bad the whole feature has a sell-by-date already. :-(