1. 3

    At home I use a Filco Majestouch 2 (tenkeyless, brown switches). At work I use something less noisy :)

    1. 6

      As someone who uses trackers on a daily basis, I have to say that’s a very solid write-up. Perhaps Jeskola Buzz could have been discussed a bit more, since it was pretty big in the late 90s/early 2000s. There’s also Buzztrax, which continues the legacy on Linux. Other than that, the article is pretty comrehensive though. There were some tracker-like editors before Ultimate Soundtracker, but that’s obviously out of scope for this one.

      There are a number of reasons why I prefer trackers over any other means of composing computer music.

      • The keyboard-driven workflow is just so much faster than clicking around with the mouse.
      • The minimalistic UI helps me focus.
      • Trackers are ubiquitous on 80s home computers and consoles. That’s great for a chiptune musician like me, because once you know one tracker, you can easily get started on another one. So I can turn most of my 8-bit junk into a musical instrument with very little learning effort.

      What’s interesting to me is that trackers actually make the music writing process more akin to programming. This becomes especially apparent in Chiptune, where you’re basically writing a list of instructions to be interpreted by the music player.

      1. 3

        The keyboard-driven workflow is just so much faster than clicking around with the mouse.

        What about trackers versus playing a MIDI keyboard? In your opinion, does entering notes in a tracker have advantages over playing them on a MIDI keyboard?

        1. 2

          Caveat, I don’t have so much experience with MIDI. For many years I was moving around a lot (aka “my home is where my laptop is”), so I never bothered carrying around a MIDI controller. Nowadays you get these really small controllers, but back in the day these things were clunky. So anyway, I’m not super qualified to answer this.

          Generally, I think the use-case is different. MIDI shines in 3 situations. a) when you have a fixed setup, eg. one DAW + a standard set of plugins that you always use. If you’re exploring many different tools and platforms, then the overhead from setting up MIDI controls is usually not worth it. b) for adding a live/human feel to an existing basic structure, ie. when you want to be less than 100% consistent/timing accurate. If you actually want to be precise, then you need to be able to play very well (which I’m not), otherwise you’re going to fiddle with correcting quantization errors a lot (unless you use your MIDI controller to enter notes/triggers on a per-step basis, in which case you might just as well use your computer’s keyboard). c) for automating sound parameters. Definitely a huge plus for “normal” computer music, for my use-case (Chiptune) it’s less relevant though.

          1. 1

            I’m confused. MIDI keyboards can feed MIDI into trackers.

            Some of them do bundle a synth, but these are usually called electronic pianos, and can accept MIDI from a tracker.

          2. 2

            Buzztrax

            Thanks for letting me learn about this LGPL’d tracker. Up until now, I only knew MilkyTracker.

            1. 3

              There’s also Radium. Haven’t used it yet, but it looks very promising in terms of bringing trackers to the next level. Back in the day I actually used Neil Sequencer, another Buzz-inspired project. Unfortunately that one is completely dead, I can’t even build it on a current system nowadays.

              Last but not least, there’s also Schism Tracker, which is to Impulse Tracker what Milky is to Fasttracker/Protracker. The .it format is more compact than .xm, so it’s often the preferred choice for making size-restricted modules.

              1. 1

                There’s also Radium.

                Which I also didn’t know, looks promising and is Open Source. Thank you!!!

                Neil Sequencer

                Seems to be GPL, but I can’t find the sources. Probably a temporal issue.

                Schism I was aware of.

              2. 2

                I have a big list of FOSS trackers that I’m trying to package for NixOS here if you’re interested: https://github.com/NixOS/nixpkgs/issues/81815

                1. 1

                  Absolutely! Thank you.

              3. 1

                I know it’s called “essential guide” but it’s a bit weird to write a history of trackers without mentioning Impulse Tracker and its descendants (Modplug, Cheesetracker, Schizm, etc).

                1. 1

                  Agreed, that’s quite an oversight. At least Modplug’s modern incarnation OpenMPT is mentioned in passing.

              1. 1

                This is a good introduction to SOLID… and also a good reminder for people like me who often forget what the I stands for (I tend to mistake it for Inversion of Control).

                1. 3

                  Lua and shell script make for amazing glue languages

                  1. 3

                    Fun fact: lead (which pipes used to be made from, hence plumbing) is “luaidhe” in Irish

                    1. 2

                      Same here. Bash -> Lua -> C, where “->” means “if the left operand isn’t enough…”

                      1. 2

                        same, but any more I tend to favor fennel + shell script

                        https://fennel-lang.org/

                      1. 12

                        Things that will keep growing : Rust, Zig, WASM.

                        Things that will make a comeback : simpler non-SPA JS frameworks with server-side rendering (Stimulus etc), and Rails by ricochet effect. A “simple, ops-less hosting” platform like Heroku or App Engine will emerge, maybe Vercel.

                        Obviously, on the hardware end: ARM laptops / deskops.

                        Things that will go down (too much complexity for nothing): Kubernetes (at least used directly).

                        Things I would like to see grow but I’m not sure: unikernels and/or lightweight VMs instead of containers (à la firecracker).

                        5G will probably change some things, too.

                        1. 4

                          Just to nitpick a bit, because this has been bothering me for a while: Conflict-free replicated datatypes aren’t a solution for conflicts.

                          Making a CRDT is easy. Whenever you have a conflict you hash both data sets and throw away the one with the lower hash. This may be stupid but it fulfills the criteria for a CRDT.

                          The conflict between the authors intentions is still there, the only thing that is conflict-free is the state of different copies of the data after all the changes have propagated. The conflict of the changes that where made is semantic in nature and probably has to be resolved at a language level and/or by a human.

                          Which is probably why Pijul ended up with counter-intuitive behavior by treating text as a graph and using that representation for conflict resolution.

                          1. 6

                            I have worked on something similar (filesystem synchronization with a CRDT model) and I agree, people misunderstand CRDTs.

                            Behind the theory, the idea of CRDTs is that from the user’s point of view they are what we usually consider “data structures” plus rules that define how users can modify then and how the data will be merged in all cases. That means that:

                            • There is no case in which the system will say “I don’t know what to do” and have to ask for outside help synchronously. Adding something to the data structure that says “ask a human later” is fine.

                            • The end result will not depend on which device does the merge.

                            • Most importantly those rules are built into the data structure and exposed to the user (they are part of the API).

                            Now the problem is to design a CRDT that does what the end user wants, and that’s a lot harder than just making a CRDT “that works”…

                            1. 6

                              Just to nitpick a bit, because this has been bothering me for a while

                              You seem to be in agreement with the post. That section of the post just says “here is some related work, it’s called CRDT, and wasn’t enough to solve it”.

                              1. 1

                                I think the directed graph representation comes from the original paper as explained well for non-mathematicians here.

                                1. 2

                                  It’s inspired by that, but does a lot more. The actual thing used by Pijul is explained in the blog post linked here as well (and your link comes from that person reading Pijul’s source code and asking us questions about it).

                              1. 7

                                QUIC being hard to parse by router hardware is a feature, not a bug. IIRC (and I may not) this is why encryption was originally introduced in the protocol. I believe that it wasn’t until TLS 1.3 started maturing that it was integrated into QUIC to also provide strong security guarantees, but to be honest I’m really unsure on this point and I’m too lazy to Google at the moment. Maybe someone else can tell us?

                                In any case, the reason QUIC being hard to parse by routers is a feature is because it ensures protocol agility. I don’t know the details but there are things that in theory could be done to improve TCP’s performance, but in practice cannot because routers and other middleboxes parse the TCP and then break because they’re not expecting the tweaked protocol. QUIC’s encryption ensures that middleboxes are largely unable to do this, so the protocol can continue evolving into the future.

                                1. 2

                                  While there are definitive benfits to it, like improved security from avoiding all attacks that modify packet metadata, it also means you can’t easily implement “sticky sessions” for example, i.e. keeping the client connected to the same server on the whole connection duration. So yeah, it’s always a convinience/security tradeoff isn’t it…

                                  1. 2

                                    I am not really a QUIC expert but I don’t really understand the issue here. The Connection ID is in the public header, so what prevents a load balancer from implementing sticky sessions?

                                    1. 2

                                      Oh I’m far from an expert too. You’re right, if the router understands QUIC it will be able to route sticky sessions. If it only understands UDP (as is the case with all currently deployed routers) - it won’t be able to, since the source port and even IP can change within a single session. But that’s a “real-world” limitation, not the limitation of the protocol, actually.

                                      1. 5

                                        What kind of router are you thinking of?

                                        A home router that can’t route back UDP to the google chrome application is just going to force google to downgrade to TCP.

                                        A BGP-peered router has no need to deal with sticky sessions: They don’t even understand UDP.

                                        A load balancer for the QUIC server is going to understand QUIC.

                                        A corporate router that wants to filter traffic is just going to block QUIC, mandate trust of a corporate CA and force downgrade to HTTPS. They don’t really give two shits about making things better for Google, and users don’t care about QUIC (i.e. it offers no benefits to users) so they’re not going to complain about not having it.

                                        1. 2

                                          You should take a look at QUIC’s preferred address transport parameter (which is similar to MPTCP’s option). This allows the client to stick with a particular server without the load balancing being QUIC aware.

                                    2. 2

                                      Google QUIC used a custom crypto and security protocol. IETF QUIC always used TLS 1.3.

                                    1. 1

                                      Company: Inch

                                      Company site: https://inch.fr

                                      Position: Senior Full Stack Developer

                                      Location: Paris, France (onsite, partial remote OK but you must live near Paris and speak French)

                                      Description: We’re a small company (10 people) making a SaaS CRM & ticketing solution for property managers in the French (and Belgian) market.

                                      Tech stack: Rails, React, PostgreSQL

                                      Contact: me (pierre at company domain)

                                      1. 2

                                        There are two good approaches to that issue.

                                        The first one is to check and handle all possible unexpected results, and have tooling to ensure you do it.

                                        The second one is “let it crash”, famous in Erlang circles. Basically, when an error occurs, log it so you can debug it and panic the process and let a process manager restart it in a known state (“process” does not necessarily mean “OS process” here).

                                        In general, you would use both: very critical code (think DB transactions, etc) constitutes an “error kernel” which you write with the first approach, and all business code around that uses the second one.

                                        1. 53

                                          Super classy comment (in my opinion) from the original author of htop, regarding the fork: https://github.com/hishamhm/htop/issues/992#issuecomment-683286672

                                          1. 5

                                            I expected your comment to be sarcastic but hishamhm’s reply really is supportive!

                                            1. 4

                                              Wonderful reply. I can’t imagine what it must be like to write a small utility then have it grow so popular it shapes your whole life.

                                              That’s the double edged-FOSS sword I guess. I’m amazed and grateful that there are people out there like Hisham.

                                              1. 3

                                                It is an entirely valid choice to not let it shape your whole life. Which is what happened here, I guess :-) And I can totally relate.

                                              2. 4

                                                Hisham is a great person. Like many I have come to know him through the Lua community, and then learned the h in htop meant “Hisham”.

                                                He is maintaining a lot of important packages in Lua land, and also has a few other personal projects such as tl and dit. I can understand why he was overwhelmed. :)

                                                If you are interested in htop and its “recent” evolutions this talk is worth watching.

                                              1. 4

                                                I agree with parts of the post, but certainly not this:

                                                More often than not automatic updates are not done with the interest of the user in mind.

                                                I don’t believe this is true. I don’t have data but I think the main reasons for automatic update are in the interest of the user, the N.1 being security patches (as mentioned elsewhere in the post). Software without at least semi-automatic security updates is one of the main reasons why there is so much malware everywhere and this is clearly not good for users. It’s even worse for hardware. Actually I think most of the “should always” / “should never” bullet points should come with “except for security reasons” (especially the last one, compatibility with plug-ins).

                                                Another reason is compatibility, especially in the case of networked software. If you change the client - server (or client - client) protocol without automated updates you can never deprecate anything and in the long run it becomes a maintenance nightmare. This gives an edge to your competitors, especially to Web-based software which (almost) always auto-updates. And being uncompetitive is clearly not good for your users in the long run.

                                                All that being said, the choice depends a lot on who the user is. I think most hardware and software for the general public should come with either automatic or semi-automatic updates enabled by default, unless it is distributed through a package repository / store (which is the ideal case). Professional software which is administered by trained people, for instance, is different.

                                                For commercial software I think the solution to avoid version explosion is to always limit the duration of user licenses. Even if you want to sell a life-long license, you can structure it so that users have a one-year license for a given version of the software and the right to obtain such a license for the latest version of the software anytime. And then maybe you don’t auto-upgrade, but you tell the user “you must click this upgrade button by this date or you won’t be able to use the software anymore.”

                                                That being said, I do agree with a lot of the post, especially the fact that if you stop supporting a version there should be a clear upgrade path, that updates shouldn’t be abused for marketing purpose, etc.

                                                1. 10

                                                  More often than not automatic updates are not done with the interest of the user in mind

                                                  I think a more accurate claim would be that more often than not, the outwardly-visible changes that come thru automatic updates are not done in the interests of the end user. It’s primarily a matter of perception, and that perception is what drives the lack of trust.

                                                  Users don’t see the vulnerabilities they were protected against, but they do see the new ads that have begun to show in their start menu.

                                                  1. 3

                                                    Yes, that makes sense. Fully automated updates make that even worse because users don’t even notice the security updates.

                                                1. 7

                                                  The several mentions of NGS make me suspect that this is written almost as an advertisement. Consider the article’s conclusion:

                                                  From my perspective, all languages suck, while NGS aims to suck less than the rest for the intended use cases (tl;dr – for DevOps scripting).

                                                  NGS itself seems to have commits from Ilya (the author), so it seems like he’s at least a contributor. Combined with other people’s comments about this article’s lack of substance, I’m a little wary.

                                                  I recently saw another article authored by try NGS people about NGS being better because it has a method that ensures that an array has exactly one element, which was then promptly edited to say “yeah NGS isn’t the only one with this”. Every language looks better than others to their creator. But I don’t think a new language is the solution, especially if it has no groundbreaking features (and I don’t know if NGS has groundbreaking features)

                                                  1. 3

                                                    Ilya is NGS’ main author. It’s a rant on his personal blog so he probably supposes the reader already knows this.

                                                    1. 2

                                                      I recently saw another article authored by try NGS people about NGS being better because it has a method that ensures that an array has exactly one element, which was then promptly edited to say “yeah NGS isn’t the only one with this”. Every language looks better than others to their creator. But I don’t think a new language is the solution, especially if it has no groundbreaking features (and I don’t know if NGS has groundbreaking features)

                                                      Do you have any examples of languages that became successful because of a groundbreaking language feature?

                                                      Most successful languages in the last 30 years have a blend of ideas from earlier languages:

                                                      • Python - ABC, SETL, Occam
                                                      • Ruby - Perl, Smalltalk, Lisp
                                                      • Java - Objective C, C++
                                                      • JavaScript - Self, C
                                                      • Go - C, Limbo, Emerald
                                                      • PHP - Perl
                                                      • R - S, Scheme
                                                      • Swift - Objective-C, Rust, Haskell, Ruby, Python, C#, CLU, others
                                                      • C# - C++, Delphi, Java
                                                      • PowerShell - Korn shell

                                                      It seems much more important to have a compelling use case, some degree of luck, and a platform or library that’s easier to use than the incumbent.

                                                      Corporate backing may have become a requirement; most of these languages were either created in the 1990s or had corporate backing.

                                                      1. 2

                                                        groundbreaking language feature

                                                        In NGS, I am throwing in syntax and features (after deliberation) which are geared towards intended use cases, nothing that I would consider “groundbreaking”, just convenient. Sometimes much more convenient than in other languages.

                                                        Not many of the features are unique but they fit the niche well. The “chance” of this language is targeting the niche. I have learned to not throw in anything that is “maybe a good idea” (or at least mark that “experimental”).

                                                        Examples of features specifically targeting the niche:

                                                        • Syntax for running external commands, syntax for running + parsing the output of external commands
                                                        • Smart handling of exit codes of external commands
                                                        • Data manipulation facilities
                                                        • Simple extensibility
                                                        • log(), debug(), retry() methods
                                                        1. 1

                                                          Have you read Frank Atanassow’s advice on designing a language?

                                                          He takes a strongly negative attitude towards creating languages but does pose some good questions.

                                                          1. 2

                                                            I didn’t see it before. I do think that creation of a new programming language must be justified. If it’s “it would be so cool to have a new language” - either keep your fun/learning project for yourself or at least state the aim explicitly in a prominent place.

                                                            Unfortunately I can not answer your question fully at the moment because of my current constraints. I’ll try to get to this later. Partial answer is at

                                                            Alternatives:

                                                            https://github.com/ngs-lang/ngs#have-you-heard-of-project-x-how-it-compares-to-ngs

                                                            Note that there is no alternative at which a looked and said: “OK, it’s aligned with my vision so much that doing NGS is not needed anymore”.

                                                      2. 1

                                                        Yes, I’m the main author.

                                                        NGS being better because it has a method that ensures that an array has exactly one element, which was then promptly edited to say “yeah NGS isn’t the only one with this”

                                                        It’s practically not feasible to go over every existing language to check the claim. “I’m not aware of any language that has this” is my approximation. The mistake was pointed out to me and I promptly updated the post as not to mislead the readers.

                                                        Everything else is addressed in today’s update of the post.

                                                      1. 2

                                                        Happy birthday, Lobste.rs. I still read you every day. Here’s to the next 8 year. :)

                                                        1. 6

                                                          German perspective: You’re an engineer after getting a university degree in an engineering program (e.g., bachelor of science in information technology). You’re only allowed to call yourself an engineer after graduating. But then immediately.
                                                          Depending on school, program that may or may not involve lots of meta/how-to engineer courses.

                                                          No extra test but also no way for non-academics to become an engineer unless they go to a university. :/

                                                          1. 5

                                                            Similar in Sweden. The term “civilingenjör” covers all engineering, not just civil engineers (that degree is called “civilingenjör väg och vatten”) and is a protected term. You’re not allowed to present yourself as such unless you have graduated from an accredited institution (nor are you allowed to purchase the special ring you’re entitled to).

                                                            1. 3

                                                              Similar in France. “Ingénieur” covers all engineering and is also a protected term. But a lot of people are obsessed by degrees here in France and it kinda sucks.

                                                              1. 2

                                                                Not exactly.

                                                                “Ingénieur” (Engineer) is not protected. It is a job title and can be given to you as long as your job corresponds.

                                                                The protected constructions are things like “Diplôme d’Ingénieur”, “École d’Ingénieur” and “Ingénieur diplômé” (Engineering Degree, Engineering School and “Engineer with a degree”, respectively). They are regulated by a body called CTI.

                                                            1. 6

                                                              I use a self-hosted instance of https://tt-rss.org/, and have been for several years. Both with the standard web-ui & the android app. It’s fine. I really enjoy my read history synced between my various devices. It’s not the most elegant UI, it has some quirks, especially in the web-ui, but it’s gets the job done well enough. I’ve tried a few others, but haven’t come across anything that works quite as well.

                                                              1. 2

                                                                Also good luck if you wade into the official forums for support or a bug report.

                                                                I’ve also been using it for years because it simply works. Wanted to change servers and use the docker container but I postponed that because that was absolutely not working and I am not in the mood to argue with the maintainer. Not sure what I will do, but I use it together with NewsPlus on Android and don’t really want to change that setup. (That Android app hasn’t been updated for ages but I bought it and will use it as long as it works, because I love it.)

                                                                1. 1

                                                                  linuxserver.io had tt-rss as a container they supported but had to stop due to reasonable(?) changes asked of the repo maintainer. The forums seem to be rather hostile. I’ve taken to just cloning and building the image myself (which the maintainer IIRC argued is what they think everyone wants to do) but is categorically the opposite of what I want do. I want a trusted repository in which to pull a minimal image that is up to date.

                                                                  Sad links of despair:

                                                                  1. 1

                                                                    Yes, I also skimmed or read all of those. Some changes were integrated after weeks of discussion but for some reason or other I couldn’t get it to work, just 2-3 weeks ago (could be my setup, sure).

                                                                    1. 1

                                                                      Ahh, if all you want is an image. Feel free to use mine!

                                                                      https://hub.docker.com/r/dalanmiller/tt-rss

                                                                2. 1

                                                                  Same here. There is an official package in Arch Linux, I use that.

                                                                  1. 1

                                                                    I also self-host Tiny Tiny RSS. On iOS I use Fiery Feeds which has a much better UI.

                                                                  1. 16

                                                                    Please correct me if I’m wrong, but AFAIK you don’t need to ask for cookie consent for core site functionality, such as remembering user settings.

                                                                    The “cookie law” isn’t about using cookies per se, but about using cookies (or any other identifier) for tracking people.

                                                                    1. 7

                                                                      That’s true. You’re not required to get cookie consent for the necessary functionality such as session cookies that for instance hold the items in the shopping cart etc.

                                                                      1. 4

                                                                        remembering user settings.

                                                                        Presumably, it depends whether your user settings cookie contains data like “dark mode on” or whether it contains a user id which allows you to load the user’s settings from your database. While they both achieve the same functionality, the latter case tracks the user, while the former does not. I don’t know whether you could try to justify the latter as “necessary”, given that the former is possible.

                                                                        1. 4

                                                                          From my reading here https://gdpr.eu/cookies/ it says:

                                                                          “Receive users’ consent before you use any cookies except strictly necessary cookies.

                                                                          Strictly necessary cookies — These cookies are essential for you to browse the website and use its features, such as accessing secure areas of the site. Cookies that allow web shops to hold your items in your cart while you are shopping online are an example of strictly necessary cookies. These cookies will generally be first-party session cookies.

                                                                          Preferences cookies — Also known as “functionality cookies,” these cookies allow a website to remember choices you have made in the past, like what language you prefer, what region you would like weather reports for, or what your user name and password are so you can automatically log in.”

                                                                          So you would have to ask for consent for “preferences” but you don’t need to ask for those as soon as someone enters a site like you need to ask for marketing/tracking cookies. You can for instance have a “remember me” box and let users check it in case they want to save settings such as dark mode or they try to login.

                                                                          1. 3

                                                                            If you keep the dark mode setting client-side, you can just store it in local storage and never send it to the server. You don’t need opt-in for that.

                                                                            1. 4

                                                                              GDPR is technology-agnostic. Don’t assume that if you use some other mechanism that you’re in the clear. Local storage can still hold persistent identifiers that your website can access.

                                                                              1. 3

                                                                                I agree, but I said “never send it to the server”. I don’t think anything done purely client-side is subject to consent in GDPR.

                                                                                1. 1

                                                                                  I wouldn’t bet on a technicality convincing any lawyers.

                                                                                  • If you embed third-party scripts on your page, you’re sharing localStorage with them, and you have the responsibility to ensure they won’t grab it. XSS on your site may need to be treated the same way as server-side data breaches, because it only matters what data leaked, not how.

                                                                                  • If you make use of the data, you’re still processing it. Even if you don’t send the data as-is to the server, it may still have privacy implications, e.g. if you choose which ads user gets served based on localStorage data.

                                                                                  1. 1

                                                                                    Both of those points are interesting, but I wonder about the implications on native, desktop applications then…

                                                                                    I’ve never had a video game ask me for consent to autosave, for instance.

                                                                                    1. 1

                                                                                      Games come with heavy EULAs, especially if any account or on-line component is involved (e.g. DRM and anti-cheat rootkits have access to lots of private information, so they must have made you “agree” to this).

                                                                              2. 2

                                                                                That only works if the user runs your JS

                                                                                1. 2

                                                                                  Yes. It doesn’t really matter if the dark mode setting isn’t saved (or even cannot be used) for users who disable JS…

                                                                                  1. 3

                                                                                    you’re right. and it also doesn’t matter if that wheelchair ramp actually works.

                                                                                    1. 1

                                                                                      I don’t know an accessibility issue that would be solved by a native dark mode and where the user cannot interpret JavaScript…

                                                                                      Honestly, accessibility matters, but this really sounds like a straw man argument.

                                                                                      1. 2

                                                                                        Correct me if I’m wrong, but what I interpret your comment to say is:

                                                                                        I don’t think it is important for users without JS to be able to save their site preferences.

                                                                                        1. 1

                                                                                          Yes, but today disabling JS is a choice so it’s not really fair to compare it to disability.

                                                                                          How many users can still afford to disable JS entirely anyway, in a time when so many popular websites are single page applications?

                                                                                          It’s 2020, and CLI browsers, screen readers, crawlers… can all run JS. (And they don’t care about a dark mode anyway.)

                                                                                          For what it’s worth I’ve always thought that styling was a client-side issue. 20 years ago we had alternate stylesheets, and Mozilla never fixed this issue so people already replied on cookies + client-side JS to solve that. The only difference in using localStorage is that the information is never sent to the server, where it isn’t used anyway.

                                                                                          1. 2

                                                                                            Yes, but today disabling JS is a choice so it’s not really fair to compare it to disability.

                                                                                            Go get yourself an Obamaphone, or a $25 phone from Best Buy, and use it as your primary device for a week. Then come back here and say that again.

                                                                                            How many users can still afford to disable JS entirely anyway, in a time when so many popular websites are single page applications?

                                                                                            That’s what I’m saying here. You could be a “popular website” which makes itself inaccessible, or you could go the extra mile and build that ramp.

                                                                                            JS can also break due to incomplete transfers, broken cached JS files, errors in the JS itself, unfinished loading due to slow connections. Do you really want to eliminate all these users, when they may depending on your service the most?

                                                                                2. 2

                                                                                  I think it’s okay to send the individual preferences to the server, but not an ID to look up preferences server-side, because that is something you can individually track the user with.

                                                                                  For example, if both me and catwell prefer the dark site with language Swahili, you can’t distinguish us if these settings are set directly in the cookies; same settings, same cookies. But if you store them in a database with user ID, suddenly I’m user 42 and catwell is user 1337, and you can distinguish between us.

                                                                                  1. 1

                                                                                    I think it’s okay to send the individual preferences to the server, but not an ID to look up preferences server-side, because that is something you can individually track the user with.

                                                                                    Is there really a difference?

                                                                                    1. 1

                                                                                      Is there really a difference?

                                                                                      Yes. Definitely. darkmode=1 vs darkmode=0 has one bit of entropy, so you can’t track me with it. SID=94898743637843438653262 is many bytes of entropy, and I don’t know what you use it for. Is that identifier only matched to a darkmode setting or is it a giant GDPR violation server side?

                                                                              3. 2

                                                                                or just use css media queries and let the person configure it on their browser instead of having to figure out where every website’s special toggle mode button it.

                                                                                @media (prefers-color-scheme: dark) and @media (prefers-color-scheme: light)

                                                                            1. 2

                                                                              For something serious that I would start from scratch with no constraints, I would pick Flask. I have used it professionally almost 10 years, I know it very well, and it does everything I want. It is also well-known enough that I know I can hire people to work with it easily.

                                                                              Currently I use Rails at work, but I really don’t like parts of it, most importantly Active Record, so I’d avoid it.

                                                                              For small personal projects I might go with Lua because it’s the language I prefer, either through CGI or with Xavante. I like Lapis as a framework but I would avoid it because I like my Lua 5.3 features, and OpenResty runs LuaJit which doesn’t have them.

                                                                              1. 3

                                                                                Wow! Of all the complaints I’ve seen about Lua, I haven’t seen this one. “Multi-values” just aren’t a thing in Lua. This is not a data structure [1]. Lua allows a function to return multiple values, not all of which need be used. The way to use multiple return values is to assign them to variables:

                                                                                function foo()
                                                                                  return 1,2,3
                                                                                end
                                                                                
                                                                                local a = foo() -- 2nd, 3rd ignored
                                                                                local b,c = foo() -- 3rd ignored
                                                                                local d,e,f = foo() -- all assigned
                                                                                local g,h,i,j = foo() -- j is nil
                                                                                

                                                                                There are two exceptions to this—if the function call is wrapped in parenthesis, only the first value is used:

                                                                                local a,b,c = (foo())
                                                                                print(a)
                                                                                

                                                                                The second is when the function is followed by a second value, only the first value is used:

                                                                                local a,b = foo(),4
                                                                                print(a,b)
                                                                                

                                                                                Lua also allows a function a variable number of parameters (like in C) and to access these, you need the select() function (it’s clunky, I admit, and it comes up from time to time on the Lua mailing list). The first parameter to select is the index of which value you want from the rest of the values given to select, and it returns values starting from that index:

                                                                                print(select(2,1,2,3,4,5))
                                                                                

                                                                                If the index is negative, it starts from the end, so this will print the last two values:

                                                                                print(select(-2,1,2,3,4,5))
                                                                                

                                                                                And if the index is the special value ‘#’, it will return the number of additional parameters given to select:

                                                                                 print(select'#',1,2,3,4,5))
                                                                                

                                                                                Lua also restricts the number of parameters to 255. Again, this is not a data structure per se.

                                                                                Your example of a tail call isn’t one. A proper tail call just returns the result of a single function call. Here’s a function that is a proper example of a tail call:

                                                                                function tablen(t,n)
                                                                                  n = n or 1 -- in case we don't specify n initially
                                                                                  if not t[n] then
                                                                                    return n-1
                                                                                  else
                                                                                    return tablen(t,n+1) -- TAIL CALL!
                                                                                  end
                                                                                end
                                                                                

                                                                                Add an additional value to that return statement, and now it’s just a recursive call that consumes stack space. And tall calls don’t have to be in a recursive situation—any return of a single function call becomes a tall call:

                                                                                function foo()
                                                                                  return 1,2,3
                                                                                end
                                                                                
                                                                                function bar()
                                                                                  return foo() -- this is also a TAIL CALL!
                                                                                end
                                                                                

                                                                                Tail calls effectively become a form of GOTO.

                                                                                Returning multiple values from a function used to be an uncommon feature of programming languages, but it’s not like it’s new—one could always return multiple values in assembly; Forth has had it from the start (late 60s), so did Perl (late 80s). I don’t know enough of Smalltalk but I suspect it too, supported multiple return values (70s). And more languages today support it (Go, Ruby and Rust for example).

                                                                                [1] In the traditional sense of the word. It may look like a data structure, but it’s an annoying one to use, and I think it hampers development to even think this is a data structure.

                                                                                1. 7

                                                                                  Thanks for reading the post and replying to it! While I did cover some of what you mention here, I also completely missed some stuff, like how wrapping a function call with parentheses limits its return values to 1 or how you can pass negative values to select to get values from the end of the multival. I’ll add those to the post - thanks so much for pointing them out!

                                                                                  Please do note that this post is not intended to be an anti-Lua rant. I’m a frequent Lua user and my motivation is to help people get a better sense for how Lua will behave in certain edge cases, not to criticize the language as a whole. Perhaps I could make this clearer in the post.

                                                                                  I do have some responses to your comment:

                                                                                  “Multi-values” just aren’t a thing in Lua. This is not a data structure [1].

                                                                                  While it’s convenient not to think of them as a data structure while programming Lua, that’s actually not the case. They are a datastructure in my opinion, just a second-class one with extra rules surrounding them. They have a length, they have a maximum size, and they have contents which can be addressed by index. This is the thesis of my post.

                                                                                  Lua also restricts the number of parameters to 255. Again, this is not a data structure per se.

                                                                                  Again - while it’s convenient to think of this as not a data structure, it absolutely is - it’s an array of items limited to 255 elements (in practice, this limit appears to actually be at 253 for some reason). Also note that this restriction applies to both arguments and return values, but only applies to literal values - this is why, for instance, you can pass a ... that contains 255 items to a function successfully, but you can’t pass the same 255 items by writing them out literally in a Lua file. See the following code for an example:

                                                                                  local function range1(tab, acc, n)
                                                                                    if n < 1 then return tab
                                                                                    else
                                                                                      tab[acc] = acc
                                                                                      return range1(tab, acc+1, n-1)
                                                                                    end
                                                                                  end
                                                                                  
                                                                                  local function range(n)
                                                                                    if n < 1 then error("n must be >= 1") end
                                                                                    return range1({}, 1, n)
                                                                                  end
                                                                                  
                                                                                  local function increment_each_value(first, ...)
                                                                                    if first then
                                                                                      return first + 1, increment_each_value(...)
                                                                                    end
                                                                                  end
                                                                                  
                                                                                  local x = range(1000)
                                                                                  
                                                                                  local y = function(...)
                                                                                    return increment_each_value(...)
                                                                                  end
                                                                                  
                                                                                  print(y(table.unpack(x)))
                                                                                  

                                                                                  Your example of a tail call isn’t one. A proper tail call just returns the result of a single function call.

                                                                                  This is the whole point of the section you’re referring to. Maybe I can be clearer that I don’t actually think the first example of range is a tail call - I already demonstrate it blowing the stack to show that it is not tail recursive. This is intended to demonstrate one of the places where the understanding that “multivals are not a data structure” would trip someone up - if multivals really weren’t a data structure, why would a call at the end of a multival not be a tail call? The reason it’s not a tail call is because it’s not actually in tail position - it’s inside a data structure that just happens to not have delimiters, instead being represented by multiple adjacent values separated by commas.

                                                                                  You might argue that this is all obvious, but it tripped me up the first time I ran into it. Thinking about it for a bit made it make sense, but my initial instinct was that the initial implementation of range was tail recursive. Finding out that it wasn’t was part of the motivation for writing this post.

                                                                                  Note that the second implementation of range in the post is properly tail recursive, and the distinction between the two implementations is what I’m trying to illustrate there.

                                                                                  Imagine that the syntax to return a multival looked like <<<1, 2, 3>>> instead of 1, 2, 3. (I’m not actually proposing this! It’s just a thought experiment.) It would then be immediately obvious that calling a function at the end of the multival was not in tail position:

                                                                                  local function range1(acc, n)
                                                                                    if n < 1 then return
                                                                                    elseif n == 1 then return acc
                                                                                    else return <<<acc, range1(acc+1, n-1)>>>
                                                                                    end
                                                                                  end
                                                                                  
                                                                                  local function range(n)
                                                                                    if n < 1 then error("n must be >= 1") end
                                                                                    return range1(1, n)
                                                                                  end
                                                                                  

                                                                                  You make a good point that tail calls do not require recursion, just returning a single function call. I’ll try to see if there’s a way to add this to the post that fits well.

                                                                                  Returning multiple values from a function used to be an uncommon feature of programming languages, but it’s not like it’s new…

                                                                                  I didn’t mean to imply that this was a recent feature in programming languages, just that it was a relatively unusual one.

                                                                                  Thanks again for your detailed critique of my article!

                                                                                  1. 3

                                                                                    What I find unfathomable (but it may just be me) is that anyone would think to use function parameter passing as a data structure. In over 30 years of programming experience, you are the second person I’ve come across to do so [1]. Perhaps I’ve been programming for too long, or involved way too much in low level details (my second computer language I learned was 6809 Assembly, followed by 8088, then 68000 and MIPS).

                                                                                    Yes, parameters can be passed around in a data structure, but these days, compiled languages on x86_64 (and some other RISC based systems—I’m also thinking of the SPARC architecture here) pass as much as possible in registers so there’s no data structure backing them. I think that’s why I find it weird to think of the “multi-values” as a data structure.

                                                                                    First off, the limit I mentioned, 250? That’s the upvalues (used in closures) limit, not the paramter limit (I was mistaken there). The Lua parser might impose a limit on the number of literal parameters one can specify, but there’s no effective limit on the number of parameters a function can have. I did construct a table with 2,048 elements, and was easily able to do this:

                                                                                    print(table.unpack(t))
                                                                                    

                                                                                    and end up with 2,048 items printed to the screen. And in am empirical test I did, I was able to construct a Lua function (in C) that returned 999,990 values (and that’s probably due to a limitation on the system than a hard-coded limit in the Lua VM).

                                                                                    Also, there’s one other language I’m aware of that uses ‘…’, and uses it in the same context—C. C allows a variable number of arguments (but the restriction in C is that there has to be at least one named paramter). The declaration of printf() is (per the standard):

                                                                                    extern int printf(const char *fmt, ... );
                                                                                    

                                                                                    And like Lua, there’s a special function you have to call to retrieve the unnamed parameters (actually, three functions that work in tandem). The function hides how the parameters are actually passed (via the stack, or maybe in X number of registers, or a mixture of the two).

                                                                                    [1] The first one also used Lua, five years ago.

                                                                                    1. 4

                                                                                      …data structures don’t have to live in memory. A data structure lives in the programmer’s head and the code that is an approximation of it. Something doesn’t stop being a data structure just ’cause a compiler decides to pass it in registers, or may optimize it out of a particular piece of code entirely.

                                                                                      There’s languages out there that let you very specifically treat function args as a tuple; the main one I’m familiar with is SML. Simple Lisp’s often pass them as a list. My understanding is that it’s just not common because having function args be a special case is useful. More common are languages that return a tuple to implement “multi-values”, such as Rust, pretty much any ML, sometimes Python, etc.

                                                                                      1. 1

                                                                                        I never thought of subroutine [1] parameters as a distinct “data structure”. At best, one could say that it forms an ad-hoc struct [2], and I think I might be too … something (I’m not sure what) to accept “data structures don’t have to live in memory.” There are implementations of a data structure (a stack can be an array, or a linked list, for instance), but that statement is just too “nothing there” for me to accept it.

                                                                                        So what is an array, a structure, and a tuple?

                                                                                        [1] And I’m using that word very deliberately.

                                                                                        [2] The VAX has two forms of a CALL instruction—one passes the parameters on the stack; the other in a pointer to some memory. The called subroutine doesn’t care which was used to call it, it just gets a pointer to memory containing the input. The layout will be the same in either case.

                                                                                        1. 2

                                                                                          It’s late, so, the simplest counter-argument I can think of is: does an array stop being an array if it lives entirely in registers? Sure you’re going to have a hard time indexing it with a pointer, but you could certainly write code to index it with an integer, which is just a pointer without context anyway, and I wouldn’t be surprised if some CPU somewhere even had instructions to make it easy. Structs get passed in registers all the time, in C now as well as Rust and whatever else. What about a floating point matrix that is loaded, stored, passed and manipulated entirely in SIMD registers, is that a data structures or not? Just ‘cause you can’t aim a pointer to it in main memory doesn’t mean it doesn’t exist.

                                                                                          Sure, registers are just specialized memory though. Then take it a step further and ask yourself, if the compiler optimizes the matrix math entirely at compile time down to a single output number, is the matrix still there? It’s not in the binary output, but it’s in the source code, you can go in and change the math and it gives you a different result. So, which version is “real”? Depends on what you want; if you want to know what the machine is actually doing then your “matrix” doesn’t exist, it’s been reduced to just some number sitting in the binary’s data segment somewhere. If you want to be able to logically manipulate the math, then you probably care a lot more about the functions and values in the program than what the compiler does with it, at least until you start optimizing. What about if I do the compiler’s job and do all the math myself, write the algorithm down in the program comments, and stick the constant result in the code? We start edging into the Chinese Room problem.

                                                                                          Guess I’m feeling existentialist tonight. Anyway, you probably know all this. I’ve just been messing around with Haskell too much lately, where “data structure” and “function that produces a data structure” are basically the same thing.

                                                                                  2. 6

                                                                                    It may look like a data structure, but it’s an annoying one to use

                                                                                    I think this is part of the point of the article; multivals are troublesome because it has many properties of a data structure, (the ... expression is a compound thing that contains other things) but it’s not first-class in a way that allows it to be used in the ways you would expect to be able to use data structures.

                                                                                    Edit: Ruby doesn’t support multiple values, but it does have syntactic sugar for returning an array which appears like it’s returning multiple values. Smalltalk doesn’t support it either. Forth doesn’t support returning any amount of values, only pushing values onto the stack, which is loosely analogous but a different thing.

                                                                                    1. 2

                                                                                      Forth doesn’t support returning any amount of values, only pushing values onto the stack, which is loosely analogous but a different thing.

                                                                                      Not so different. Returning multiple values from a function in Lua is exactly that: pushing multiple values on the stack.

                                                                                      That’s why I’m strongly in the “not a datastructure” camp: it’s all just syntactic sugar to manipulate the stack, which is the underlying “data structure”.

                                                                                      Even if you don’t write bindings, if you write a significant amount of Lua, you should at least look at the C API calling convention to understand things like this.

                                                                                      1. 3

                                                                                        Returning multiple values from a function in Lua is exactly that: pushing multiple values on the stack.

                                                                                        Sure, but in Lua the stack is an implementation detail, whereas in Forth it’s The Whole Point; no one can use the language without thinking about the stack every step of the way.

                                                                                        you should at least look at the C API calling convention to understand things like this.

                                                                                        I agree but IMO the fact that you have to read about the C API to really understand this feature even when you have no interest in ever using C is a red flag.

                                                                                  1. 6

                                                                                    Cutoff Multivals

                                                                                    The one that that kills me a little in lua comes from this: It’s impossible to set an assert message on a function that returns multiple values.

                                                                                    > function getthings() return 5, 4 end
                                                                                    > assert(getthings())
                                                                                    5	4
                                                                                    

                                                                                    That all works fine, but what if I want my assert to have more details about where I was when that function failed?

                                                                                    > assert(getthings(), "no things when $FOO")
                                                                                    5	no things when $FOO
                                                                                    

                                                                                    :/

                                                                                    1. 3

                                                                                      That’s frustrating, I hadn’t run into that with assert specifically! Thanks for providing that example. It’s a good instance of a case where multivals contribute to the large number of tradeoffs when determining argument order for your Lua functions.

                                                                                      1. 2

                                                                                        It’s not a problem unless you try to do something like this:

                                                                                        a, b = assert(getthings(), "no things when $FOO")
                                                                                        

                                                                                        and then you can just do that instead:

                                                                                        a, b = getthings()
                                                                                        assert(a, "no things when $FOO")
                                                                                        
                                                                                        1. 1

                                                                                          Oh, for sure, that’s why it only kills me a little. I didn’t mean that it was impossible to write it in a way that accomplished what I want, it was more just an example of the problem in the article that I had run into. (And that took me far too long to realize what was happening, before having a blinding realization that it simply couldn’t work the way I wanted it to.)

                                                                                        2. 2

                                                                                          What do you think of this?

                                                                                          assertm = function(x, ...)
                                                                                            assert(unpack(arg), x)
                                                                                            return unpack(arg)
                                                                                          end
                                                                                          
                                                                                          1. 1

                                                                                            Oh, interesting, message first. Yeah, that definitely seems to work. Thanks for the tip!

                                                                                            (Sidenote, that code needs a s/arg/.../g.)

                                                                                          2. 2

                                                                                            Please note that the “opposite” way of using assert is intended: somewhat resembling Go, it’s a common idiom for a failed function to return 2 values: nil, "some error message". You can then plug it straight into assert like below (as shown in “PIL”):

                                                                                            local f = assert(io.open(filename))
                                                                                            

                                                                                            and expect assert to print the error message in case of a problem.

                                                                                            As a side note, the freely available old edition of PIL is for a super ancient version of Lua, but I’d still resoundingly recommend reading it for all the tricks and ideas it communicates.

                                                                                            1. 2

                                                                                              Yep, for sure. I was thinking of exactly that when I mentioned adding more context.

                                                                                              I’ve got the 5.3 ed of PIL and also highly reccomend it. It’s actually one of my favorite books. It’s one of the only books I’ve ever read that manages to not be overly verbose, but also doesn’t have big gaps leaving you confused.

                                                                                              If anyone else is interested in picking it up Feisty Duck has a DRM free ebook version of it.

                                                                                          1. 1

                                                                                            Arch since 2005. I was using Gentoo earlier, and switched when drobbins went to work for Microsoft.

                                                                                            Before that, I had used Red Hat, and briefly Mandrake and Slackware.

                                                                                            I also spent almost a year using Minix3 as my main OS (around 2007), because I wanted to learn more about it. I stopped because it wasn’t very practical.

                                                                                            Why Arch? Because it’s mainstream enough to support all my hardware out of the box and have almost all the software I need packaged, but at the same time it has modern software versions, mostly vanilla. Also it’s very easy to make your own packages if needed. I never make install into the system, I always create a package instead.