HNHacker News
TopNewBestAskShowJobs

hddqsb

421 karma · joined September 6, 2017

submissionscomments
hddqsb··on Pattern matching and tail recursion in Emacs Lisp
First time I'm hearing about `named-let` and tail call optimisation in Emacs, thanks for sharing!

A clarification: Tail call optimisation in Emacs is not tied specifically to `named-let`, but it is tied to `cl-labels` (which `named-let` uses internally). Normal functions declared using `defun` don't currently support tail call optimisation.

(The tail call optimisation was only added to `cl-labels` recently, in Emacs 28.1: https://github.com/emacs-mirror/emacs/commit/29c7f8c915c3889...)

hddqsb··on Pattern matching and tail recursion in Emacs Lisp
I was about to post a similar comment. My version might be easier to understand, so here it is.

------------

The author confuses "apply" and "reduce" (aka "fold").

- `apply` calls a function once, passing it all the specified args. It is very similar to `funcall`, except the last argument of `apply` is a list of args.

- `reduce` repeatedly calls a function on an accumulator and each element of a list.

Examples:

    (vector 1 2 3 4)
    => [1 2 3 4]

    (funcall 'vector 1 2 3 4)
    => [1 2 3 4]

    (apply 'vector 1 2 (list 3 4))
    => [1 2 3 4]

    (seq-reduce 'vector (list 1 2 3 4) 0)
    => [[[[0 1] 2] 3] 4]
The author's confusion is probably because `+` in Lisp is variadic, so `(apply '+ nums)` and `(seq-reduce '+ nums 0)` happen to produce the same result.

------------

Also, the way the author's implementation guesses the initial value is awful. The standard practice is to either ask the caller to explicitly provide the initial value (which is what `seq-reduce` does), or to use the first element of the list as the initial value.

(The latter has the advantage of being simpler to use, since the programmer doesn't need to find the correct identity element that needs to be passed as the initial value (e.g. for + the identity is 0, but for * the identity is 1). The disadvantage is that it fails on empty lists. Also, in statically-typed languages it can only supports binary operators, so it is not as powerful as the version that takes an explicit initial value.)

hddqsb··on Nix-Lisp: A Lisp implementation written in Nix
Hi! It looks quite polished for a weekend-style project :) But the "Current state" section is upfront about it. I should have noticed the commit date; I guess "archiving" the project would have sent a stronger signal.

Please don't rewrite it on my account (I'd just mention laziness and fix the `if` bug since it's trivial: https://github.com/utdemir/nixlisp/blob/4e0eec383363df72b7b4..., the comment already says the right thing). It would be interesting to see the "bork" examples if you still have them on hand (I'd even put them in the README).

It was fun to play with, thanks for making and publishing!

hddqsb··on Nix-Lisp: A Lisp implementation written in Nix
Eh, that's just quibbling over the syntax. The `define` forms could be rewritten as `let` to make things look more "pure".

(The only situation where `define` might be considered "impure" is if it was used to redefine a variable that already existed in the same scope; and even then, it could still be considered "pure" as long as it created a new variable instead of mutating the original variable.)

hddqsb··on Nix-Lisp: A Lisp implementation written in Nix
Some comments in case the author is here:

1. You should make it clear in the README that this uses lazy evaluation, because most Lisps use strict evaluation.

2. `lambda` should have an implicit `begin` around its body to allow multiple forms, e.g.

    (lambda ()
      (define x 1)
      (+ x x))
(as of commit 4e0eec383363, calling this lambda fails with "list too long").

3. The behaviour of the top-level forms changes when they are wrapped in `begin`. For example, the program

    (car 1)
    2
succeeds (due to lazy evaluation), but wrapping it in `(begin ...)` causes it to fail with "expecting a cons". If the intent is to use lazy evaluation, then both versions should succeed.

4. Since this dialect is pure (no side effects), it should be a syntax error if a non-final form in a `begin` expression expands to some expression which is not a declaration. For example,

    (defun f (x)
      (g x)
      (h x))
should be a syntax error, because the call to `g` is useless (unless it's a macro that expands to `define`).

In the same vein, you could make it a syntax error if the final form in a `begin` expression is `define` (and likewise for the forms in an `if`).

5. The way `if` handles values other than true/false is backwards compared to most Lisps (and based on the comments in the code I think it's an accident). For example

    (if "foo" "first" "second")
evaluates to "second", but in most Lisps it would evaluate to "first" (because strings are considered "truthy").
hddqsb··on Nix-Lisp: A Lisp implementation written in Nix
Ha ha, I had the same initial reaction. That's regular Nix code, which is used to run the Lisp code (the stuff between the first '' and the last '').
hddqsb··on Size is the best predictor of code quality (2011)
The article's title refers to "code quality", but the word "quality" never appears in the body. The article is about the number of bugs, so I think a more appropriate term is "software quality" (more specifically "software functional quality", per Wikipedia; but of course it would be clearest if the title just said "number of bugs").

To me, "code quality" means something very different (well-written, readable, maintainable, etc.; Wikipedia calls this "software structural quality"). According to this definition, "code quality" may be correlated with the number of bugs (hopefully with a negative correlation!) but the two are very different things.

And I'd like to boost @kragen's comment (https://news.ycombinator.com/item?id=33566329#33579042, toned down):

> haldar's summary is ... incorrect ...

> the paper finds that faults (and wmc (methods per class), cbo (coupling between object classes), response for a class (rfc), number of methods added (nma), etc.), correlate to class size (non-comment source lines of code), not program size

hddqsb··on Resolving the great undo-redo quandary
(But also buggy...)
hddqsb··on Resolving the great undo-redo quandary
This sounds very similar to Emacs' undo history, except Emacs doesn't implement the "window shopping" optimisation. Instead, Emacs has a command `undo-only` which is similar to `undo` except it will skip over undo-redo chains.

> Thus you can use up all available memory in approx. 64 quick steps by: ...

I disagree. If the editor uses a very basic implementation, then "Undo to beginning" would probably require the user to perform multiple undo steps, in which case the memory usage would be linear in the number of steps rather than exponential. If the editor supports "Undo to beginning" as a single operation, then it would be reasonable to expect a more sophisticated implementation that does it efficiently -- memory usage can be reduced by sharing the repeated sections of the undo/redo stacks, and it is possible to efficiently skip over undo-redo sequences by recording which states are identical. Exponential memory usage would be indicative of a lazy programmer rather than a fundamental issue with the GRUQ-orithm :)

hddqsb··on Resolving the great undo-redo quandary
This. To elaborate, the problem is that without the "window shopping" optimisation, you are forced to repeat all your "window shopping" excursions twice every time you want to go a little further back in time.

To illustrate how horrible this is, I'll continue the example: When you paste that something, you get state4. Suppose you then wanted to copy something from state1. Without the "window shopping" optimisation, you would have to go state4,state3,state2,state3,state2,state1, copy, state2,state3,state2,state3,state4, paste.

If you then wanted to copy something from state0, it would take 34 steps!

hddqsb··on Google Ad Disguising Itself as www.gimp.org
Update: It used to be possible to set the Display URL field to an arbitrary value, see for example https://www.youtube.com/watch?v=AQFR8eJlYxQ&t=100 (2015). Maybe it's still possible somehow.
hddqsb··on Google Ad Disguising Itself as www.gimp.org
That's a good theory, but I don't think it's what happened here. The ads are gone for me now so I can't verify, but when I did get the ad I checked the network monitor and I'm pretty sure there were no redirects via lookalike domains. Also, I'm pretty sure I copy and pasted the display URL and it was plain ASCII.
hddqsb··on Google Ad Disguising Itself as www.gimp.org
Do you know how that can be done? Based on the video https://www.youtube.com/watch?v=jx-gl6K2zQw it looks like only the display path can be edited (not the domain).
hddqsb··on Google Ad Disguising Itself as www.gimp.org
Just to avoid no confusion, the issue here is that the URL displayed in the ad (and also when hovering over it) has a different domain from the page the user lands on when they actually click the ad. It's not about whether the advertiser owns the domain.
hddqsb··on Google Ad Disguising Itself as www.gimp.org
That's what I thought too, but I managed to get the malicious ad and confirmed that it's a destination mismatch in Google Ads rather than an open redirect (no requests to gimp.org in the network monitor).
hddqsb··on Google Ad Disguising Itself as www.gimp.org
EDIT: There is definitely a mismatch between the display URL and the landing page URL. It's not clear to me how that can happen; for example https://www.youtube.com/watch?v=jx-gl6K2zQw shows that only the display path can be edited (not the domain), consistently with the wording on https://support.google.com/google-ads/answer/2616010 and https://support.google.com/google-ads/answer/2375287. On the other hand, https://support.google.com/adspolicy/answer/6368661 talks about destination mismatch as if it is technically possible and just forbidden by policy.

The ad's ID is DChcSEwiPvfuL-YX7AhVmkmYCHUXQC1wYABAAGgJzbQ (displayed when reporting it), the display URL is https://www.gimp.org/ and the final location after clicking the ad is https[:]//gilimp[.]org/ (with no intermediate redirects via gimp.org).

Update: The DNS records for gilimp.org have been deleted. Archived snapshot: https://web.archive.org/web/20221029152445/https://gilimp.or....

-------------

Original comment:

The Reddit user says the ad's display URL was different from landing page URL. If that's the case it is particularly concerning. I believe Google Ads only allows the advertiser to set the path component of the display URL, and takes the domain from the landing page (real) URL; so it's unclear how the mismatch could happen.

Maybe the Reddit user took the screenshot on a separate occasion from when they clicked the malicious link, and the ad changed in that time (currently I can see an ad for GIMP, and it links to the official domain, and the linked Twitter thread linked by @pmoriarty says the attacker is actively changing things). The only other explanation I can think of is that the official GIMP website has an open redirect vulnerability.

hddqsb··on Shell script best practices, from a decade of scripting things
> Check if the first arg is `-h` or `--help` or `help` or just `h` or even `-help`, and in all these cases, print help text and exit.

`-h` and `--help` are fine. But `help` and `h` should only display the help if the script has subcommands (like `git`, which has `git commit` as a subcommand). Scripts that don't have subcommands should treat `h` and `help` as regular arguments -- imagine if `cp h h.bak` displayed a help message instead of copying the file named "h"!

I wouldn't encourage `-help` for displaying the help because it conflicts with the syntax for a group of single-letter options (though if `-h` displays the help, there is no legitimate reason for grouping `-h` with other options).

And ideally scripts that support both option and non-option arguments should allow `--` to separate them (e.g. `rm -- --help` removes the file called "--help"). But parsing options is complicated and probably out of scope for this article.

> If appropriate, change to the script’s directory close to the start of the script. And it’s usually always appropriate.

This is very problematic if the script accepts paths as arguments, because the user would (rightly) expect paths to be interpreted relative to the original working directory rather than the script's location. A more robust approach is to compute the script's location and store it in a variable, then explicitly prepend this variable when you want paths to be relative to the script's location.

hddqsb··on Show HN: Fractal Garden – An Exhibition of Mathematical Beauty
You can allow more zooming on the Mandelbrot Set by increasing maxIterations.

(Note that the colours will change because of the way the code is written. Also, it will require more computation, so it's probably a good idea to only increase maxIterations when zoomed in.)

hddqsb··on Xonsh: I don't remember how to write a for loop in Bash [video]
It works in bash/zsh/ksh but not in sh/dash. Examples:

    for i in 1 2 3 4 5; { echo $i; }
    for ((i=1;i<=5;i++)) { echo $i; }
(It doesn't work with `while` loops, at least not in bash.)
hddqsb··on Xonsh: I don't remember how to write a for loop in Bash [video]
Tip: You can use { } instead of do/done to emulate C syntax.

Aside: [[ -v x ]] tests if the variable x is set. It should probably be [[ x -ne 0 ]] to match the example from the parent.

hddqsb··on Xonsh: I don't remember how to write a for loop in Bash [video]
> ...although I just tested it, and the increment and 0-padding only work in non-interactive mode ='(

Works for me...

    $ echo $0 # determine current shell (alternative: "ps -p $$")
    bash
    $ echo {01..100..20}
    001 021 041 061 081
But note that `{x..y}` has a major limitation: it doesn't support variables. Example:

    $ n=5
    $ echo {1..$n}
    {1..5}
    # intended output: 1 2 3 4 5
The `for ((...))` syntax mentioned by @zazaulola supports variables (it works in bash and zsh).

For completeness, here is a POSIX sh approach that doesn't require `seq` (in fact I notice this is what your "Uber Fancy" version uses internally):

    i=1
    while [ "$i" -le 5 ]; do
        echo "$i"
        i="$((i+1))"
    done
(but note that `continue` won't work properly).
hddqsb··on Paving the on-ramp to Java
The article briefly mentions a hypothetical static method "readln", which would help with reading input.
hddqsb··on Turns are better than radians
> But math never decreed that sine and cosine have to take radian arguments!

To quote a really important comment posted by Eduardo Vasquez on the article:

> [...] all those formulas of derivatives and primitives of trig functions in standard calculus books assume that arguments are expressed in radians. Say, the derivative of sin(x) w.r.t. x is cos(x) --- that is only true if x is in radians. Otherwise, you would get an extra factor, due to the chain rule. [...]

(There are a few comments here that point this out, but they are nested pretty deep so I thought it was worth repeating.)

hddqsb··on Algorithms and data structures implemented in many programming languages
Correct (actually a linked list of blocks[1]). It supports insertion and removal from both ends in O(1), which is very useful. However, it does not support insertion and removal from the middle (via an iterator) in O(1), so it's not a complete replacement for doubly-linked lists.

[1]: https://github.com/python/cpython/blob/a4ac14faa5c2be433738d...

hddqsb··on Unifying fold left and fold right in Prolog
The author is confused, their Prolog implementation is clearly a left fold!

I'm only half joking :)

To elaborate: The variable names "ACC" and "RES" suggest the predicate should be viewed as a left fold, because when the predicate is viewed as a right fold the meanings of the variables are flipped.

hddqsb··on Tell HN: Reddit mandatory login to see sensitive pages
EDIT: They appear to be A/B testing the email requirement, at least on reddit.com (https://news.ycombinator.com/item?id=32438251#32448986).
hddqsb··on Tell HN: Reddit mandatory login to see sensitive pages
They appear to be A/B testing the email requirement. I tried reddit.com on desktop again just now, and in some sessions it lets me skip the email but in others it says the email is required.
hddqsb··on Rob Pike's simple C regex matcher in Go
Yes. My proposed fix takes O(P * T) time and O(1) memory (but it only supports glob-style wildcards; it doesn't support repetition of specific characters / character classes / sub-patterns).
hddqsb··on Rob Pike's simple C regex matcher in Go
[Note: This comment tree is about the Redis code, which uses glob-style patterns rather than regex. So in this comment and in my grandparent comment, "*" means "any string" rather than "zero or more of the previous pattern".]

An NFA / DFA would be a perfectly valid way to implement an efficient matcher, but it's not the only way.

For example, memoization (https://news.ycombinator.com/item?id=32434412#32436442) can be easily shown to have time complexity O(P * T) where P = len(pattern) and T = len(text), which is the same as a basic NFA implementation.

In the case of the Redis code, my proposed fix requires much less code and memory than memoization or an NFA, and has time complexity O(P * T). It's slightly harder to prove this bound; here is a sketch of the proof:

Matching against a pattern fragment that does not contain "*" takes O(len(pattern_fragment)).

When a call to stringmatchlen() encounters a wildcard, it may recurse O(T) times (and then it returns). Each recursive call can be classified either "leaf" (if there is a match failure before the next wildcard) or "full" (if the match reaches the next wildcard).

The insight I described leads to at most one "full" call at each level of the recursion (i.e. for each wildcard in the pattern). So for a particular pattern fragment, the number of "leaf" calls that process it is O(T). Hence the overall time complexity is O(P * T).

hddqsb··on Tell HN: Reddit mandatory login to see sensitive pages
Agreed (in fact I referred to it as a dark pattern in another comment).

@grumple pointed out that throwaway accounts that can't be easily linked to the poster are essential to some reddit communities, which may play a part in why this feature is still available.

← PreviousPage 5 of 8Next →