4 ms·
> dynamically typed so[...] People accustomed to strongly typed languages and thinking mostly of monolithic apps always pin things on dynamic typing even where
by cxr 3y ago
> dynamically typed so[...]
People accustomed to strongly typed languages and thinking mostly of monolithic apps always pin things on dynamic typing even where it's not the cause. It's not about dynamic typing in this case.
What's going on here is partially because of separate compilation and partially because of mobile code (as Lars Bak explains[1]). Even if developing in a strongly typed language, the machinery would still have to take care to deal with the same thing. In fact, this isn't even JS; this file is TypeScript—and this expression being strongly typed is almost certainly exactly why this code was written this way: because `useControlledState<number>` demands it. If `useControlledState` were just vanilla JS and no one were doing typechecking during development and build, there would have been no problem with passing something else as an argument, because it will of course evaluate to NaN anyway when undergoing the number treatment.
(This is kind of subtle, but it's not that subtle. Too many people make this mistake, and there's probably something that needs to be done about that; "because dynamic languages" has become something of a thought-terminating cliché that leads people to the wrong conclusions—as demonstrated even in the interview, at the point where Bak felt prompted to point this out.)
1. Inside V8 — A Javascript Virtual Machine. Going Deep: Expert to Expert. Accessed July 3, 2020. <https://channel9.msdn.com/Shows/Going+Deep/Expert-to-Expert-Erik-Meijer-and-Lars-Bak-Inside-V8-A-Javascript-Virtual-Machine https://channel9.msdn.com/Shows/Going+Deep/Expert-to-Expert-...>.
- stinos 3y agoFunny. Seems I fell into the same trap I just sketched, making wrong assumptions because of not looking around in the code. But to be honest from your explanation I still cannot tell whether I merely used incorrect terminology wrt dynamic typing (but the general principle still holding) or whether this code guarantees that defaultValue is in fact always a numeric type?
- cxr 3y agoThis code doesn't. It guarantees that within `useControlledState`[1], `defaultValue` is either NaN, an actual number, a number-like string, or something else that can be coerced into one[2]. If `defaultValue` is, for example, `true` or even an empty array, then the result of the expression `isNaN(defaultValue) ? NaN : defaultValue` will pass through `true` or the empty array. 1. <https://github.com/adobe/react-spectrum/blob/main/packages/%40react-stately/utils/src/useControlledState.ts https://github.com/adobe/react-spectrum/blob/main/packages/%...> 2. whether one of those non-numbers is prevented from actually making an appearance at this point for other _reasons_ is a different question, but the fact remains that one making its way through is not something that would be stopped from proceeding further by the isNaN check here (Addendum to, I hope, clarify: I'll reiterate that it's the type annotations on the `useControlledState` call site parameterized as `useControlledState<number>` (and the TypeScript team's decision to type `isNaN` to take only parameters with a number type) that "guarantees" `defaultValue` is a number. But that guarantee is a soft one, i.e. not a guarantee at all, precisely because of mobile code and separate compilation; I can contrive a project right now that uses the Adobe Spectrum library and pass whatever I want to it—it's not like the Adobe devs' compilers are going to be able to stop me.)
- deleted 3y ago[deleted]
- cxr 3y agoPS: Gilad Bracha makes a similar point in this interview about pluggable types: Newspeak and Pluggable Types. Software Engineering Radio. IEEE Computer Society. <https://www.se-radio.net/2009/07/episode-140-newspeak-and-pluggable-types-with-gilad-bracha/ https://www.se-radio.net/2009/07/episode-140-newspeak-and-pl...>.
- mrkeen 3y ago> because it will of course evaluate to NaN anyway when undergoing the number treatment. The point of typing is to make sure this situation doesn't happen. I write a method to accept Number inputs. I don't want to do the work (whether it's thinking or writing tests) to make it a pleasant experience for someone to pass non-Number inputs to my function (intentionally by mistake).
- cxr 3y agoDid you take note of the context in which this conversation is taking place? Because it doesn't make sense as a response within that context.
- mrkeen 3y ago(My top-level reaction to TFA, for background. I left it out because no-one asked.) > Writing code for both computers and humans Show me what you got. > isNaN(defaultValue) ? NaN : defaultValue Yuck. > This made me pause for a moment. Of course it did. I still pause every time I re-read it! > This ternary is much better. This is expert-level programming. This is how to write code with empathy for other programmers. No, it makes me pause every time I read it, and I cannot follow the path from "made me pause" to "expert-level programming". Back to your comment: >> dynamically typed so[...] > It's not about dynamic typing in this case. It is about dynamic typing. isNaN should not exist (except for specific cases such as machine floats where NaN means something concrete). > If you saw that, your spidey-sense might start tingling, “This is a number—did they consider the NaN case?” I want tingle-free programming. Let the compiler do the tingling for me. A Number-which-may-or-may-not-be-a-Number is not statically typed. I don't care if it's in a .ts file. There are 15 more references to NaN in that module and they do not make for easy reading: // Clamp to min and max, round to the nearest step, and round to specified number of digits let clampedValue: number; if (isNaN(step)) { clampedValue = clamp(parsed.current, minValue, maxValue); } else { clampedValue = snapValueToStep(parsed.current, minValue, maxValue, step); } Why doesn't the above code match its comment? Why doesn't it look something like: // Clamp to min and max, round to the nearest step, and round to specified number of digits let output = input |> roundNearest(step) |> clamp(min, max) |> roundToDigits(numDigits) Their code either calls into clamp() or snapValueToStep() based on whether 'step' is a number. Does that imply 'step' needs to be a number for the snapValueToStep case? If so, 'step' will need to be checked again inside that method. Why didn't they accept a typed number as input? Or - if they were forced to accept a string - check it at the top of the method? Oh wait - they did! Twice in two lines! let clampStep = !isNaN(step) ? step : 1; if (intlOptions.style === 'percent' && isNaN(step)) { clampStep = 0.01; } So now 'step' and 'clampStep' are both in scope. They may or may not be numbers (insofar as the coder is willing to keep scrolling back up and re-reading code that at first "makes them pause", but then later makes them feel like it's "expert code".) Why not: const clampStep = case parseNum strStep of Just step -> step Nothing | intlOptions.style == Percent -> 0.01 | otherwise -> 1 * One definition site, so you can read all possible values from the leaves {step, 0.01, 1} * The possibly-a-String step has been demoted to 'strStep :: String', so that 300 lines later, the coder cannot not accidentally pass 'step' to another function (and if he did, the compiler would stop it immediately). * clampStep is actually a number, so no more NaN checks. But hey, that's all par for the course: Java programmers don't think they hit NPEs, C++ programmers don't think they have ("modern") memory issues, and dynamic programmers don't think they have type problems. What really prompted me to comment was your suggestion that it would have been better to skip the confusing checks, keep passing through an untyped object, and let some downstream expression blow up.