-
Notifications
You must be signed in to change notification settings - Fork 70
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
a = b.mul(c) alters b #4
Comments
Yes, this behavior is intentional and absolutely desirable, because of method chaining: Does that harm any of your design decisions? I embed fraction.js quite often (as you can see in the samples folder) and see it as a assembly-like way of handling rational numbers. So the object represents a register if you want, which gets modified by the operations. In your example c and a reference the same object. |
This is not needed for method chaining (in case of It could be a design choice though, and depends on the audience you have in mind. Most libraries I've come across (like for complex numbers, bignumbers, etc) keep instances immutable. This makes it very easy to reason about your code. The only counter example I've seen is moment.js, which mutates the dates themselves when operating on them. Now that I know it I can reckon with it. But it gives tricky side effects if you're not aware of them! It means that things go wrong by default, unless you clone the right variables at the right place. I came across it when I implemented a method function cube(x) {
return x.mul(x).mul(x); // ooops: that gives wrong results as x is mutated...
} What I do now is put a function cube(x) {
var tmp = x.clone();
tmp.mul(x);
tmp.mul(x);
return tmp;
} |
You're right. From that perspective and with your cube example, my design decision isn't that good. I'll think about that for 2.0.0 and hope that doesn't make things too complicated for you. For now you can come up with something shorter like: function cube(x) {
return x.clone().mul(x).mul(x);
} If you have performance in mind, then something like this would speed things up: function cube(x) {
// Works as (n/d)^3 = n^3/d^3 and as gcd(n, d)=1 => gcd(n^3, d^3)=1 (no canceling is needed)
x.n = Math.pow(x.n, 3);
x.d = Math.pow(x.d, 3);
// x.s isn't changed with odd exponent
return x;
} I wanted to implement a general pow() method this way, but numbers break out of the rational scope if exponents become rational as well: (2/1)^(1/2) for example. Robert |
You are right I can simplify the Thanks for wanting to reconsider the default behavior of fraction.js. It's no showstopper at all, I just have to consistently use Built in support for |
Leaving just the clone() method in the code instead of a lot of tmp stuff makes it also easier to remove the overhead, when the new fraction.js behaves differently. Built in support for pow() could be done quite easy when exponents are integers. I can add this function if you want. As I said, rational exponents make things ugly and was the only reason I haven't done it so far. I benchmarked a lot of such tiny things when I improved the easing functions of tween.js. For ^3 it doesn't make a big difference if you use fraction.js already is quite fast. There is a jsperf ( http://jsperf.com/convert-a-rational-number-to-a-babylonian-fractions/26 ), competing against ratio.js, rational.js and some other implementations. As far as I can say, the But improvements and especially performance improvements are always welcome! So let's make it even better :) |
Cool to see fraction.js outperforms the alternatives, congrats :). The thing I saw in your code multiple times is calling |
Yep, that's what I just meant, that the optimizer isn't used, when I mentioned " mul: function(a, b) { However, this needs some extra code in |
Good idea, as you have limited arguments you could easily pass them one by one. It's indeed no critical thing, but it will be fun, you can really get serious performance jumps :). |
Grats, you did it! I just removed the 8,9% Improvement based on 5M iteration is not that bad :) |
I know, I really like the way math.js interacts with fraction.js! :) Just to let you know: I did some additional work on performance tweaks yesterday. For every method call, there were two gcd() invocations. The other thing is that a pow function just arrived for integer exponents. It's the same implementation as we had earlier in this thread. What I just saw: The valueOf() function shadows the toString() function when I cast the Fraction object to a string. For now on, it's necessary to explicitly call .toString(). Do you know a way to circumvent this behavior? |
Yes, same here :) Fraction.js offers exactly the API I need, without extra stuff, the code is very compact! I'm not sure what problem you mean when
|
Yep, in this case it's trivially the same. But |
argh, looks like another of these odd inconsistencies in JavaScript! Would this be a reason for you not to implement |
I looked up that one. JavaScript behaves the way we thought, but the algorithm is a little different when It doesn't mean that I would get rid of |
Okay 1.9.0 just arrived npm. If you change the dependency to 1.9 as well, you can use |
Thanks Robert! |
I was thinking quite long now about immutable Fraction objects. Intuitively it might make much more sense to return a new object on every operation invocation. From a performance point of view I like it much more to work on the same attributes and see the object as the algebraic container, all operations are getting applied to. I then was thinking that a parameter could control the behavior of immutability. I could abuse the cancel() function for that, so that a new object is returned whenever "this" is returned atm. However, I more and more get distanced from the idea. The lib is really simple and introducing option parameters would be an overshoot. So either changing the API or enhancing the documentation. I prefer the latter and leave it to the user when a new object is needed, especially because in one other project which will be published soon, I make use of the mutable behavior. What do you think? The change would't be noteworthy, it's really just a design decision. But calling |
I totally agree with you that you shouldn't support both behaviors. If you leave the behavior as it is (Fractions are mutated on operations) extra documentation will help a lot. If you want to make a more well-found decision here, you could do (a) benchmarks to see how much you really gain from this, and (b) collect a set of typical use-cases of Fraction.js, and see how much it helps or hurts to go for one or the other solution. Btw what is the reason that you don't use a regular JavaScript prototype but for every operation copy variables the input variables to a global object (via |
The design of the lib has two reasons: I have two pre-allocated objects in the global scope for performance reasons: One where all the parsed data goes to and one which holds the actual fraction. Not using prototypes has the reason to have "truly private methods" within the object, which are not visible to the outside world. Yep, I'll do some more experiments on how the current mutable implementation outperforms one which always works on fresh objects. I think I'll wait until I finished the work on a different lib I want to publish, which relies on fraction.js, if the one or the other way is better. I'll extend the doc for now :) && Thanks for the feedback |
Ok thanks for the explanation. It's funny that in a couple of areas we would make the exact opposite decisions. But well, Fraction.js has a nice API, works fast, and is well tested, so I'm happy with it :). |
I think it's a mix of a functional and OOP approach if you will but things aren't cast in stone. If you find time, I'd be glad to hear what opposite decisions you would have made. |
Some random thoughts:
Not meant to criticize how you set up the library but meant as constructive input, some food for thought. |
Okay, finally published 2.0.0 with immutable objects, a prototype design, removal of Thanks for your input! |
wouw, thanks, that looks very clean :) Thanks for rethinking immutability of your lib. |
When I do an operation on a Fraction, the original Fraction is altered, for example:
Is this intentional behavior?
The text was updated successfully, but these errors were encountered: