the intent of the code is to get the previous month RELATIVE to the current date.
But that isn't what it does. From the original post:
function getMonthName(monthNumber) {
const date = new Date();
date.setMonth(monthNumber - 1);
return date.toLocaleString([], { month: 'long' });
}
That is a function which is meant to take a number (presumably 1 to 12) and return a localized name for it. This is essentially an array lookup and should return the same output for a given input (and locale) every time it is called. If the intent is to return a value relative to the current date, it is even more wrong, since it should gather the month from the current date, not the function paramenter. This claim of intent, not present in the original post, is an example of you changing your story over time.
Yes, it would help find the problem faster because the first time invalid date is passed in the program will crash.
No, it wouldn't. As I have said before, testing for unexpected return values is just as effective as testing for errors, that is, not very with the function originally presented under sensible assumptions. If the function actually did look like the intent you claim, the tests would be different, necessarily replacing Date
for consistent runs, but would be equally likely to catch the problem whether failing on value or error. And if you are eschewing testing and relying on runtime crashes, you have bigger problems.
Given that I have agreed and commiserated, and neither of us can change JavaScript, there is nothing to be gained from pursuing this complaint. In contrast, what I have tried to say, if followed, would give you an approach that leads to more reliable code, even in the face of undesirable APIs.
I had thought that worth pursuing, and had thought you worth investing my considerable time. Alas, I can only lead you to the water...
I'm not missing your points, even as you change them. I've agreed that JS sucks. I've agreed that errors can be more helpful. I'm not trying to argue with you about that. What I have said, from the beginning, is that in the code you originally presented a behavioural change for setMonth
will not help you find the problem any faster. Test failures for the wrong output occur just as often as test failures for errors, on exactly the same few days each year. The API change gives no advantage for the specific function this discussion started with in this regard. However, an approach that avoids inconsistency will, because in this particular instance, that is the real source of the problem. That is all.
In that context—the one you started with—it does not matter that there is often good reason to call Date()
without arguments. The getMonthName
function presented, effectively an array lookup, should produce the same output for any given input every time. It has no reason to engage in any behaviour that varies from day to day.
There is absolutely nothing wrong with getting the current date.
Bluntly, the code you presented fails precisely because it gets the current date where it should create a more specific one, and then fails to deal with that variation appropriately. You can keep distracting yourself with language design decisions, but that won't help you avoid this particular type of problem in the future because that's not where it is.
Getting the current date is often fine. In this specific instance, it is not. That is why the function doesn't work. If you are missing that point, as much as I appreciate your enthusiasm in continuing the conversation, I will take the L (and the code that actually works) and move on.
Yes, and I've said that I agree with that in general. I know that this isn't hypothetical; that's exactly why I keep saying that throwing an error doesn't help you find this bug early at all.
Even the silent weirdness can be caught by the most basic of tests checking output against input, but only if your function works the same way on every invocation.
Whether making a giant fuss (as you'd prefer) or making the best of it (as it actually does), the setMonth
method always works the same way. My code always works the same way. The setDate
suggestion makes the code always work the same way.
Code that always works the same way is easy to test.
If the day of the month is constant and incompatible with setMonth
, whether there's a thrown error or just an unwanted return value, a simple test will reveal that on every test run. If the day of the month is constant and always compatible with setMonth
, the test will pass appropriately on every test run.
The bug in the code you originally presented comes from working differently over time. That's why, most days, tests won't identify the problem, even with a fussy, noisy API. Most testing days, the date will just happen to be compatible, and even the fussiest, noisiest API will carry on without any mention of the problem.
The reason the original code works differently over time has nothing to do with the silent, unexpected behaviour of setMonth
. It's entirely down to calling Date()
without arguments, the entire point of which is to give different values over time. That call effectively introduces state that is not controlled by the function. And not bringing it under control is the real source of the bug.
Yes, absolutely, JavaScript sucks. Make F# the only supported web scripting language! But JavaScript's suckiness is not the cause of this particular bug. JavaScript's suckiness is not the reason this bug is hard to catch. The real problem lies in code that functions differently over time when it should (and could easily) be consistent. That's what actually makes it hard to test.
Plenty of other languages and API design choices still allow code that functions that work differently over time, which is why, as justifiable as the complaints are in general, those factors are irrelevant for this particular bug. Write code that always works the same way and the problem goes away. That's the real core of the issue.
Obviously, that's easier said than done, and it's irritating that neither loud errors nor most testing will help you in this regard, but that's the way it is.
I agree with you that errors are useful feedback for coders who don't know the ins and outs of an API. And every programmer is in that group at some point. But the difficulty in identifying this particular bug doesn't stem from the API decisions.
Whether Date
s throw an error, or work with what they're given, has no bearing on the subtlety of this bug. Either way, tests that don't replace Date
will fail to identify it most of the time, and tests that do, based on its use within the function, would be called wrong-headed by many.
Either way, the bug only shows up at the end of months longer than the target month, and that infrequency has nothing to do with the peculiar design choices of the Date
API. It stems exclusively from the evaluation of Date()
called with no arguments returning different values at different times—behaviour you have not objected to, and which I'd expect to be considered entirely appropriate, in fact its very point—combined with an attempt to use that value, whatever it may be, without due consideration.
Since the month is the only part of interest, there's no reason to allow the other parts to vary at all. Fixing them, as I suggested at the beginning of all this, is the simplest approach, but setting them first, as has also been suggested, would work too.
You can once again complain about JS design decisions and I'll agree about many of them, but, as much as you might like it to be, and as annoying as so many of us think they often are, here it is beside the point. The perniciousness of this particular bug stems from unnecessarily calling a function with inconsistent output and then improperly processing that, instead of using a function call with always-predictable output.
I've tried to point that out in all the ways I can think of, so if it's still not getting through, I give up. And if your acknowledgement was too subtle for my sleepy brain, and I've ended up overexplaining, then I'm sorry.
I was taught that side effects are not so one-sided, and that changing output in response to outside state (such as the date) is also a side effect, a side effect on the function, rather than a side effect of the function, but I'm happy to use other definitions so long as they're commonly understood.
As I said before, though, even if JavaScript did throw an error as you'd prefer, it would still allow your function to have date-based problems. They'd be a bit noisier perhaps but no less present, and just as "well it's worked fine so far". And that's because, as I keep saying, the real problem here is using a function with inconsistent output and not thoroughly dealing with the possibilities. An API change wouldn't alter that. Most of the time it would still let you write bad code.
I also probably agree with you that errors are generally better than silence in response to bad input but, as someone else has said (more or less) it's not always unreasonable to consider "31st [Month]" as 31 days after the end of [Previous Month]. Without throwing errors, this flexibility is possible. Perhaps the creators believed having to mutate the day-of-month first was an acceptable trade-off for that.
You've replied while I was editing, so see that regarding what I mean by side effects.
As far as throwing an error when you try to create "31st February", this wouldn't actually help much, since the error would still only occur on some days of the year, because your original code doesn't account for the range of outputs from Date()
when called without arguments.
To perform correctly, your code needs to normalise the day of the month, or just create the date more explicitly to begin with, but this is a calendrical issue, not a JavaScript one.
The rake has nothing to do with JS (which I agree is cursed, but for its own reasons, not this).
You have called a function in a way that does not give a consistent value (Date()
). Such functions are hardly the preserve of JavaScript. You've failed to adequately deal with the range of values produced, with code that tries to insist that the "31st February" can be a meaningful date in February. You should accept that this is your mistake and learn to (better) avoid side effects where possible.
Also, the function isn't side effecty since it doesn't make implicit references outside its scope.
Edit responding to your edit:
Also, the function isn't side effecty since it doesn't make implicit references outside its scope.
The Date()
function's output varies according to something other than its input (and even the rest of your program). Using its output without accounting for that variation means that your function, as originally written, also gives inconsistent return values, varying according to something other than its input, because it does, in fact, reference something outside the function. If it did not, the results would only depend on the monthNumber
argument, and would always be consistent. I don't know what you call that, but I view it as a side effect.
As you have said, the rake is that months have different lengths, and you need to account for that. But that's not one of JavaScript's many issues.
You want to create the date "31st February", but it's JavaScript that's cursed?
Write a less side-effecty function.
function getMonthName(monthNumber) {
const date = new Date(2023, monthNumber - 1, 1);
return date.toLocaleString([], { month: 'long' });
}
It's an event that tells an oppressed group to know their place, and reminds everyone that any attempt to turn the tables on the ruling class will result in a showily horrific punishment. What could possibly be more British?
Their physiology is barely diverged so their intellects are likely to remain similar. Espionage is frequently the theme of Romulan encounters, which would help keep them up to date. And if they procreate more frequently than every seven years, they might have a much larger population even with greater murderousness, with more people being advantageous for tech development.
Here's how you can do it while only ever dividing or multiplying by two.
Decimal to quaternary
This is a cycle of looking at remainders from dividing by two, with the first one an odd-even determinant, and the second a big-little determinant for each quaternary digit. You make numbers even before dividing by two, so there are never fractions to consider.
- Is the decimal number even? If yes, remember that you'll have an even quaternary digit (0 or 2). If the decimal number is odd, subtract one from the decimal number, and remember that you'll have an odd quaternary digit (1 or 3).
- Divide the decimal number (having subtracted 1 if odd) by two. This gives you an intermediate number.
- Is the intermediate number even? If yes, your quaternary digit is the lesser of the possibilities (0 or 1). If the intermediate number is odd, subtract one from the intermediate, and your quaternary digit is the greater of the possibilities (2 or 3). Write the quaternary digit down.
- Divide the intermediate number (having subtracted 1 if odd) by two. This gives you a new decimal number for the next round.
- Repeat from step 1 unless the new decimal number is less than 4, at which point it becomes the final (left-most) quaternary digit. New quaternary digits go the left of previous ones.
Example
Decimal number is 5710.
- 57 is odd so the quaternary digit will be odd (1 or 3). Subtracting 1 gives 56.
- 56 divided by 2 is 28 for the intermediate number.
- 28 is even so the quaternary digit is the lesser possibility for an odd digit, i.e., 1. Write down 1.
- 28 divided by 2 is 14 for the new decimal number.
Next round:—
- 14 is even, so the quaternary digit will be even (0 or 2).
- 14 divided by 2 is 7 for the intermediate number.
- 7 is odd, so the quaternary digit is the greater possibility for an even digit, i.e., 2. Write down 2 to the left of the previous quaternary digit. Subtract 1 from the odd intermediate number (7 - 1 = 6).
- 6 divided by 2 is 3 for the new decimal number.
Final digit:—
- 3 is less than 4, so write it down as the last quaternary digit, to the left of the previous one.
That process gives 5710 = 3214; that is, 3 sixteens, 2 fours and 1.
Quaternary to decimal
Here you only need to add a small number and then double twice with each digit.
- Start with 0 as your running total.
- Add the left-most quaternary digit, then ignore that digit for subsequent rounds.
- Multiply the new total by 4. You can multiply by 2 twice if you prefer.
- Repeat from step 2 using the next quaternary digit unless it is the last (right-most) digit.
- Add the final quaternary digit to the running total. This is your decimal number.
Example
Quaternary number is 3214.
- Running total starts at 0.
- Adding 3 makes 3.
- 3 times 4 is 12.
Next round:—
- Adding 2 to 12 makes 14.
- 14 times 2 twice is 28, then 56.
Final digit:—
- Adding the final digit (1) to the running total (56) gives 57 as the decimal number.
So 3214 = 5710.
If it ain't broke…
You have regular backups already. Unless you want to add fault tolerance with something like RAID, I don't see why you need to buy anything right now.
But those kinds of initialisations belong in .profile
(or, if you're using a weird desktop environment, its own configuration file), particularly if you want .desktop
files to work. (In .bashrc
, PATH
will grow longer in each subshell, which shouldn't cause problems but is wasteful.)
So, what desktop environment (GNOME, KDE, Cinnamon, etc.) are you using?
.profile
is executed by login shells for the benefit of it and its subshells, and by DEs like Cinnamon for the benefit of .desktop
launchers at login.
So, have you logged out and back in again since adding these lines to .profile
?
And of course, the .profile
has to be executed properly for its configuration to take effect, so it`s useful to know if the problem is with those specific lines, or the file as a whole.
Add:—
date >> ~/profile-execution-log-top.txt
echo $PATH >> ~/profile-execution-log-top.txt
to the top of .profile
, and:—
date >> ~/profile-execution-log-end.txt
echo $PATH >> ~/profile-execution-log-end.txt
to the bottom of .profile
(use alternative paths as you see fit) to monitor that activity. You can test this by sourcing .profile
but the real test is logging out and in again. Look at the time when you do this so you can correlate each action with each timestamp in the log files. If .profile
is executed to completion, you should have two files with matching timestamps but different PATH
s. If you don't have a matching timestamp in the "end" log file, there's a problem mid-execution. If neither file is being updated, .profile
isn't being executed at all.
Since .bashrc
is executed for all non-login shells, it shouldn't really source .profile
, which is only meant for login shells, and might trigger expensive activity. (.profile
might source .bashrc
, but that`s fine.)
Are other lines in .profile
being executed, or is the whole file ignored? Have you logged out and in again since adding these lines?
Having thought about this some more with practicality and clarity thrown out the window in favour of abstractions, how about this?
A game is a sequence of moves. Past moves are immutable, future moves unknowable; a singly linked list fits the bill here.
Each move consists of a player token and a position. The position might ordinarily be thought of as a grid index but, as you point out, it could just as well be membership in one of the potentially winning lines. Either a move is part of one of these lines or it isn't. This makes the position equivalent to a bit field. If each potential win line is distinct, they could indeed be held sparsely in a set.
Checking for a win then consists of counting player tokens for each potential win line and checking for crossing the necessary threshold. Filter, sum, max, greater than?, any?
I think this scheme would be applicable to arbitrary game boards, with none of it requiring mutation. Is that the kind of thing you were after? The reflection/rotation equivalence isn't present here, but I still think that's more an artefact of analysis, rather than a property of gameplay.
- The ordering of each row should not matter.
This is true for your abstracted rows, but is maintaining eight sets of three square states, two or three of which must be updated with every move, really a better model than a single sequence of nine, where only one needs to change at a time? It's more complex to think about, and is less efficient to update. When you throw in steps to canonicalize the rotation and reflection, which may produce different transformations from the input/output grid on the first three moves, you may need to change even more set items with each move.
It's true that, mathematically, the mapping from grid to sequence is arbitrary and the only thing that matters is consistency, but if you view programming languages as a way to communicate with humans, then clarity of purpose, rather than mathematical idealism, should be your goal. Use a nine-item array or a three-by-three nested array for the underlying storage and treat your eight win-checking sets as views into that more ordered structure. These views could well be functions that return a set and are themselves held in a set to be applied in any order. Similarly, treat canonicalization as another way to view the underlying board.
You could sidestep the mutable borrowing by not mutating individual squares. Take a leaf from the functional-programming books and use a function that takes a board and a move and returns an entirely new board. Or takes a board and returns one of the abstracted row sets. There are only nine squares and nine moves. The win-checking views aren't needed before move six. A bit of copying isn't going be a problem.
Yet the "it's the economy, stupid" talking point still won't die.
In order to keep the campaign on message, Carville hung a sign in Bill Clinton's Little Rock campaign headquarters that read:
- Change vs. more of the same.
- The economy, stupid
- Don't forget health care.
Although the sign was intended for an internal audience of campaign workers, the second phrase became a de facto slogan for the Clinton election campaign.
— https://en.wikipedia.org/wiki/It's_the_economy,_stupid#History
And, of course, they forgot point 3.
Since the XSS incident a couple of weeks back, I hadn't been able to log in, or even sign up for a new account. All attempts at either ended with the spinning bear. Now, presumably because of the upgrade, I have been able to create a second account to post this, but I can't log in to my original account, AppelTrad, because it prompts for 2-factor authentication.
This is (partially) my own fault, I suppose, for clicking the checkbox and not mentioning that it didn't actually give me any of the promised results, while I was still logged in; since I was also able to untick the box without being prompted for anything, I just assumed it was a bit of not-yet-implemented UI and that I had reset the option for if it ever became effective, and carried on without any problems until the forced logout.
Since "2FA being broken is a known issue", I'm wondering: is it possible for an admin to reset that field in my database record (or whatever needs to be done to cancel 2FA) without any of the security shenanigans that should accompany working two-factor authentication, so I can successfully log in again? (I have my passwords saved, so it's not just a mistyped password issue.)