-
Notifications
You must be signed in to change notification settings - Fork 13
Mixing of weak typing and duck-typing is still prevalent for certain operators #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I'd like to point out that there are two possible resolutions: the choice is on, one can go for either duck-typing or weak typing, the historically prevalent choice being weak typing so it's probably easier (and more "according to the plans") to resolve it in favor of eliminating overloads. In any case, if "generic arithmetic operators" that look like numeric operators but really can do whatever, are deemed useful, it's also a choice to eventually introduce Please keep in mind that the focus of this issue isn't to competitively compare the overload-driven and the coercion-driven approach, simply to resolve an inconsistency that has the worst of both worlds. Should such a discussion take place, I think it would be worth thinking about making the overload-driven approach the primary way but at the end of the day, many languages can even live without operator overloading. |
My initial reaction is to agree that we should consistently apply the rule "operators coerce their operands" and that By second reaction was to check Roast, and see that the current behavior is specced and was added in a commit by @TimToady in 2015. That makes me suspect that I'm missing some consideration that cuts against consistency here. I'm not positive what that consideration is, but I suspect it has something to do with laziness. I note that Imo, we should figure out what that justification is and either document it or, if it's not worth the complexity-cost of being inconsistent, remove the special behavior of ranges. |
This may sound arrogant but I think with almost 8 years in production, our judgement is almost guaranteed to be better, and it's much rather the exception that should justify itself than we should be trying to chase it down. If it's so useful, Either way, this is definitely a breaking change and a design decision to be made, that's why I opened an issue for it here. |
I think the point here is that What is countable are sequences. A good point is about missing |
If we characterize a |
I don't think this is related to the issue. It's not mandatory to have This whole mixing causes a kind of "diamond" problem. The biggest advantage of "weak typing" is that you can reason about the outcome of the operation regardless of the input types. Now, what would happen if you had a It's rather just an illustration of how fragile this system is that |
So far, what I see is a case where in many areas operators need to take special care of Then, again, what about This entire issue looks more like a technical issue about treating bugs/incomplete implementations when it comes down to |
afaict the current spec does this:
edit: this is wrong... [I was mixing Range and Sequence]
since Range endpoints are linear (ie Real):
the benefits are:
so I see no benefit in changing (although this feature may need to be learned) note[1] in general I think that the useful realm of powers for calculations with real physical measurements is related to the powers present in the laws of physics ... so J=σT⁴ is about the maximum power and (so 2,3,4,<1/2>,<1/3>,<1/4>, and -ve equivalents) |
I think this in itself shows why these overloads are a bad idea. My brain melted while I was trying to figure out, how you ended up with 40 when we took an interval that had a "length" of 9.5 twice. I suppose you added the minimum values and the maximum values respectively and maybe counted the integers inclusively? In any case, what actually happens is that the
There is nothing to propose here, I just sent the code that already does this. The result is 9, by the way. Again, I do think that 9.5 for both would make more mathematical sense, that being the size of the However, there is something that we both missed in this discourse:
What does it mean? Actually, having both operands as It's a bit hard when I don't see any reaction (apart from @codesections ) to the basic point: namely that it defeats the purpose of weak typing if you start adding ad-hoc overloads, making the code both harder to reason about and kind of unpredictably still fall back to the base behavior which is, as you can see with I think there would be several options for compromises (including generic arithmetic operators or introducing a method for Ranges) - I would even say mutually beneficial changes, even, as they wouldn't sacrifice anything you pointed out as valuable for you, they would merely be a breaking change. Or alternatively, I can add this to the "not gonna fix" issues, without ever understanding where I failed to illustrate the conceptual problem that actually goes against the basic design of the language. I don't have many other options. |
Now this demonstrates why is there such a big misunderstanding. 40 was a mistake of mine, but it was based one of my experiments with sequences where it did make it with 0.5 step. So, ignore the number. But the case is interesting from the point of view of different expectations. Mine is for the step to be 0.5 with 20 elements: 0.5, 1, 1.5, etc. Your is 9.5, and in a way it is interesting approach. But when it comes down to the fact that And all this not to mention one's expectations about how
How about complex numbers math? Which is also about tuples. ;)
And that's why you get the same for Here is my position in brief:
|
I tried to find some more context to the commit @codesections pointed out, which specced the current behavior: Raku/roast@139c10c. There was a corresponding commit to Rakudo just a minute before: rakudo/rakudo@59b5a4b Earlier that day there was a discussion how a Range could be shifted, starting here: http://irclogs.raku.org/perl6/2015-09-28.html#15:27.
There was another discussion after the commits: http://irclogs.raku.org/perl6/2015-09-28.html#21:48 Maybe also of interest could be this part of the old design docs: https://github.com/Raku/old-design-docs/blob/63e44c3635/S03-operators.pod?plain=1#L3632-L3640
[I'm aware, that I don't react directly to the primary point of this issue (mixing of weak typing and duck-typing). But @2colours pointed out that Range is one of the hybrids, that sticks out. So I thought that adding this context could be useful.] |
To expand a little on my earlier note, the current behaviour is good for things like this…
|
I wouldn't expect something that is called "a range" to be an
Yes problem here, because "a" really could not be coerced to a number, however
They are not just irrelevant to
That's quite sad when I'm giving multiple examples in every comment.
Interval arithmetics won't work with Edit: just two days ago, you said |
I wanted to mention in a separate comment that the current behavior, that is mostly like 2d vectors, not like intervals, also leads to absurdity if you multiply by a negative number: Perhaps if you want vector behavior, the actually clean solution would be to add a vector type. |
To address the question of should raku always do duck typing or always weak typing. I think that the ethos of raku is to be pragmatic. That is it is the antithesis to a formal language with tight rules and purity. Since no one would argue that Back to Range … well it seems to me that these are special in a parallel to the specialness of Date and Datetime. Range is used as a building block for array indexing and I can see that it would be handy for other natural objects such as part of a plot (x-axis, y-axis). So imho the current design is helpful as an easy way to factor and offset a Range. The fallback to regular Numeric coercion aka ‘+’ also makes sense (but you have to know that 0.5 .. 10 has 10 elements :-) ). Finally I can see that Range + Range or Range * Range quickly get into the realm of wtf. Fortunately raku lets you do your own overloads if your application rally needs something esoteric like this and I for one am glad that the language does not try to nail these down esp. with all the endpoint corner cases… |
Is the suggestion that there could be "generic arithmetic operators", the way there are generic comparison operators for example, unpragmatic? If there is a language suited for a modification like that, it's Raku. What would be the downside to keep custom behavior to such generic operators that don't coerce and don't fall back to a default? What am I missing? I'm getting the impression that "tight rules and purity" are demonized, like something that is definitely going to hurt. From what I can see, it's almost for free. I'm not sure if I have said it but the problems with these custom overloads on top of coercions start to amplify when you reach the "diamond problem", with Edit: before somebody says "but the user could still overload Edit2: sorry, somehow I didn't see my own comment, got confused, and basically summarized it again. Deleted now. |
Finally I had the chance to ingest the wikipedia link and also to understand the intention of Range to check if a (Real) number lies within a, err, Range. I felt inspired to write this post raku: Home on the Range Edit: ^^ apologies I have now fixed the link |
@librasteve the link is bad |
@librasteve Perl stands for "Practical Extraction and Reporting Language". Granted, "pragmatic" is much closer "practical" then "pure". For me
Neat, right? But that only works by happy accident. So yes, |
There is a false dichotomy here. If by "pure" we mean "not setting up traps for the users" then being "pure" is very much a pre-requisite of "pragmatic". Anyway, there is no apparent contradiction, and I'm not sure where this narrative shows up and why.
why is this a "save"? I would have suggested a name like
This seems unrelated; the question is the interface. Please, to any of you, the question remains: what is your problem with either a dedicated operation, or creating the appropriate generic, non-coercive operators - the problem that trumps the current unpredictability of these semi-overloadings? |
One of the few things I could agree to. Perhaps it would make more sense for typed ranges where, say, integer and string ones are iterable whereas rationals and floating point are not. But going this way may bring us to the complex areas of custom user types where we wouldn't have an easy way to guess iterability/countability of a type. Otherwise I consider the Wikipedia page one of the best answers to most of the questions here. The problem is not the overloaded operators doing something wrong; it is in too limited support for interval mathematics when it comes down to |
I stand corrected on this point and have edited my post accordingly.
well yes, but the documentation is clear that '~~' is the operator to check for inclusion |
It's not about the behavior, it's about the interface. The overloading itself is wrong. If Raku had used arithmetic operators the way Python does - no fallback, no coercions, only overloads - I wouldn't bring it up at all. I think there are contradictions between the proposals as well, like here with |
ProposalI put this strawman forward for comment... We split the use cases of today's Range type as follows:
This is a breaking change so would be done in (?) 6.f. Interval could be exiled to a module in the meantime. Backward compatibility would only break for Range.new (ie. $x .. $y where $x | $y !~~ Int|Str) which could warn with "class Range is deprecated for non Int|Str endpoints, class Interval will be substituted" (or maybe a full on error would be better?) Backward compatibility could be facilitated with a new role Rangy (maybe) that, when punned, provides the combination of behaviours that old Range supports. In that case class Range does Rangy and class Interval does Rangy |
It's a whole different topic from what I opened the issue for, moreover it doesn't even address it. I'm considering closing this issue now; if you want to proceed with the unrelated topics, please find a place for those discussions. |
This comment responds to your first two comments. My summary:
Conceptually, both duck typing and weak typing are about allowing an argument corresponding to a particular parameter of a particular function (or operator) signature to be something other than a particular type, provided the argument is acceptable for other reasons. I think duck typing in Raku is best understood as a function/operator using an argument corresponding to a parameter by assuming it will behave as needed by the function/operator, perhaps checking if first using Raku's Weak typing in Raku means an argument corresponding to some parameter supports a coercion into a target type. This will almost always be checked in the parameter declaration in the signature of the function/operator; the body only gets executed if the signature successfully binds all required parameters to arguments. I would guess there's relatively little need to combine them for any given single parameter of a single function/operator signature. That said, if they have been so combined for some parameter, and it introduces a nice DWIM, then I see no reason to stop that unless known corresponding WAT dogs bite, not merely yap. Likewise, if there are multiple routines (via inheritance or overload resolution) for any given function/operator, I don't currently see a reason to change overloads that introduce a nice DWIM (unless corresponding WAT dogs bite, not merely yap).
You can say so, but there needs to be adequate evidence for such a claim. (And to be clear, I mean a lot more than 1 example, though it seems 1 will have to do as our start.)
Having looked it up, I see a classy succinct DWIM. Thank you Larry. :)
I don't agree. It numerically adds 1 to each of the two numbers visible in the range gist, the
I disagree.
I think your use of words is misleading. Definitely for the general case of "intuition" and "suddenly". First, intuition is personal, knowledge based, and tends to be approximate, leading to mistakes, more of them, and bigger ones, when we know less, and then fewer and smaller ones as we know more. Second, results that don't match with intuition, especially if they are pretty dramatic and initially difficult to explain, are ideal initiators of learning and opportunities for effective teaching. A google for "role of surprise in learning" provides rich information from academic brain science papers and articles about this (perhaps surprising!) aspect of learning. Even good old Socrates and Aristotle are said to have said something to the effect that “surprise combined with astonishment is the beginning of knowledge”. As you'll find out if you read the relevant material, if one enjoys updating one's knowledge, and especially if the learning material is good, and double especially if the new thing learned feels good, then the outcome of the surprise will result in optimal learning, optimally priming later intuition. And even if it's not enjoyable, or the learning material is not clear, or the new thing learned doesn't seem useful, then assuming the update still happens, and is correct to the degree the learning material is, and still lodges in the brain (eg you're not super tired), then the outcome will still at least improve/correct future performance, improving later intuition.
Imo it's easy provided you base your reasoning on knowledge gleaned from sources such as good documentation, and remaining approximately aware of where you lack knowledge. (I think this is so especially if you are curious and enjoy learning; if you don't it's possible to react negatively to surprises, learn less, and then struggle.)
Too few surprises means poor learning efficiency. Too many also does. The sweet spot is in the middle. There are parts of Raku that imo have far too many surprises, but this
What would that operation mean? Did you research applying operations like these (shifting and scaling ranges)? If you did, did you draw any conclusions about scaling exponentially?
It doesn't have to be like this. But the reason is for it to be enjoyable and intuitive for those who know it and want it.
Neither If these overloads do make code harder to reason about -- which I'm not yet agreeing or disagreeing with -- it's nothing to do with weak typing or duck typing. I am also not convinced these overloads make code harder to reason about overall. Yes, one can't presume that ranges numerify to their length for all numeric operators. And generalizing to code using the plus/minus/multiply/divide ops with an untyped variable on one side and a numeric scalar on the other, you can't assume the result will be a number. But how often have you written code like that and actually wanted ranges to numerify to their length rather than that being an error? Thus far your narrative suggests to me that:
I'm confused by your mention of overloads. Overloads are orthogonal to duck typing which is orthogonal to weak typing. I guess you're thinking any particular overload does either coercion or duck typing? It would surprise me if many did both, but it would also surprise me if your recipe is a helpful way to process things. (That said, , surprise is the spice of life...)
Do you mean having alpabetic name aliases to the symbols? What's that got to do with this issue?
What happened to duck typing?
As noted it involves neither duck typing nor weak typing, so if it's the worst of any worlds, they're different worlds.
Overloads are everywhere in Raku. They're a great thing and they're here to stay, with operators being an exemplar of their use. So are coercions. And while duck typing has seen very little use, it's also a good thing that it's available, and here to stay, even if I can't think of any operator that uses it. But anyway, each of these things is orthogonal to the others. I think I've read some other comments in this thread but I need to stop for now. It sounds like this issue may be closed. If so, and someone (@2colours or anyone else) decides to open another issue, please do make sure it has taken what has been written here into account. (I haven't read all of it, but it would be really upsetting if a new issue simply ignored what was written in this one, even if it's a painful process to extract salient contributions before writing a new issue.) |
In the light of my strawman proposal (^^^), I did some research off the excellent wikipedia Interval Arithmetic page - this showed me that (i) interval arithmetic is a large and complex area and (ii) that there are standards and libraries that do this deeply and I assume correctly. I also found that the '..' Range literal "operator" is not overloadable easily within a module (at least until I get a real handle on raku AST) and this makes the premise of hooking in the Range constructor invalid. So, to absorb my Interval Arithmetic energy and to act as a proving ground for collaboration of how to extend raku in this area and the possible inclusion of this concept into the Physics::Error module (I am the author), I have written a new (rather noddy) Math::Interval module. Currently this does the Range op Range operations ( So given this and mindful of the priorities on the core team, I would say that this new module supercedes and embodies my strawman. Even long term, I cannot see a real Interval Arithmetic capability in raku core (although Julia has something like this), and would expect a deep implementation that uses standard libraries to live in a raku module. Meantime this is about playing with the APIs to the concpet in a shallow and hopefully convergent way. Please do feel free to make any contributions you have via a PR... it may take an hour or so to appear on raku.land. |
@librasteve I'd appreciate if you can translate this into a PR for |
@vrurg some questions:
i’m asking the latter because we may want to evolve to include more operations over time like log, exp, trig, powers, complex - some of which which can be quite quirky in Interval Arithmetic I'm probably more at ease with just incremental change in module land |
No.
It can be a problem-solving, explaining the concepts. It would then be closed with PR merge when all is settled down.
Just have a look into src/core.c – that's all you'd need for now. And it is not much different from developing a module. The only problem I see is that we're about to prohibit extension of older language version with new features. But this case I'd rather consider a bug fix. |
At the point you said this, you claimed DWIM a couple of times, which is the same thing in a different package. If you rely on subjective feels, you can't discredit an argument by addressing its subjectivity.
I think your use of words is misleading. Numeric addition means adding numbers. Nothing more, nothing less.
This is a harsh claim that would actually require justification.
This is both vague (what makes something sensible or DWIM?) and not a technical categorization. And yes, they do involve a half-heartedly applied principle of duck typing, it's just harder to say whether it's the
So you do notice that an argument has been made, even if you are reluctant to consider it relevant. I'm grateful for that anyway. Honestly, I think it's legitimate to even write code that assumes that adding anything ( Also, I think it's much easier to learn or promote the language if you can count on the same behavior - or principles, at the very least! - applying all the time, rather than requiring a handbook for any type you may encounter in a huge language. And yes, I see no comparable advantage of the current behavior. Let me ask: how often do you even want to offset a
Wait, wait. This is an unrelated topic. If the numeric coercion of Anyway, as we have come this far, one would ask at the very least: why do Lists add as their number of elements, really? Wouldn't concatenation be much more DWIM and useful overall? Ignoring the consequences, as we are kind of ignoring the consequences with the overloads on
This is where I think the deep lying social/community problems start. If this is what you think is appropriate for beginner doc material, rather than being an issue of teachability itself, I don't think we can have any meaningful conversation about this topic.
I'm not sure about your confusion. It was a very soft and "innocent" mention of overloads, namely that there should be no overloads. Anyway, I was thinking in coherent architectures which should be sound and complete. It's not that a single overload does this or that. Also, for the lack of better words, I specifically started the issue with explaining the two ways I can identify, and I have never come across a third way. (No, arbitrarily mixing these two is not a third way...) If you want the types of the operands to govern the behavior, you will achieve that by overloads as far as we are concerned, and you will refrain from coercions because coercions would suddenly mean you can't just look up the interface of your type. (Which, again, could work but worse than refraining from such thing.) If you want the chosen operator/operation to govern the behavior, you are free to allow coercions (as the behavior is still very straightforward and generic) but you won't do overloads because that would break the principle that you can just look up the operator/operation and reason about it. Both of these approaches can make sense and be useful but apparently Raku does neither. You need to look up the overloads on the operands (both/all of them, quite possibly), and if you reach a dead end, you have to consider where it may fall back and what coercions might be applicable. This is much more tedious with any given types (I presume this is why you had to propose that complicated chunk of "beginner documentation"), and unmaintainable if you have little presumptions about the data you need to perform operations on. In which case, ironically, your best bet is to write Pythonic code with explicit coercions, hoping to narrow down the candidates to something you are at least aware of.
That the current hybrid operator could be finally restored to really just "numeric operator", as the intention was, while the benefits of having behavior depending on types could be kept for something else, which could also be pure.
Nothing, it refers to the same phenomenon. (And here I thought it was a plus that I reiterated the same point using different words.)
As noted it involves both. |
@vrurg - yeah that looks doable - BUT we are going to have to draw the line somewhere I get the feeling that Larry wanted to show us where to plug in real Interval Arithmetic but then avoided adding a deep implementation a la Julia into core and placing it in the critical path and slowing things down (!) |
one of the raku superpowers is hypers >> << and operator prefixen, so in Python you may do:
in Raku, you can do
Thus it makes sense generally to have .Numeric (aka prefix:<+>) return .elems on Iterables as the simplest, widest common result, since there is a smorgasboard of variations for you to reach for if this is not what you want. This also meets the expectation of perl coders. This general rule is sometimes overridden eg. Date, Datetime, Range and Junction. These exceptions are documented and need to be learned. I think that Larry likened perl to English on several occasions, in that sometimes you have to burden the language with nuances, richness and special exceptions in order to get the richest user experience. (As opposed to eg. German) |
It still puzzles me if somebody wasn't content with just adding
I don't know if Perl coders have expectations other than being ready for a surprise in any moment, it's really Perl versus rest of the languages most of the time. Anyway, as things went down, there is no particular reason to make an appeal to Perl folks. They refused this language on several occasions, and there is less and less of them by the day to win over anyway.
The problem with these likenings is that they contain so little substance. What does "the richest experience" mean in English and what does it mean for Raku? In what exact quality are they alike? If I had to compare English to a programming language, I would think of languages like oldschool Javascript, C, or outright Go. I would have never enjoyed writing Raku code if it was like that. Uninspired and uninspiring, wordy, minimal syntax that doesn't really support abstractions, ambiguity and so on. Matter of fact, I would have never chosen to learn English if it didn't have insane payoff in real life but it's basically unavoidable. Again, kind of like the bad parts of C and Javascript added up... I was trying to illustrate that the expressiveness doesn't depend on these "clever hacks" a single bit. It makes sense that one would want to overload many operators every now and then and it equally makes sense that one wouldn't want to mix it with coercions to create code that is harder to reason about. In which case it's obvious (and not very difficult) to set up operators that don't coerce and are encouraged to be overloaded. The end result would be equally "rich", "expressive" or whatever. Actually, I'd bet my life if that was how it got added originally (and there clearly weren't many discussions about adding the Yet roughly half of all feedback sounded like some core value is being "under attack". As if just by staying consistent when it costs basically nothing one would give up on the terseness, rich syntax and "batteries included" approach, or anything one can at least argue for. I can't recall any feedback regarding any of the proposed alternatives, and the most approachable, relatable feedback on my concerns was basically like "it doesn't matter all that much in reality". Besides that, I can recall sentiments like "Larry must have had a good reason", "it does what I mean", "the behavior is useful", "weak typing doesn't have the benefits you assign to them anyway" (this one doubles down on the concerns about the design, lol) and the almost expected "the design is great, people just need more education on Raku". I didn't want to close the issue in a ragequit fashion but I haven't forgotten about it. Probably I will close it after a good night's sleep. |
I think @2colours's premise for this issue is very much on point. Perl did one thing better than all the other dynamically typed languages: It retained clarity of what an operation does by using different operators for different operations instead of relying on static typing to make it clear which operation was expected. In Perl, This great design feature however does not square well with object orientation (which came to Perl only later). In OO it's ultimately the object that decides on the operation. In jnthn's words, you need to speak the object's language. Raku being very much object oriented therefore ends up with an impermanence mismatch. Being multi-paradigm unfortunately makes this unavoidable. I think it's important for the discussion to acknowledge that this problem does indeed exist. It makes language design hard. As this lengthy thread shows, there are often no clear answers. In such a case it's helpful to have a visible list of commonly agreed upon goals or design principles that any solution must satisfy. Otherwise the discussion will run around in circles and it's personal preference against theoretical musings. There'd be no win in such a situation. A fact that's quite evident when reading this thread. So, do we agree that the problem exists and that the solution will have to be a compromise? Of yes, what are the must goals? |
I thought I would dig out a reference to Larry's linguistic take on perl (and subsequently, raku, I believe) The piece that I was trying to channel is under the heading "No Theoretical Axes to Grind" Since English is my mother tongue (sorry), I have a less than objective take. But I think that it is packed with special cases and exceptions. And yet when you are very good at it (I am thinking of Shakespeare, not me), I think that these corners and nuances help to improve the product that you can make using it. Anyway, that's my case why it's OK to be pragmatic (practical) and not force these useful (for some) exceptions into a set of principles. |
Hi all, I have opened a new problem solving Issue #392 to debate the merits of: |
Then I think it's really time to postpone this issue to the indefinite future; maybe if there is more of a consensus or momentum in considering it an issue in the first place. |
Duck-typing (a la Python) works by the premise that the user knows the type of the operands and no coercion happens. Weak typing (a la Perl and Raku) works by the premise that the user knows how the operation treats its operands and no overload happens. Mixing the two principles makes code fragile, yet still there are operators in the core (mainly the numeric operators) that mix the two approaches.
A question on IRC highlighted that an operation like
(0..10) + 1
can return1..11
. The problem with this is that it contradicts the concept of "numeric addition" - neither the way the operands are treated, nor the return value fits that picture. Actually, it's really easy to bust it:(0..10) + (1..1)
is suddenly12
, even though the intuition is pretty similar to(0..10) + 1
. It's hard to reason about this kind of code.Taking a look at the docs, there aren't excessively many (documented) "hybrids".
Range
has the 4 basic arithmetic operations (not**
though, interestingly...),Date
andDateTime
have+
and-
overloaded, and that's about it for the immediately apparent operations. I can't see a strong reason why it has to be like this, and it actually makes code harder to reason about in any nontrivial situation, because of the conflict of the two typing paradigms.The text was updated successfully, but these errors were encountered: