Skip to content

Instantly share code, notes, and snippets.

@2colours
Last active September 26, 2023 11:50
Show Gist options
  • Save 2colours/90e3b2e87535e9792e5780b2ab63e0b6 to your computer and use it in GitHub Desktop.
Save 2colours/90e3b2e87535e9792e5780b2ab63e0b6 to your computer and use it in GitHub Desktop.
Responding to for-otherwise and the Itch.scratch article

This was originally meant to be posted at rakudo/rakudo#5390. Since I have put effort into it and I do think there is thought in it, I decided to preserve it despite the blockade thrown into the way.

Anyways. There's the blog post and the discussion. The fact that Slang::Otherwise has 5 stars on Github is some indication that the repo is appreciated by some people at least.

I'm not arguing against the existence of a slang. I'm arguing against throwing it into the core language, let alone by a mere PR. I don't think the discussion even looks so clear and one-sided from a neutral point of view, and I think I have seen this article before.

I would like to respond to the article itself in this comment... that will be enough for now I think.

First example

I needed to build an object for every value in the @values array, or a single special object if @values was empty:

I think this is basically where we parted. Why do you want to make zero values have an interface like it was one value? Well, let's see.

I checked the referenced article and I think it's a great illustration of how memes of the Dawkinsian sense work. To quote the problem as the author phrased it:

The only thing left to do is to cover the edge-case where the user provides no test values at all (i.e. when there are no @values to iterate through). That can happen either when an explicit empty list of default values is passed to assert, or when the block passed in doesn’t explicitly declare a parameter at all (and therefore defaults to the implicit topic parameter: $_). In either case, we need to call the block exactly once, without any argument.

This is an interface question. If your interface is "here, give me a couple of values to perform tests on", it makes perfect sense to do nothing when you receive no values. The author didn't like this interface and instead of adding a special case for no values at the interface level ("if the user provided no values, it's considered to be this one special value"), they propagated the "raw" interface to the implementation. The funny thing is, if you assign something like Nil or Any to a @variable in Raku, you would actually get behavior that is fitting for this interface...

So this is the reason I don't see code like this as much as Perl folks do. We have completely different first thoughts when we come across a problem, and probably partially because of how we learned the language or programming in general. If you want to treat no data as one unit of data, do just that - this seems to be the much more common approach in other languages. Also, that way, you can decide to carry the one unit you decided on as much as you want, as meaningful data to the backend. This actually seems like better layering: you translated the interface at a reasonably high abstraction level and the low levels don't have to do any business logic anymore.

Second example

At almost the same time, in other (non-blog) code I was writing, I needed exactly the same construction...to do something with every element of an array, or something different if the array had no elements:

    for @errors -> $error {
        note $error if DEBUG;
        LAST die X::CompilationFailed.new( :@errors );
    }
    if !@errors {
        note 'Compilation complete' if DEBUG;
        return $compilation;
    }

I think here it really makes a difference that it's not any array we are dealing with but an array of errors, and "there have been some errors" and "there have been no errors" are really two fundamentally different cases, regardless if you retrieve this information as "is this array empty" or by checking a status code, having a boolean flag, etc. Here, the if-check actually has a meaning - frankly, in this case, it doesn't even seem nice to run a loop through the errors and rely on the emptiness of them. In a situation like this, the whole control flow of "there are errors" and "there are no errors" can be expected to be different and it seems accidental that the author wanted to do nothing besides the loop for the error case. (Actually, the error did already do something besides the loop, using the phaser.)

So in general, this code would really look like:

if @errors { # or whatever check for an error
# do some stuff maybe
for @errors -> $error {
# do something for each individual $error
}
# do something afterwards, maybe halt the program, dump a file, etc.
} 

# maybe an else clause if the error case isn't escalated here

In this structure, it's visible that the "happy path" doesn't belong to the loop itself but the whole error handling, so I wouldn't say this use of for-otherwise would be common or even idiomatic.

but that just underscores the absurdity of needing to test the state of the iterated array twice within the first two lines.

I don't know if it was ever absurd (talking about strong judgemental language) but it's surely not absurd in my snippet where going through the actual errors is just one tiny part of the error handling and nobody - okay, not most people - would get the idea that the for is there as an if there-was-an-error check.

So, from what I see, these were the two examples provided, all I can respond to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment