NewPipe on Android. Recently I had to use YouTube on a friend’s iPhone and oh my goodness, how horrendous it is!
NewPipe is fantastic. You can even download videos for offline uses!
On the desktop, fish shell is great. The only thing I miss from bash is the “sudo !!” to repeat the previous command as root, but otherwise I love the sane defaults and all the little helpers. It’s one of the first things I setup on a fresh Linux install.
You can alt-s
to toggle sudo on your current command. If the prompt is empty it will automatically toggle sudo on your last command!
+1 for NewPipe! It is stunning how good that app is compared to the official experience. Also, the developers deserve all the praise in the world for the fact that when the video extractor breaks due to YouTube changing something on their end, we reliably get a fix pushed out within days if not hours.
I agree, NewPipe provides a much better YouTube experience. I recommend the fork NewPipe x SponsorBlock x Return YouTube Dislike, which can use the crowd-sourced data returned by the SponsorBlock API to skip unwanted sections of videos such as reminders to like and subscribe.
You might notice in NewPipe x SponsorBlock’s README that the first installation method is a link “Get it on IzzyOnDroid”. If you’re not aware, IzzyOnDroid is more than just an APK host. I recommend installing the app like this, which will allow you to easily update it:
I added this language to the CoffeeScript wiki’s long-maintained page List of languages that compile to JS. Anyone curious about alternative languages or looking for design inspiration might want to check out the other Clojure-like languages on that page.
There is no single killer feature that’s Better Than Vim,
One candidate here is syntax aware “text objects”: https://docs.helix-editor.com/usage.html#syntax-tree-motions
Do you use that a lot? For me it seems to have tho potential to be amazing, but I keep forgetting to use it.
For me the killer feature is the multi cursor. I use that all the time.
Neovim does have this feature in the form of https://github.com/nvim-treesitter/nvim-treesitter-textobjects/
Alt-o
(or its equivalents in other editors) I use all the time. Generally for me, multicursor + such “extend selection to the next syntax node” + moving arrows and other navigation keys to the home row give 80% of the modal editing, without needing modal editing.
This sent me down a rabbit hole, finding out how to æ make option really work as alt on macos with Norwegian keyboard.
Turns out there’s a ton of nonsense coded as alt(option)+letter - behaving more like compose - eg option-o inserts unicode glyph for œ (U+0153), and alt-a inserts the apple logo, for those with a burning desire to type that in directly.
So far the best fix I’ve found (wip) is to define a copy of current input in ukulele - and manually remove/replace pretty much everything defined behind option-modifier.
As a bonus there are some dead keys, like tilde - that only outputs tilde on tilde followed by space - otherwise a composition like ñ (U-00F1) - which is also possible to get rid of.
Now I can have my alt-o!
https://software.sil.org/ukelele/
The general idea: https://medium.com/@scr34mz/make-your-mac-keyboard-work-on-medium-and-others-editors-without-dead-keys-issues-french-keyboard-4666b72fa2ae For dead keys
For anyone who has similar problems with dead keys on macOS, but only in the terminal, note that you don’t need to create a new keyboard layout to solve that:
Hi everyone, My first post here. Nice to meet you all and thanks to @ocramz for inviting me.
This project has been in the making for a long time. It includes tooling and infrastructure to help developers write high-level tests for complex software workflows that are not easy to unit test. I wanted to take ideas from visual regression testing, snapshot testing, and property-based testing and build a general-purpose regression testing system that developers can use to find the unintended side-effects of their day-to-day code changes during the development stage. I wrote the first line of code in 2018 and left my job 2 years ago to work on it full-time (i.e. all the time). I am posting it here because I want to hear your unvarnished thoughts and feedback about its latest v2.0 release, a milestone version that hopes to be useful to small and large teams alike. This version comes with:
An easy to self-host server that stores test results for new versions of your software workflows, automatically compares them against a previous baseline version, and reports any differences in behavior or performance.
A CLI that enables snapshot testing without using snapshot files. It lets you capture the actual output of your software and remotely compare it against a previous version without having to write code or to locally store the previous output.
4 SDKs in Python, C++, Java, JavaScript that let you write high-level tests to capture values of variables and runtime of functions for different test cases and submit them to the Touca server.
Test runner and GitHub action plugins that help you continuously run your tests as part of the CI and find breaking changes before merging PRs.
I would really appreciate your honest feedback, positive or negative, about Touca. Do you find this useful? Would love to hear your thoughts and happy to answer any questions.
Congrats on the release!
I haven’t really felt the pain of making snapshot tests scale (currently working with only about a 100 snapshots), and the Website / Docs don’t really make it clear to me what problems Touca solves that I might face in the future.
I’m definitely interested in a UI for visual regression testing as I’ve struggled to find good tooling for that, but looking at the screenshots it doesn’t seem like a big focus for Touca.
Thanks for sharing your thoughts. I’m sorry that you didn’t find the docs clear enough. I’ve tried to briefly explain the differences between Touca and snapshot testing here: https://touca.io/docs/guides/vs-snapshot/. As outlined in that document, one primary difference is that Touca removes the need for storing snapshot files in version control and moves the storage, comparison, visualization, and reporting of test results to a remote server. In this new model, instead of git committing snapshot files with differences, you’d use the server to promote a new version as baseline.
You are right that visual regression testing of web UI is not a focus of this project. I believe there are many good solutions in the market for web apps. We focus on software that may not have a web interface, like API endpoints, data pipelines, machine learning algorithms, command-line tools. We want to make it easy to test these types of software with various inputs without writing extensive unit tests and integration tests.
I found that page and got the difference, but I really like version control and its benefits. Focusing a little bit on the benefits a centralized server brings in comparison to the usual approach would be the interesting part for me :)
When visiting https://touca.io/ as someone who already understands snapshot testing, it was difficult to find how Touca was different from normal snapshot testing. The only obvious difference was that it has a web app interface instead of my local Git diff interface, but that on its own doesn’t sound like a desirable feature for an app that already has a CI pipeline. I think your pitch about “removes the need for storing snapshot files in version control” should be more visible. I still have no need for the feature – I struggle to imagine a case where snapshot files in nested folders would not be easy enough to manage – but at least that information would have made it clearer that the software is targeting problems I don’t have, so I don’t need to read more of the site.
On the home page, one heading is relevant to that question, “Snapshot testing without snapshot files”. However, the rest of that block doesn’t elaborate on how snapshot testing could possibly work without snapshot files or clarify whether “snapshot files” are the generated snapshots or source code files with snapshot tests. The next sentence, “Remotely compare the your software output against a previous baseline version”, sounds like it could equally apply to traditional snapshot testing. I think the word “remotely” in that sentence was also meant to imply “without snapshot files”, but I just interpreted it as “diff snapshot files on our remote server instead of locally”. (Also, there’s a typo: “the your” should be “the”.) The final part of that block, “brew install touca
”, is not at all connected to the previous two sentences and seems out of place. Without reorganizing the whole page, that brew
command might seem more relevant if a “Try it:” label were before it.
After I first was confused by the home page, when I clicked Docs, I just saw another summary of snapshot testing in general followed by a bunch of links, none of which sounded like they would explain how Touca is different.
I saw in the sidebar that I was in the Getting Started section. When I skimmed the next page, Concepts, it looked like just another explanation of snapshot tests in general.
Okay, so I went to the next page, Writing Tests. Great, examples of how to actually use this. Except… after reading the whole page it was hard to understand the expected input and output of each test. There is “alice”, “bob”, and “charlie”, but is the input literally those strings? How can that be when the assertions mention student.gpa
? And where is the expected output saved – why wasn’t it mentioned on this page? If it’s not saved to a file, it must be saved to a server, but I had trouble imagining at this point why that would be better. At that point I gave up on understanding Touca. Only later did I come back to these comments and see your link.
I think the Writing Tests page is too abstract. The JavaScript code sample calls code_under_test.find_student(username)
, but that’s not defined anywhere, so I struggled to imagine how it’s connected to the rest of the code. Maybe include a short stub definition in your examples, like this?
async function find_student(username) { // the code under test
if (username === "alice") {
return {name: "Alice", gpa: 4.0}
} else if (username === "bob") {
// etc.
}
}
And the next line, touca.check("gpa", student.gpa)
, didn’t give any hint as to what expected value Touca would compare the GPA against. Maybe add a comment to that line: // compare student.gpa to Touca’s saved 'gpa' value in the scope of this testcase
. That comment may be inaccurate; you can correct it.
Hi @roryokane, Thank you so much for this thorough feedback. I really appreciate you taking the time. I am going to read it again and again tomorrow and apply your suggestions both to the landing page and to the docs.
I think your pitch about “removes the need for storing snapshot files in version control” should be more visible.
Agreed. Will try to clarify.
I struggle to imagine a case where snapshot files in nested folders would not be easy enough to manage
You mentioned you are familiar with snapshot testing. Could you share more about your typical use-case?
I think the Writing Tests page is too abstract.
Based on your suggestion about this page and the “Concepts” page, I’m tempted to just rewrite the entire “Getting Started” section. This is exactly the type of feedback that I was looking for. Thank you!
IUP implementation: https://www.tecgraf.puc-rio.br/iup/en/7gui/7gui.html
Context: IUP
The link doesn’t explain how the project is different from MoonScript, which also compiles to Lua, or in what way it’s a “dialect”. Does anyone know?
Past the “import” statement, the “An Overview of YueScript” (the first sub-section of the Introduction) shows new language additions. I compared them recently on my microblog entry “Yuescript first impressions”:
I just discovered Yuescript, which is like MoonScript with more features. I have mixed feelings.
I like features like pipelines (much cleaner than repeated assignment or nested parentheses in function calls) and compile-time macros. The sugar for multiple and destructuring assignment is handy.
I find the additional operators unnecessary, and not worth their cognitive overhead. The
?
operator was already used as sugar for a parameter-free function call. The[]
operator could easily have been a function in a library instead.One of the trade-offs for this much syntactic sugar is some syntactic ambiguity. An opinionated formatter could resolve some of this.
This definitely is not congruent with why I didn’t adopt Elm more deeply. To me, Elm was too limiting; it was ossified, locking in bad design decisions and making them impossible to revisit or even experiment with alternatives. There’s also the compilation model; I don’t want to invoke any ECMAScript-specific compilation workflow if I can avoid it, since they’re usually not hermetic and they want to generate lots of scaffolding code. As with many other users, the showstopping misfeature was the inability to add native ECMAScript to bridge to some portion of the browser API which Elm hadn’t yet wrapped or typed.
If we’re thinking retrospectively, I would also mention the mistake of tying a language exclusively to Web browsers. It is fine to support browsers as a platform, but I feel like Elm always treated non-browsers, including its development REPL, as second-class platforms.
Wouldn’t you say that puts you in the category of users who generally disagreed with the goals of Elm? In which case Elm was never going to be the language for you. Definitely a contributing factor to reduced adoption, because it turned you away to other options. I think there’s probably multiple sets of people when it comes to niche languages:
which then has overlap with:
a) Users who have their needs met by technologies they already use
b) Users who have their needs met but are curious in alternatives
c) Users who don’t have their needs met
This blog post mostly covered 2.2/3 b/c, but there’s definitely a lot to be said for the 1/2.1 a/b/c groups too.
I agreed strongly with the design goals of Elm: first-order data, total functions with static types, etc. Elm was one of several MLs that I evaluated because I didn’t want to write ECMAScript directly. I recall that I gave up on using Elm for a project because I needed to fetch and parse binary data from a URL, and there was neither a satisfactory way of doing it nor a way of adding it myself. This evaluation was several years ago, and I expect that my particular issue has now been overcome, but I can’t operate in high-control communities like that.
In contrast, I’ve had issues with PureScript and OCaml, and I deliberately avoid e.g. using spago
, but I was able and allowed to write my own hacks and build scripts in order to satisfy both their tools and my design requirements.
I currently have a few hand-written ECMAScript stubs which use Preact, and then the bulk of the application code is written in Cammy and compiled first to PureScript and then to browser-friendly code. In the future, I plan to compile Cammy directly to WASM. I’m fully aware that it’s unreasonable to ask other people to write in Cammy, so I don’t.
Probably. The bibliography might be enlightening. Cammy is only meaningful in that it approximates the pseudocode seen in many category-theory papers; otherwise, it’s really not a good language for anything.
But all the old WATs like
["10", "10", "10"].map(parseInt)
are still there.
Could someone explain this to me? What the holy hell happens to make it produce the crazy result [10, NaN, 2]
⁉️
I had never seen this before and just had to try typing it in to see what happened … I guess I don’t know JS as well as I thought I did.
I think map passes the value AND index of the array element to the mapper function, so you get the result of “10” in base 0 (defaults to base 10), base 1 (error because 1 is not a valid digit in base 1), and base 2 (which is 2).
In general in JS, if you see [].map(funcName)
, it’s going to bite you in the ass, and you should change it to [].map(x=>funcName(x))
as soon as possible. And do it even faster if it involves this
.
I understand why, but this shouldn’t be necessary though because it’s verbose and doesn’t meet expectations coming from many languages–introducing a point and needing arguments to get around what sometimes feels like a bug. This is why you’ll see separate functions like indexedMap
, mapWithIndex
, mapi
, and the like in other languages so you’re not overloading the function arity.
Is there are Clippy-like linter for JS/TS that could warn against mistakes like this one? Deno itself has lint
, but that doesn’t seem to catch this one. Nor does eslint
apparently.
Neither do TS/flow. And there’s a reason they don’t: https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters
It’s unfortunate but there’s not much to be done here. You’d have to craft a very strict, one-off eslint rule to prevent this error in your codebase at the cost of some performance and convenience (eg prevent passing anything but an arrow function to Array.map)
An in-depth description of the problem is here, for anyone having trouble following along: https://jakearchibald.com/2021/function-callback-risks/
Linters cannot rule out cases when the argument function is actually supposed to take an element and its index. But at least they could warn about a possible gotcha.
this is typically why I favour lodash/fp versions as they are strictly limited to one argument for easy composition!
Love letters like this make me think I should give JS another shot. Warnings like that remind me that I don’t want to. It’s an abusive relationship.
See I disagree here - it’s very much a “know the APIs you’re using”.
It is reasonable - and can be useful - for map() to pass things like the index as argument to the mapped function.
It is reasonable - and absolutely useful - for parseInt to take a base argument.
What we’re seeing here is when you try to use these two together.
Imagine a statically typed language:
int parseInt(string, base = 10);
class Array<A> {
Array<B> map<B>(B(*f)(A)) {
Array<B> result = [];
for (int i = 0; i < length; i++) {
result.append(f(this[i]))
}
return result
}
Array<B> map<B>(B(*f)(A, int))
Array<B> result = [];
for (int i = 0; i < length; i++) {
result.append(f(this[i], i))
}
return result
}
};
here if you did [“10”, “10”, “10”].map(parseInt) you would call the second map, and so would get [parseInt(“10”, 0), parseInt(“10”, 1), parseInt(“10”, 2)]
Of course, if you know you know, but for non-JS programmers this is very surprising because:
map
passes multiple argumentsI get that it’s not the common map api in other languages, but coming in with “this is the API in some other unrelated language, therefore this is WAT” is simply not reasonable.
For the latter: not needing exact parameter count is a fairly fundamental part of the language. Saying it’s surprising that map(parseInt) invokes the same behavior as anywhere else in the language is “surprising” is again not reasonable.
JS has quirks, but labeling basic language semantics as WAT is tiresome.
I get that it’s not the common map api in other languages, but coming in with “this is the API in some other unrelated language, therefore this is WAT” is simply not reasonable.
It is still a reasonable criticism because map
exists in a broader social context with widely known conventions. The criticism is that JS doesn’t obey what people would expect. And the resulting confusion is the empirical proof of the problem.
In some cases, you might say, “Well ok, we know people will expect such and such, but such and such is actually a poor design because of X, so we’re going to fix it even if it’s going to cause some confusion”. That is, you can break with convention if you have a good reason. In the case of JS, this defense doesn’t hold up imo.
Ramda’s solution to the problem, which is to add indexing only on explicit request, results in a cleaner, more composable API. Generously, you could argue that “just add i
to every function” has practical convenience, despite wtfs like map(parseInt)
. But that is a debatable counterargument to the criticism. The criticism is at minimum reasonable, even if you don’t think it’s correct.
Also, the fact that map()
takes a function that expects 3 args (IIRC) but is commonly passed a function with one argument and things don’t blow up (neither at runtime nor at compile time in TypeScript) is pretty strange and probably the cause of a lot of these kinds of bugs. In general, JS/TS’s approach to “undefined” is difficult for me to intuit about and I’ve been using JS on and off for about 15 years.
map passes multiple arguments
It’s vaguely reasonable to not know this, but also the signature of map’s functor is on the first line or two of any documentation.
it’s not an error to call a function with the wrong number of arguments
Because variadic arguments (and the arguments array) are first class language features. This is where we get into “this is a core JS language feature, you really need to know it exists”
Variadic functions are fine, the confusing thing is that all functions can receive 0-N args instead of only those that opt in. Yes this has been in JS from the start, but it’s unusual in casual JS use and in other popular languages.
It is reasonable - and can be useful - for map() to pass things like the index as argument to the mapped function.
It is not. You shouldn’t concern yourself with indices at all if you’re using map
et al, and if you need that index, just zip the collection with its indices before mapping.
Or you can remember that other languages have other idioms, and that the index can be useful in principle. There’s also an additional parameter which is the array being enumerated. Together these can be useful.
You my have a functional language puritanical belief about what the API should be, but that’s a subjective opinion, hence it is reasonable that the functor receive additional information about the iteration, especially in a language like JS where variadic functions are first class.
I have the utmost respect for different languages having different idioms (I’ve used quite a lot of OSes and languages throughout the years and am myself bothered when people raised on a blend of Windows, Linux and C-style languages complain that things are just different to what they are used to.) but in this case, it’s just a fractal of bad design.
It’s not just because the map
API is misleading, it’s because the extra arguments are thoroughly superfluous and tries to fit imperative idioms into a functional one.
But first and foremost, it’s a bad design because it never took into account how it would match the default parameter convention of JS.
One could also remark that parseInt
is a strange global function with a strange name in an object-oriented language with some FP features.
You are right. These are totally reasonable APIs. parseInt in particular is totally fine. For map, it’s a little quirk that’s different than maps in other languages, but it’s forgivable because it is useful.
It really bothers me because of how Javascript functions are called. As roryokane says, you can get around the API by defining an extra function that only takes one argument:
[“10”, “10”, “10”].map(n => parseInt(n))
And that works because Javascript functions will ignore extra arguments instead of throwing an error. Ultimately, it’s that looseness that bothers me.
You’re complaining about dynamic typing then, not JS.
Plenty of languages have varargs, plenty of languages have default parameters. The reason map(parseInt) works is because there is no type checking beyond “can be called”.
If you do want “arguments must match exactly”, then all your common calls to map would need function wrappers, because they’d trigger errors from not handling all three arguments.
You’re complaining about dynamic typing then, not JS.
No, I’m not. Python is dynamically typed, but it will throw an error if you provide an extra argument. Python also has varargs, but it’s opt in which I find reasonable. I want dynamic checks at run time, which this behavior doesn’t give me. (Or checks at compile time. Mostly I want to know if I screwed up.)
Right, but parseInt taking an optional base argument is reasonable, and that makes the arity match; python wouldn’t raise an error there.
Again, parseInt is fine. Default arguments are fine. I don’t like how you can pass extra arguments to a function and they get ignored. I want them to throw an error.
There are a bunch of problems. First variadic functions are a first class and relatively fundamental concept in JS, so you’ve got a different language style. That’s just a basic language choice - you can disagree with it, but given that fact you can’t then say it’s unreasonable when variadic nature of calls comes into play.
Second, I could make a JS style map() function, and pass python’s int() function to it, and it would be called just as JS’s is, because the additional parameter is optional, and hence would be fine. The only difference is that python bounds checks the base and throws an exception.
The issue here is that variadic functions are core to JS from day 1. We’ve generally decided that that is “bad” nowadays, but given the functions have always been variadic in JS, large swathes of APIs are designed around it.
Yes, that’s why I try to avoid JavaScript. There are lots of decisions that were made that seemed reasonable in the past, but turned out to have negative consequences that persist. I’ve been burned by some of them and have a negative opinion of the language.
This is just not a WAT. What it is, is a classic example of people pretending reasonable behaviour and acting as though it is WAT. If you pass a function that takes multiple parameters (parseInt is in modern syntax: function parseInt(string, base=10)), and passing it to another function that will call it with multiple parameters, and then acting like the result is a weird behavior in the language, rather than a normal outcome that would occur in any language.
Start with:
function parseInt(string, base=10) -> int; // which is a fairly standard idiom across many languages for defining this function
and then say “map passes the value and the index to the mapping function”, and ask what the outcome would be.
What we’re seeing here is a person saying “I am blindly assuming the same map API as other languages”, and then acting like the API not being identical is somehow a language WAT. It’s right up with the WATs claiming “’{}’ is undefined and ‘+{}’ is NaN”, or “typeof NaN == ‘number’”, etc
Then again, your reply is a classic example of a language afficiando taking a clear WAT and explaining it as reasonable behaviour – even though it quite clearly is a bit of a problem for users of the standard map function coming from practically every other language.
Granted, like almost every other WAT, it’s a rather minor one for anyone who’s been writing JS/TS for more than a week. Still, I’d very much like a linter to automatically catch these for me. Even experienced people make dumb mistakes sometimes, and it’s just a complete waste of time and energy. Rust, a much stronger typed language (whose type checker already wouldn’t allow this), contains a linter that does catch problems similar to this one.
Then again, your reply is a classic example of a language afficiando taking a clear WAT and explaining it as reasonable behaviour – even though it quite clearly is a bit of a problem for users of the standard map function coming from practically every other language.
I’m unsure what to say here. map in JS passes the index of the iteration, and the array being iterated. That is what it does. If you pass any function to map it will get those parameters. If the function you pass has default parameters, those will be filled out.
Similarly, parseInt takes an optional base that defaults to 10.
I get that people are quite reasonably tempted to do map(parseInt), but I cannot work out what JS is meant to do here different, unless people are saying map should explicitly check for parseInt being passed in?
I suppose the simplest way to fix this would for map’s type to be like it is in Haskell, i.e.
(a -> b) -> [a] -> [b]
So the function map takes as argument takes just one argument, and map does not pass an index of the iteration.
I don’t doubt that there are valid reasons why this would be cumbersome in Javascript/Typescript, but I’m not familiar with them. Index of the iteration is not typically needed in functional style, so it’s kind of redundant and as shown here, potentially harmful.
Start with:
function parseInt(string, base=10) -> int; // which is a fairly standard idiom across many languages for defining this function
and then say “map passes the value and the index to the mapping function”, and ask what the outcome would be.
Alas, that’s backwards. Things are surprising not when they are inexplicable in hindsight, but when they are not expected in advance. You have to start from what the user sees, then follow the network of common associations and heuristics to identify likely conclusions.
If the common associations lead to the correct conclusion, the interface is probably fine. If common associations lead to the wrong conclusion, there will be many mistakes — even by users who also know the correct rules.
So in our case, the user starts by seeing ths:
[10, 10, 10].map(parseInt)
Now the user has to remember relevant facts about map
and parseInt
and come to the correct conclusion. Here’s a list of common associations that lead to the wrong answer.
parseInt(mystring)
to mean parseInt(mystring, 10)
parseInt(mystring)
without needing to remember parseInt(mystring, base)
existsf
is simply another way to write x => f(x)
Alas, that’s backwards. Things are surprising not when they are inexplicable in hindsight, but when they are not expected in advance. You have to start from what the user sees, then follow the network of common associations and heuristics to identify likely conclusions.
parseInt takes a base argument. map provides the base. the semantics of both are reasonable. The same thing would happen in any other language.
Now the user has to remember relevant facts about map and parseInt and come to the correct conclusion.
Yes. the user should know the APIs that they’re using. The problem here is that many modern languages have a version of map that has a different API, and end users blindly assume that is the case.
Array.map() provides a third argument. Other languages would blow up when parseInt()
was called with two arguments. You seem to argue elsewhere that JavaScript’s parseInt() accepts more than 2 arguments (because all functions in JavaScript accept ~arbitrarily large numbers of parameters), but that’s precisely what’s bizarre (and as many contend, error prone) about JavaScript–that we can call a function whose signature is function(a, b)
with 3 arguments and it doesn’t blow up.
The issue isn’t that people don’t understand map()
‘s signature, it’s that JavaScript is unusually permissive about function calls which makes it easy (even for people who understand that JavaScript is unusually permissive about function calls) to make mistakes where they wouldn’t in other languages.
No, you want the over application to work, because that’s how JS works, hence how APIs are designed. The alternative is that you would have to do (for example)
someArray.map((value,a,b)=>value*2)
I get that people don’t like JS functions being variadic, but they are, and that means APIs are designed around that variadic nature.
I mean, the obvious alternative is to make map()’s functor accept one argument, but that’s beside the point. We were debating about whether Javascript was uniquely confusing and the answer is “yes, Javascript functions are (uniquely) variadic by default, and this seems to result in many bugs and WATs”. To add to the confusion, it also has a syntax for expressing that the programmer intends for the function to be variadic: function(…args).
If you pass a function that takes multiple parameters (parseInt is in modern syntax: function parseInt(string, base=10)), and passing it to another function that will call it with multiple parameters, and then acting like the result is a weird behavior in the language, rather than a normal outcome that would occur in any language.
It seems like the Go compiler disagrees with you:
func CallWithThreeArgs(f func(int, int, int)) {
f(0, 0, 0)
}
func TakesTwoArgs(int, int) {}
func main() {
// cannot use TakesTwoArgs (value of type func(int, int)) as func(int, int, int) value in argument to CallWithThreeArgs
CallWithThreeArgs(TakesTwoArgs)
}
https://go.dev/play/p/BY7rRRqRMwt
As does the Rust compiler:
fn call_with_three_args(f: fn(i64, i64, i64)) {}
fn takes_two_args(a: i64, b: i64) {}
fn main() {
// note: expected fn pointer `fn(i64, i64, i64)`
// found fn item `fn(i64, i64) {takes_two_args}`
call_with_three_args(takes_two_args);
}
Here’s Python:
def call_with_three_args(f):
f(0, 0, 0)
def takes_two_args(a, b):
pass
// TypeError: takes_two_args() takes exactly 2 arguments (3 given) on line 2 in main.py
call_with_three_args(takes_two_args)
You’re right, calling a function with an incorrect number of parameters does fail. So let’s try what I said: calling a function assuming one API, when it has another. It’s a bit harder in statically typed languages because (A)->X and (A,B=default value)->X are actually different types. In C++
The problem is you’ve misunderstood the claim. The claim isn’t “I can pass a function that takes N and expect it to be called with M arguments” it is “I can pass a function that takes a variable number of arguments, and that will work”. Referencing rust isn’t relevant as it has no concept of variadic functions or pseudo variadic with default parameters. C++ does, and I can make that happen with [](std::string, int = 10){...}
which could be passed to a map the passes an index or one that does not, and both will work.
Your example of python however is simply wrong. If python’s map passed the index:
def map(array, fn):
result = []
for i in range(0, len(array)):
result.append(fn(array[i], i))
return result
you could call that map function, passing int() as the functor, and it would be called. The only reason it would fail is int() bounds checks the base. The behaviour and language semantics are the same as in JS.
Now you can argue about details of the map() API, but the reality is the JS is a language that allows variadic calling, so it is a reasonable choice to provide those additional parameters, You can argue much more reasonably that parseInt should be bounds checking the base and throwing an exception, but again the language semantics and behaviour are not weird.
The problem is you’ve misunderstood the claim.
Your claim was that any language would behave as JavaScript does when passing multiple parameters to a multiple parameter function.
I can pass a function that takes a variable number of arguments, and that will work
Ok, so you’re arguing that unlike other languages, all functions in JavaScript take a variable number of arguments which is just rephrasing the criticism that JavaScript is unusual (thus more programmer errors) and contradicting your claim that it’s behaving as any other language would do (e.g., “but again the language semantics and behaviour are not weird”).
Your example of python however is simply wrong. If python’s map passed the index:
Respectfully, you’re mistaken. Array.map()
passes a third parameter to fn
. If we update your Python example accordingly, it blows up as previously discussed:
def map(array, fn):
result = []
for i in range(0, len(array)):
result.append(fn(array[i], i, array)) // TypeError: int() takes at most 2 arguments (3 given)
return result
I’ve never heard the acronym WAT before, but I would call JS’s “map” function a footgun for sure. It’s named after a classic/ubiquitous functional, uh, function dating back to early LISP, and has the same purpose, but passes different parameters. Yow.
(But I have no problem with parseInt, or JS’s parameter passing in this instance. It’s all on map.)
Here’s a detailed answer adapted from a chat message I once sent to a coworker.
Starting from this JavaScript console transcript, in which I’ve split out a strs
variable for clarity:
> const strs = ['10', '10', '10']
> strs.map(parseInt) // gives the wrong result?
[10, NaN, 2]
The correct way to write this is to wrap parseInt
in a single-argument anonymous function:
> strs.map(x => parseInt(x))
[10, 10, 10]
strs.map(parseInt)
gives unexpected results because of three factors:
Array.prototype.map
calls the passed function with multiple arguments:
> strs.map((a, b, c) => [a, b, c])
[
['10', 0, strs],
['10', 1, strs],
['10', 2, strs],
]
The arguments are element, index, array
.
The parseInt
function takes up to two arguments – string, radix
:
> parseInt('10') // parse with inferred radix of 10. That is, '10' in base 10 is…
10
> parseInt('10', 10) // parse with explicit radix of 10
10
When calling a function in JavaScript, any extra arguments are ignored.
> const plusOne = (n) => n + 1
> plusOne(1)
2
> plusOne(1, null, 'hello', {a: 123})
2
Thus, the incorrect version was calling parseInt
like this:
> parseInt('10', 0, strs) // parse with explicitly assumed radix due to `0`
10
> parseInt('10', 1, strs) // parse with invalid radix of 1
NaN
> parseInt('10', 1, strs) // parse with radix 2. That is, '10' in base 2 is…
2
Yeah, uh, much as I love JavaScript, parseInt()
is probably the worst place to look for elegance in the design of its equivalent of a standard library. It doesn’t even necessarily default to a radix of 10.
After skimming Wikipedia’s page on fixed-point arithmetic, I think you are right.
Almost 20 years ago, I read a paper by some HCI folks who wanted to test how good the Star Trek computer interface would be. They simulated it by having a series of tests where either a human used a computer directly or a human asked another human to use a computer. They timed the tasks and subtracted the time that it took for the human who was pretending to be the computer to interact with the computer. In the majority of cases, the human using a GUI or CLI was faster. Humans evolved to interact with the universe with our hands long before we developed speech, so this wasn’t surprising. The only tasks where the voice interface was best were the ones that required a lot of creativity and interpretation by the user and even then they often required a lot of iteration because humans are really bad at asking for exactly what we want in an unambiguous format.
I think augmented reality with haptic feedback, done well, would be far more of a revolution in how we interact with computers than any natural language model.
Maybe I’m understanding it wrong but that’s no surprising outcome and also not testing Star Trek Computers?
Human -> Human -> Computer
is a longer path than
Human -> Computer
So why would it be faster?
They timed the tasks and subtracted the time that it took for the human who was pretending to be the computer to interact with the computer
Perhaps this is the key point?
That’s an interesting-sounding paper. I tried to find it, is it this one? I was able to find a really crappy scan of the full text.
I’m afraid the paper you found doesn’t match @david_chisnall’s description.
David described an experiment involving humans listening to voice input and then operating a computer according to those instructions. In the paper you linked, “Voice recognition based human-computer interface design” by Wei Zhang et al., voice input went not to humans, but to “InCube software, which is a voice recognition software that […] handles lexicons of up to 75 commands”. Its voice recognition capabilities sound much weaker than that of humans:
For the voice input interfaces, the ASGC [automatic semester grade calculation] software is pre-loaded with the oral commands such as “start”, “enter”, “OK”, etc., as well as 10 digits and 26 letters. A command consists of several keystrokes, a voice template, and an attribute. The commands are then linked with the equivalent keystrokes. Before a command can be recognized, a corresponding voice template must first be created.
I, too, would be curious to read the paper @david_chisnall described.
I wonder how different it is when one doesn’t have the high-bandwidth output channel of vision. It would be interesting to repeat the experiment except with blind people.
The way Tony Stark uses JARVIS in the marvel movies kinda makes more sense, now that you mention it. Even though he uses voice commands extensively, there’s almost always a visual, and often, if not tactile, at least manipulatable, holographic, component.
The voice commands are either for doing background tasks, or for additional, complex, queries or transformations in the visual representation.
But it’s all focused on the visual and touchable representation: the voice commands are shortcuts, not the main interface. Like keyboard shortcuts in a GUI program.
Looks like a much cooler future than blank text boxes everywhere.
Reporting a website bug:
When viewing the linked story in Chrome 108 on macOS, but not in Firefox 109 on macOS, some symbols are displayed as empty squares.
For example, this sentence with the up tack symbol (representing the bottom type)
(I use scare quotes because we quietly ignore ⊥).
looks like
(I use scare quotes because we quietly ignore □).
And this sentence with a rightwards arrow
Hask × Hask → Hask
looks like
Hask × Hask □ Hask
Both pieces of text are using the browser’s default serif font thanks to the CSS .math {color: #000;font-family: serif}
. In both Chrome (broken) and Firefox (working), the default serif font is “Times”.
Interesting. Do you have any suggestions on how to fix this? I try not to have strong opinions about which font to use because I want to leave that choice up to the user agent.
Note this important sentence at the end of the introduction, which I overlooked at first, causing confusion:
I’ll assume knowledge of categories, functors, and natural transformations.
Asking as someone with only vague knowledge of those concepts – knowledge that was acquired only due to curiosity about the meme phrase – is it common that people would understand those three concepts in category theory but not the concepts explained in this blog post? For example, is there some common type of analysis that requires knowledge of categories and functors but not knowledge of bifunctors or monoidal categories?
As Corbin says, the first three topics in many introductions are categories, then functors between categories, then natural transformations between functors. I chose to take them as given because a) the article was getting long enough already and b) Haskell programmers curious about the meme words have probably seen Haskell versions of those concepts in their regular programming or research.
Categories are usually the first defined concept when studying category theory, and functors and natural transformations follow afterwards. (Historically, natural transformations were the main definition; categories and functors are auxiliary concepts.) For example, the Wikibook for category theory has them as the first three chapters, and the Rosetta Stone paper has them in the first four definitions. They are a common starting point.
You will not often encounter categories without any monoidal structure whatsoever. It’s just too easy to take two data and form a pair, and that’s often all that’s required. Some folks prefer to work only with operads because of this, and think of categories as degenerate special cases of operads. The best way to understand operads is with a picture or two.
I learned the beginnings of Category Theory during Math grad courses and saw many concrete examples of categories, functors, and natural transformations in other Math domains; yet I did not understand the details of why Haskell Monads are called Monads until I read this blog post. That’s partly because I haven’t yet seen a reason to care about Category Theory in a software context, but in any case I probably wouldn’t have come up with this explanation on my own without a lot of effort.
You may be interested to learn that every programming language has an associated native type theory and categorical structure.
Thanks. What would really pique my interest is an example of a real software problem where Category Theory concepts make it substantially easier to reason about.
If I recall correctly, Haml didn’t actually build an AST - it just translated the syntax back to strings. And if you had a sub-template, you could call it (regardless of whether it was Haml or Erb) and get the results into the Haml via a string. So just having something that looks like it’s an AST doesn’t mean it actually has the security benefits of one.
Note that nowadays most “string” solutions automatically escape their template-injected strings unless you explicitly ask it not to, so the problem isn’t as big as it used to be (although there’s still the problem of contextual placement, as the article points out). Nevertheless, I totally agree that a structural approach where you actually represent the tree you’re trying to write out as a string is the proper solution.
Yeah I agree that the issue is more complex. On closer inspection, the article doesn’t actually justify what the title claims.
i.e. It’s not clear to me that AST approaches are actually more secure than string-based ones in practice.
https://lobste.rs/s/j4ajfo/producing_html_using_string_templates#c_smlpfm
The comparison seems to be between real deployed string approaches, vs. mostly hypothetical AST approaches.
AFAICT, the real AST-based approaches ALSO have the contextual escaping problem. Interested in counterexamples.
AFAICT, the real AST-based approaches ALSO have the contextual escaping problem. Interested in counterexamples.
If you treat URLs as proper objects, this is not a problem.
For example, let’s say we want to construct an URI based on a base URI with two params (for the sake of example they’re hardcoded here):
(let ((link-url (update-uri base-url query: '((param-a "this is" param-b "some&special stuff"))))
(link-text "some&other special < stuff"))
`(a (@ (href ,(uri->string link-url))) ,link-text))
This then encodes to:
<a href="http://www.example.com/some/page?param-a=this%20is&param-b=some%26special%20stuff">some&other special < stuff</a>
The uri library is responsible for correctly url-encoding query parameters and path components, while the SXML library is responsible merely for correctly HTML/XML-encoding the attribute and text nodes. I think this is as it should be, the XML stuff shouldn’t even be aware of URI-encoding.
NOTE: JavaScript in HTML is a different kettle of fish, unfortunately it doesn’t compose very well - AFAIK, JavaScript cannot be XML-encoded, so for example the following would be the logical thing to do when encoding JS inside XML and hopefully also HTML:
<script type="text/javascript">
if (10 > 2) {
window.alert("News flash: 10 is bigger than 2");
}
</script>
Unfortunately, this is a syntax error. <script>
blocks behave more like cdata
sections than regular tags. This is no doubt for ergonomic reasons, but it also means that any script handling needs to be special cased. In SXML you could typically do that with a preorder rule that shortcuts the regular encoding. But it’s ugly no matter how you slice it.
Right, good examples. I’m not saying the problem can’t be solved in principle – just saying that I don’t see a lot of practical and robust solutions for it. That is, what are the solutions in HAML and XHTML approaches for embedded languages and auto escaping (JS, URIs, URIs in URIs), and are they deployed in real apps?
It does seems like the Lisp solutions have the strongest claim, but I think they are mainly used for “simple” sites like Hacker News (which is good IMO – I like simple sites)
But I’d say it’s a little like comparing Python and Standard ML … Standard ML is nice and clean in its domain, but not really a fair comparison, because Python has to deal with a wider range of problems, because it’s used 1000x more
That is, what are the solutions in HAML and XHTML approaches for embedded languages and auto escaping (JS, URIs, URIs in URIs), and are they deployed in real apps?
The idea would work just fine in any other language. For example, if you’re using an embedded DSL like haml, you could do exactly the same thing:
- some_url = url_for(action: 'index', params: {param_a: 'this is', param_b: 'some&special stuff'})
%a{href: some_url.to_s} Some text
Unfortunately, this requires Rails. AFAICT, the standard Ruby URI library doesn’t have a clean way to add parameters from a hash to a query string (with auto-escaping, no less), so the example is not the best.
BTW, if you have automatic string coercion methods you wouldn’t even have to call to_s
on the result. I can’t remember if Haml does that.
One example of a system for safely building URLs programmatically is Ruby on Rails’s feature of path and URL helpers. However, that only helps with building URLs internal to the app.
The way it works is that the most common ways of defining a route also define methods that can build a URL to that route. For example, if you create a route /comments/:id
, then in your view (whether it’s ERB, Haml, or another templating system), you can call the Ruby method comment_path(123)
to get /comments/123
or comment_url(123)
to get http://example.com/comments/123
.
I always really liked the syntax for constructing internal URLs. It’s too bad they didn’t build something equally ergonomic for constructing external URLs (AFAIK).
Reporting a typo in this article:
the excellent Hypefine command
“Hypefine” should be “Hyperfine”, or “hyperfine” to match the capitalization in the tool’s README.
You wrote that you’d like this to be possible:
let generatePerson = name => {name, id: generateId()}
[In today’s JavaScript] instead you need to do:
let generatePerson = (name) => { return { name, id: generateId() }; };
To make your code work with today’s JavaScript, you only need to wrap the object literal in parentheses (docs):
let generatePerson =
name => ({name, id: generateId()})
No, they actually said that, it doesn’t work but it should because it follows from the syntax afterwards that we can’t do anything else.
I’m a way it’s a cool idea, but what if you really wanted to open that block, but mistyped something? Now instead of a syntax error your little helper does something unexpected.
The imports point is partially okay as well, but the tooling is already good enough that I don’t know what’s up there, it just works.
I’d be somewhat partial to your point if this wasn’t JavaScript, but it is. It’s already a language where mistyping something causes any kind of early hard error is the exception, not the rule. I don’t think this particular case would make the problem worse in any perceptible way.
Perhaps, but the tooling is working wonders, compared to, say, 10 years ago.
All I’m saying is that these seem like nice ideas but they seem to me like they’re not thought entirely through.
I haven’t actually used them, but KDL’s “slashdash” comments to knock out individual elements are pretty interesting. From The KDL Document Language:
On top of that, KDL supports
/-
“slashdash” comments, which can be used to comment out individual nodes, arguments, or children:// This entire node and its children are all commented out. /-mynode "foo" key=1 { a b c } mynode /-"commented" "not commented" /-key="value" /-{ a b }
It’s a little more clear with the syntax highlighting on the site.
Clojure supports something similar with its #_
reader macro, which makes the reader ignore the next form. It’s pretty handy for debugging.
#_(println "don't print me")
(+ 1 #_2 3) ; equals 4
Clojure also has a comment
macro that ignores its body and evaluates to nil
. I rarely use it.
Racket (and maybe other Scheme-family?) has this as well with S-expression comments: #;
Easy to remember since ;
is a line comment.
To save people time: the documentation only explains how to run the web server on Linux or GNU or in a virtual machine. The project links to no public servers where users of other operating systems could try it.
For examples of these not mentioned: Erlang has symbols called atoms, VB.NET has date literals (and XML literals).
Erlang also has pattern matching over binaries. I don’t want this as a language feature, but I do want sufficient language features to be able to implement this as a library feature. I hate writing any low-level network or filesystem (or binary file-format) code in non-Erlang languages as a result. This is especially annoying for wire protocols that have fields that are not full bytes and most-significant-bit first. Erlang handles these trivially, C forces you to experience large amounts of pain.
Yes - binary pattern matching is pretty much the biggest thing I miss on anything not BEAM. There’s all sorts of little touches to make it error-free too, like the endianness notation and precise bit alignment.
Why stop there? In Common Lisp reader macros allow you to pretty much do whatever you want! Want a date literal? Cool. Want to mix JSON with sexpressions? No problem! Want literal regex? Yup, can do that too.
I don’t think there’s a joke in there. s-expressions
is the way it’s spelled. The hyphen emphasizes “Ess expressions” over “sex pressions”
Now I’m confused! :)
If you were joking, I didn’t pick up on it. I wasn’t trying to be pedantic, just offer the background of why it’s s-
and not just plain s
.
Examples of symbols:
:example
example
:example
:example
'example
Clojure also has Symbols, IIRC called keywords (I guess because they are most often used as keys in mapping data structures).
They’re also called keywords because Clojure has a different construct for symbols. (The subtle distinction between when to use a keyword and when to use a symbol is a common “discussion” point with new adopters.)
There was an attempt to add XML literals and XML querying syntax to JavaScript (the E4X syntax), but it never got wide enough support.
Scala has XML literals. I imagine this is the PL equivalent of seeing someone’s old picture from high school.
A lot of deployment tools require you to define the deployment steps in YAML files:
These tools end up reimplementing programming language features like variables, conditionals, and function calls within the data structures supported by YAML. They support using YAML’s alias nodes feature to emulate variables or functions, but might additionally define their own variable feature that supports string interpolation.
The configuration languages CUE and Dhall seem to provide a better foundation for deployment tools than YAML does.
Another turn around the configuration complexity clock, wheeeeeee! http://mikehadlow.blogspot.com/2012/05/configuration-complexity-clock.html
Wow, I’ve never heard of alias nodes. YAML is way more complicated than I thought. I use GH actions but never really considered how configuring it is kind of like a language in itself
Surprised this hasn’t been mentioned yet: UBlock Origin.
Link: uBlock Origin