Ruby-core dilemmas
This is a modal window.
Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.
Formale Metadaten
Titel |
| |
Serientitel | ||
Anzahl der Teile | 50 | |
Autor | ||
Lizenz | CC-Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Unported: Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben. | |
Identifikatoren | 10.5446/37493 (DOI) | |
Herausgeber | ||
Erscheinungsjahr | ||
Sprache | ||
Produzent | ||
Produktionsort | Miami Beach, Florida |
Inhaltliche Metadaten
Fachgebiet | ||
Genre | ||
Abstract |
|
Ruby Conference 20132 / 50
2
3
7
13
15
17
18
19
28
29
35
39
44
48
49
50
00:00
SpeicherabzugSpeicherbereinigungBitSpeicherabzugMereologieGefangenendilemmaVektorpotenzialVerdeckungsrechnungDatensatzCAN-BusCode
01:23
ComputervirusInstallation <Informatik>Wurzel <Mathematik>ProgrammbibliothekFramework <Informatik>Physikalisches SystemBitAutomatische IndexierungMultiplikationsoperatorVersionsverwaltungMathematikGrundsätze ordnungsmäßiger DatenverarbeitungRadikal <Mathematik>Installation <Informatik>PunktComputeranimation
02:39
Patch <Software>IndexberechnungSymboltabelleHash-AlgorithmusComputervirusProgrammbibliothekProjektive EbeneBrennen <Datenverarbeitung>VersionsverwaltungFunktionalKlasse <Mathematik>Patch <Software>InstantiierungBitrateAutomatische IndexierungGeradePhysikalischer EffektComputeranimation
03:47
MenütechnikInstallation <Informatik>Funktion <Mathematik>Data MiningWort <Informatik>RelativitätstheorieWeb logBitSuite <Programmpaket>MultiplikationsoperatorSoftwaretestRuhmasseFunktionalAbzählenCodeParametersystemRechter Winkel
04:32
p-BlockRechenwerkAliasingKlasse <Mathematik>VariableOvalVariableMathematikSoftwaretestFormale SpracheCodeSuite <Programmpaket>AbzählenSchnittmengeVirtualisierungp-BlockGüte der AnpassungResultanteZeiger <Informatik>Kontextbezogenes SystemImplementierungLesen <Datenverarbeitung>Patch <Software>InstantiierungComputeranimation
06:23
VersionsverwaltungFehlermeldungElektronische PublikationStandardabweichungÄquivalenzklasseIdeal <Mathematik>Keller <Informatik>PufferüberlaufFehlermeldungErwartungswertBitImplementierungFlächeninhaltVersionsverwaltungReelle ZahlVerschiebungsoperatorComputeranimation
07:18
Element <Gruppentheorie>PASS <Programm>ErwartungswertGleichheitszeichenKreisbewegungParametersystemNegative ZahlFehlermeldungRechenwerkAusnahmebehandlungLaufzeitfehlerInstantiierungVererbungshierarchieDrucksondierungKovarianzfunktionZeichenketteSpannweite <Stochastik>MehrrechnersystemPatch <Software>GeradeDatentypGanze ZahlKreisbewegungInstantiierungKovarianzfunktionVersionsverwaltungKlasse <Mathematik>Hash-AlgorithmusParametersystemCase-ModdingRechter WinkelZahlenbereichProgrammfehlerProzess <Informatik>BitFehlermeldungMultiplikationsoperatorPuffer <Netzplantechnik>CASE <Informatik>DickePatch <Software>Zeiger <Informatik>SoftwaretestGeradeSystemaufrufMereologieVerkehrsinformationZeichenketteGanze ZahlDefaultLaufzeitfehlerVerschiebungsoperatorElement <Gruppentheorie>Schreiben <Datenverarbeitung>Minkowski-MetrikTypentheorieObjekt <Kategorie>ProgrammierungInnerer PunktFlächeninhaltFokalpunktNichtlinearer OperatorWellenpaketeCosWort <Informatik>COMComputervirusVererbungshierarchieCodeBitrateStapeldateiArray <Informatik>E-MailGewicht <Ausgleichsrechnung>Endliche ModelltheorieQuaderComputeranimation
15:45
ParametersystemFehlermeldungDatentypGanze ZahlKreisbewegungLambda-KalkülPartitionsfunktionPatch <Software>Notepad-ComputerBinärdatenCodeHinterlegungsverfahren <Kryptologie>ProgrammfehlerObjekt <Kategorie>Web SiteZahlenbereichVersionsverwaltungResultanteInternetworkingCASE <Informatik>ZeichenketteSymboltabelleBitMixed RealityProgram SlicingArithmetisches MittelDatenverwaltungExogene VariableZeiger <Informatik>BereichsschätzungKovarianzfunktionE-MailDämpfungFahne <Mathematik>Cheat <Computerspiel>EntscheidungstheorieKreisdiagrammSpannweite <Stochastik>sinc-FunktionCursorGreen-FunktionPatch <Software>NeuroinformatikAutomatische DifferentiationSystemaufrufRandverteilungData MiningMailing-ListePunktZusammengesetzte VerteilungBinärcodeFormale SpracheOrdnung <Mathematik>VertauschungsrelationVerschlingungGleitendes MittelSoftwaretestMAPComputervirusVarianzPartitionsfunktioneCosElektronische Publikation
24:11
RundungSchwimmkörperSystemplattformOvalGleitkommarechnungDämpfungZahlenbereichReelle ZahlSpannweite <Stochastik>RestklasseVerschlingungResultanteSystemaufrufMathematikFormale SpracheDivisionGreen-FunktionSelbstrepräsentationVersionsverwaltungDezimalzahlUnrundheitVorzeichen <Mathematik>TypentheorieAuflösung <Mathematik>Ganze ZahlInverseBitZweiCASE <Informatik>InformationNegative ZahlRechenwerkCodeUmwandlungsenthalpieLokales MinimumObjekt <Kategorie>Arithmetischer AusdruckPunktrechnungSymboltabelleSystemplattformSchnelltasteZeichenketteGleichheitszeichenProgrammfehlerPotenz <Mathematik>BimodulSchreiben <Datenverarbeitung>UnendlichkeitPunktRechter WinkelParametersystemGreen-ITMultiplikationsoperatorRoutingQuaderTeilbarkeitDifferenzkernInverser LimesPRINCE2SoundverarbeitungMUDVirtuelle MaschineComputeranimation
32:38
OvalGleitkommarechnungSchwimmkörperFahne <Mathematik>Virtuelle AdresseSpeicherverwaltungKlasse <Mathematik>Schreib-Lese-KopfMehrwertnetzRekursive FunktionData DictionaryHackerQuellcodeLokales MinimumSoftware Development KitZeichenketteSelbstrepräsentationDezimalbruchKernel <Informatik>Array <Informatik>Objekt <Kategorie>Strom <Mathematik>Lie-GruppeGleichheitszeichenMarketinginformationssystemLastIndexberechnungElement <Gruppentheorie>SymmetrieRekursive FunktionFunktionalATMInterpretiererSystemplattformNichtlinearer OperatorDatenflussBitParametersystemCodeArray <Informatik>Objekt <Kategorie>Zeiger <Informatik>Klasse <Mathematik>InformationsspeicherungGreen-FunktionImplementierungProgrammfehlerElement <Gruppentheorie>MultiplikationsoperatorWort <Informatik>RestklasseSkalarproduktPaarvergleichZeichenketteElektronische PublikationCASE <Informatik>ResultanteGebäude <Mathematik>Arithmetischer AusdruckVersionsverwaltungOrdnung <Mathematik>DämpfungHackerÄquivalenzklasseDatenstrukturPoisson-KlammerSystemaufrufURLUnendlichkeitQuellcodeThreadGlobale OptimierungCase-ModdingInformationLie-GruppeGraphfärbungSelbstrepräsentationBimodulIntelligentes NetzTurnier <Mathematik>DifferenzkernDatensatzRechter WinkelBitrateCall CenterSystem FSichtenkonzeptMathematikerinGraphQuaderComputeranimation
41:05
Rekursive FunktionDatenstrukturHash-AlgorithmusRekursive FunktionGeradeZweiShape <Informatik>PaarvergleichGraphMultigraphProgrammfehlerCASE <Informatik>QuaderVerkehrsinformationBitSchreib-Lese-KopfPatch <Software>Gleitendes MittelGRASS <Programm>MultiplikationsoperatorVorlesung/Konferenz
42:24
SoftwareVideokonferenzComputeranimation
Transkript: Englisch(automatisch erzeugt)
00:17
So I'm going to start by the most difficult thing.
00:20
Well, first of all, everyone should have these little cutie cardboard things. If you don't, well, grab some next to you, because there really should be plenty for everyone. This is going to be for the second part of the talk, so if you stay that long. All right, so my name is Marc-Henri Laffortune. And that's, I'm sorry, it's not my fault.
00:42
I'm from Montreal, the French part of Canada. And we, you know, most people speak French there. So you can call me Marc. It's probably easier for most of you. And today, I'm going to talk about some Ruby core dilemmas I think might be interesting. And we're going to delve into a bit of code, Ruby a lot,
01:02
and some even C code. Don't be too afraid. So that's the title of the talk. But really, it could be Ruby core dilemmas and how I became a garbage collector. So actually, I'm going to first start as to how I became a garbage collector. And then we're going to look at some potential garbage
01:21
together. So let's go back five years ago when I fell in love with Ruby. And I was having some weird difficulties playing around with it and just asking 42 if it was odd or calling methods like find index. It was giving me no method error. And I was like, what's going on?
01:41
Well, on my Mac, there was Ruby 1.8.6 installed. And these were actually features only available in 1.8.7 or 1.9, because 1.8.7 actually introduced quite a bit of new features. I mean, it's officially just a point release, but 1.8.5
02:01
introduced two changes to the API, 1.8.6 three changes, and 1.8.7 137. And of those 137 changes, many of them are quite fun, quite interesting, and I wanted to use them. So what do I do? This was before RVM. Now with RVM, it's so cool.
02:22
You can juggle, change versions as you see fit. Try JRuby, Rubinius. But at the time, I was like, oh my god, there's already a Ruby install. Do I overwrite it? Do I do another one? How do I do with my pass? I'm really bad with the terminal. I just didn't want to touch any of that. So I was just lazy, and I said, screw that.
02:43
I'm not going to overwrite anything. I'm going to find another way. So of course, another way is just not use the new functions. And if you have a library that uses them, then don't use the library. So I was like, no, that's not the Ruby way. There's a beautiful feature for me. I want to use it. So I did what you're not supposed to do.
03:03
I monkey patched the basic classes to try to replicate the behavior. So for instance, here, I'm defining the odd method for fixNum and findIndex with array. And I did a couple of those for my first project.
03:21
This was cool. It worked. Then I did a second one. So I didn't want to copy the whole thing. So I said, oh, let's create a gem. So I start with my first gem. And I released my first version on April 1, 2009. And it had like 12 of the backboards I wanted
03:41
and had written. So today, the backboards gem has over 250 backboards and quite a bit of some gems uses it. So I get a whole bunch of downloads. But back then, nobody knew about them.
04:00
So a friend of mine on my blog said, well, is it going to work with Rails? And I answered, duh. I mean, Rails at the time targeted 1.8.6. So I'm just adding new functionality. There's no way it should break Rails. Now, the nice thing is Rails has this really huge massive test suite.
04:20
So I just ran it, but including backboards first. And it wasn't supposed to change anything. But of course, I was wrong. And there were major many, many failures. They were mostly due to gsub. So this was the code I had for gsub. It looks a bit complicated. But in 1.8.7, if you call gsub without a block
04:42
and with no argument, it returns an enumerator. I wanted to do the same thing here. So let me simplify it for you. Imagine that you monkey patched gsub this way. Should this change anything? The answer is it's not going to work.
05:02
And this is an example where instead of giving you the result you wanted, it's going to give you an error on nil. I was like, why is that? What's going on here? I mean, $1 is one of those funky global variables I never use. But I mean, anything starting with $ is just you're not
05:21
supposed to use them. I don't use them. I'm a good Ruby citizen. But some people do. What's up with that? Actually, $1 is not really a global variable. If you try to set it, for instance, it will tell you you can't. So I was like, OK, let's go into C code because I've got no idea what's going on with this. And if you do, you'll find that there's a whole bunch
05:41
of virtual variables. And they're defined like this in a C code. So OK, I can't play around with the $1. And because of the block context, it doesn't work anymore. So I removed the gsub backward. There's really no way around that. And every other feature basically of 1.8.7, I was
06:03
actually able to backward. But the question was now, I think I'm not breaking anything, but maybe I'm breaking stuff in JRuby or Rubinius. And so I was curious. What test suite do these guys have? Well, Rubinius has this really great
06:21
suite called RubySpec. And RubySpec is really testing Ruby the language. And it's really meant, actually, for all implementations of Ruby, including Ruby MRI. So I thought I should be able to run RubySpecs for my backport, and it should ideally pass, right?
06:42
OK, this is an example when you run RubySpecs just to show you that today there's over 150,000 expectations when you run the full suite, and it runs pretty quickly. There's still some errors today. I don't know why, but anyway. So here we're going to see a bit what RubySpec is about
07:01
and what writing a simple backport is about. This is a stack overflow, a real stack overflow question. This guy is stuck with 1.8.7, and he wants to use rotate on an array. And he says, hey, I want this, and I don't want to upgrade, he's lazy like me. What can I do? So the best answer was just call shift.
07:23
You're going to get your element and push it back. So it's going to push it at the end. All right, so this gives us our version one of the rotate backport. And let's see what the RubySpecs are saying. So the first test is usually a very, very basic test.
07:43
So in this case, you take a very simple array, and you try to rotate it, and you see what result it gives you. And if we actually try to pass it with our simple implementation, we pass. But do we pass all the specs? Well, no. The first one we get is it tells us that we should
08:04
return a copy of the array when the length is 0 or 1. And the example here is with an empty array, if you try to rotate, then our backport just doesn't work. So oh, yeah, we didn't think of this. But if you shift from an empty array, you get nil, which you're pushing back to the array, and you get an array
08:22
containing nil instead of the empty array you were expecting. OK, so version two of rotate. You can even make a gem release, right? You just don't do anything if it's empty, because you're happy with your solution. OK, so now we pass this test. But we have another test that fails.
08:41
Array rotate with an argument and returns blah, blah, blah. Oh, yeah, right. Rotate takes an argument. So let's add an argument. Default is 1. And it's quite nice, because shift accepts an argument. So we get an array, and we use concat instead of push. We should be good.
09:01
No, we're not really good. And actually, the test says that we should really do a mod on the size. Because if the number is negative or if it's too high, it should still work. But shift won't. So OK, this gives us a solution. We add mod. We're at version four of our rotate. Surely, we must be pretty close to passing the specs.
09:23
Well, we still have a failure. We're not supposed to mutate the receiver. Oh, right, there's two rotates. There's rotate bang and rotate without the bang. And it's only rotate with the bang that's supposed to mutate the receiver. So OK, now we have two back ports for the price of one. We're at version five, pretty proud of our work.
09:41
So you see what I did with rotate? I just called dup, and then I called a rotate bang version. OK, now we passed that test. But there's another spec we failed. We're supposed to return a runtime error on a frozen array. So the example that we fail, actually, is an empty array.
10:03
Because on an empty array, we don't do anything. So we don't raise an error. If the array is not empty, we're trying to shift. And this will raise an error. I mean, Ruby will raise an error for us. But in the case it's empty, we have to do something.
10:21
The reason we have to do something is Ruby, in general, wants to fail early. So in theory, you should be able to call rotate bang on a frozen array because it's not really doing anything. But in practice, we prefer to raise an error because normally, we expect that your array is probably not going to be empty. And in those cases, you would get an error.
10:44
So we have to modify this a bit and try to shift 0 if it's empty. So this will also raise the error for us. So we're at version 6. And do we pass the specs yet? No. OK, we have to return.
11:01
We should not return a subclass instance for array subclasses. Now, what the hell is this? I need to talk about covariance. Imagine that you decide to subclass array. Now, in practice, I don't know. Have you ever subclassed a base class? Maybe hash.
11:21
I think I've subclassed hash a couple times. And Rails actually subclasses string for the safe buffer. But in general, you don't typically do this. But imagine you did. And we're not even adding any method. We're just doing this for fun. We're creating an instance of that subclass. If we call rotate and ask what's the class of the
11:42
returned value, it's actually array. And that's our problem. We call dup. And dup will really make a duplicate. It will give us an instance of sub. So just for the fun of it, do you guys know if we call flatten if it will return sub or if it will return array?
12:02
I heard one sub, and he's right. So I think everyone agreed with him. That's why everyone is silent. Really good job. All right. If you ask the first two elements of an instance of class sub, you get array. If you ask for the first two elements but with the slice,
12:25
you get sub. If you add two sub instances, you get an array. And if you multiply an instance by two, you get sub.
12:41
All right. So that can be a little bit counterintuitive. But the funny part is that in Ruby specs, this kind of behavior is actually tested. Because when you're re-implementing Ruby, you're saying, OK, I don't really know why the C Ruby does this, but we might as well try to do exactly the same thing.
13:01
So if ever someone depends on that, we're compatible. And the covariance question goes for a lot of things. Just comparing two things, like substring versus an actual string, hash time, usually C Ruby will check that
13:23
you are a string, but you don't have to be a string. You have to be just not instance of, but kind of, if I'm not confusing the two. But I noticed when writing the specs that if you do that with range, it didn't work.
13:40
So I was like, well, that's got to be a bug. So that was my bug or feature. And I was like, I think there's something wrong here. So I submitted my first bug report. I went to the C code, tried to find where the hell this was happening. It was pretty simple to change. Actually, there were two lines in my patch.
14:02
And I was like, OK, I'm going to get my first patch accepted in C Ruby. And I had to wait six months. And I had to convince Matz and everyone else, yeah, it's a bug, because look at all the other cases, it works. And it really should be working. And that's how it is. So in 1.9.1 and over, it actually returns true.
14:27
OK, so if we go back to our version 6 of rotate and we want to be a good citizen and return just an array, we just need to replace the dup with array new of self and call rotate bang.
14:40
And now, surely, we must be with our super version 7 of rotate passing in space. We don't. So we actually have two more failures. And the argument that is passed needs to be cursed with calling toInt.
15:01
So basically, MRI always uses duck typing. It doesn't require you to pass an integer. You can pass any object. And if it needs an integer, it's going to call toInt. If it needs a string, it's going to call toStr. And make sure that what you return in those cases are an
15:22
actual string or an actual integer. And we also have some cases where we don't actually look at what is passed. So for instance, again, in the empty case, here, we're just disregarding whatever was passed and we're writing 0. But if whatever was passed was complete garbage, we
15:42
actually need to raise an error. So this is left as an exercise for all of you. Or you can look at the backwards code that actually goes through the trouble of doing the cursor and checking the result and everything to actually pass the Ruby specs. And this is the spec example of what it's trying to do,
16:05
if ever you want to contribute to Ruby specs. It's using MSpec, which is a version of RSpec. And this is the kind of thing it looks like. All right. So we're getting in the second half of the talk.
16:23
And this is where I'm going to want you to use these things. I'm going to present a couple of examples of Ruby code. And I'll want you to tell me if you think that the behavior you see is possible, should be possible, and if
16:41
it's correct, if it's a feature, or it actually is a bug that needs to be fixed and you need to post on bugs.rubylang.org. So this is an example with covariance, where we're not passing a string to partition. We're passing an object that has a method to SDR.
17:01
And if we do that, we get actually different results. Is this a bug or is this a feature? Raise whichever you think is a feature or a bug. Ah, come on. I want more than that. Even if you don't know, just take a wild guess. You're on camera, and the whole internet will know that
17:20
you were wrong. OK, so in this case, really, in all cases of, did I say covariance before? It's not covariance. It's just duct typing. In all cases of duct typing, CRuby must behave exactly the same way if you pass an object that is string-like, or
17:41
if you pass an actual string. So in this case, this is a bug. And 20 minutes later, after I submitted a patch, it was merged in by Nobu. Nobu, I don't know if you've been to Koichi's great talk. He had this really awesome, yeah, Nobu is with us.
18:00
So Nobu is the, so I could say that Nobu is the number one committer in CRuby. But that wouldn't really be fair, because if you've been to Koichi's excellent talk, there was this huge pie graph
18:21
of the number of commits. And one third are from Nobu. So all the others are like tiny slices all around, and Nobu is just taking the whole bulk of things. So thanks, Nobu, for merging this so quickly. This was easier to show that it was a bug, because you
18:42
could actually crash Ruby, in this case, when you were playing with the results. It was fun. All right, so, bugger feature. You take an array you call valuesAt with a range that extends past the array. So if you ask the range from 1 to 42 inclusive, you
19:02
get 2 and 3. And if you ask for 1 to 43 exclusive, that's what the three dots mean. You get 2, 3, and nil. Bugger feature.
19:20
All right, I see it's a little bit more red than green, but there's a bit of a mix here. Well, don't be ashamed, because when this was submitted by Mark Rata on the mailing list, first response was from Yui, from Narus. He's another C committer.
19:42
He's responsible for the upcoming Ruby 2.1. He's the release manager. He worked a lot on multilingualization. And he does a whole lot of stuff. And he actually commits also a lot to Ruby spec. So he's like, is this a bug?
20:00
I'm not sure. Nobu said, it's definitely not a bug. So everyone that raised the green check mark and feel confident that Nobu agrees with you. But I was like, no, no, no way. This has to be a bug. So in those cases, what do we do?
20:20
We do nothing. We do nothing, and we wait. And one day, Matt comes in and says, I've thought about
20:40
this very well, and the trailing nil must be a bug. OK, so the nil is not good. There's something wrong going on. Next question, of course, is, OK, do we want it to have any nils, or do we want a bunch of nils? So is this a bug if we modify Ruby so that it returns
21:04
this, or is it a feature? Oh, lots of green, lots of green. It's really good. So it's a feature since Ruby 2.0. And why is it a feature?
21:22
Because Matt says so. That's really the definition, right? I mean, it's a bug or a feature, depending on what Matt decides. In this case, it was actually something that could
21:40
change potentially some code that we included in the news file, but some others are not so clear. All right, I'm going to acute one for you. You have a string that's nine characters long. OK, so now I'm cheating a bit. The question mark is not an actual question mark.
22:01
It's a character. I'm not telling you which, so I'm just adding a bit to the mystery. So I'm just telling you that you have a string. It's nine characters long, exactly. I call toSim, so I'm getting a symbol. And I call toS on it, and I get plus. Bug or feature?
22:24
Lots of red, lots of red. Well, you're wrong, OK? If you try this with any version of Ruby you have on your computer, including JRuby and Rubinius, Ruby 1.8, 1.9, or 2.0 with plus in parentheses binary, you're going
22:42
to get plus, all right? And you can check in Ruby specs. I don't actually know who wrote the spec, but someone went and said, oh, there are these special cases. We have to include them in the Ruby specs. And it also works for minus binary if you're really interested, and you get the symbol of minus.
23:01
Now, this actually was brought to me by Kenichi Kamiya, who said he was going to be here today. Is he here? He's here and shy, probably. So I brought this up, and I said, Matt, this makes no sense. He agreed with me. So this is no longer going to be the case.
23:21
There's no longer going to be these bugs or features. I'll leave it up to you. All right, 0 plus 1 plus 0 plus 2 is equal to 0 plus 3. You get false. Is that a bug? Is that a feature? OK, lots of green, couple of reds.
23:42
Well, I'm very happy for you, those people that raised the red flag. That means that you don't deal too much with floats. And this is probably very good for your sanity, because there's so many little bugs with the floats, it's not even funny. So actually, this is a feature.
24:02
It's called ISO something. And this is an actual website. I'm not inventing that. You can go there. It has a small explanation, some links to Wikipedia, and solution hints for depending on your language.
24:21
And it's going to tell you that in Ruby, to avoid these kinds of issues, you can use big decimal or rational, depending on what you need to use. So this is because floats are actually not exact numbers. They really represent very, very tiny ranges of real values. And when you actually play with them,
24:42
they're going to give you sometimes unexpected results. All right, I'm getting back with my question marks here. So I'm writing this float number. And if I ask for the string representation, I get 0.21. Does it seem like a bug, or does it seem like a feature?
25:05
OK. Lots of green. That's a feature, because if you ask to store too many decimals in your float, Ruby uses the normal double, 64 bits of resolution. It's going to say, well, really, you're talking about
25:21
0.21. That's where it fits in the range. I'm going to simplify it for you, and we're just going to talk about 0.21. It's good. Bugger feature. I have less decimals this time. And I ask Ruby to get me a string and then get me back my float.
25:41
But it doesn't give me back the float I started with. Is that a feature, or is that a bug? Oh, lots of green. Well, that's a bug. Because in most versions of Ruby 1.8, there were specific float values that wouldn't round trip like this. But really, you can type in a whole bunch of numbers.
26:03
But when Ruby prints to you a number, it should always be the minimum representation that has all the information about that float. So it should contain all the information, enough to round trip, but no more. And in some cases, Ruby was not
26:22
giving you enough information. So if you tried to round trip, it would fail. All right, so did you know that you could round integers? It doesn't make sense to give positive values. I mean, it's going to give you back your integer. But if you give negative values, it's going to strip off some of the information.
26:42
So if you start with 321, you get 320, you get 300, and then you get 0, because there's no 1,000 units. And then if you go down, down, down, down, at some point, you get 0.0. Bugger feature, lots of red, couple of greens.
27:03
So yeah, ideally, you would want to have always 0, because it doesn't make sense that at some point, it starts to be a float. I mean, you start with an integer. You ask it to be rounded up. You want to fix none. So this was fixed. And we're actually, that's because I
27:21
was backporting round. I was writing some Ruby specs. And writing the Ruby specs, they were actually failing in Ruby. I was like, what's going on? And I was trying to test the extreme cases. And then I looked at the code and found some other bugs. So I'm going to be going a little bit quicker here. But these are kind of funny examples
27:42
where you're like, what is going on with this? And if you delve in the code, well, you see that sometimes you need to, you can't exponentiate with too high values. And so you take some shortcuts. And if you make some small mistakes,
28:01
then it could bite you. Another funny example, and even some basic arithmetic with no arguments, no nothing would give you the wrong results on some platforms.
28:21
So if you have a behavior that changes from platform to platform, that's a really, really big sign that this is what you should be raising. All right, bugger feature. We have three objects, n, o, and p. And they are all equal to one another. But if you do 1 over n, 1 over o, or 1 over p,
28:42
you get three different results. Bugger feature. I'm impressed. Lots of green check marks. So yeah, if you have 0, 0.0, and minus 0.0, they're all equal with the equal sign.
29:01
But if you inverse them, you're going to get different results. There's actually the equal, the eql question mark method just to distinguish 0.0 from 0 and 1.0 from 1. And it doesn't even distinguish minus 0.0 from 0.
29:23
As far as I know, the best way to distinguish them, if ever you need to distinguish them, and then I really pity you, is to take the inverse and check for the sign. All right, so x is equal to something,
29:41
and x equals x returns false. Bugger feature. Oh, lots of red. It's actually a feature. So congratulations to the few green check marks, because actually, not the number is the,
30:01
as far as I know, only, well, it is the only built-in object that is not equal to itself. All right, foo and bar are two very simple objects. And I ask the modulo of foo and bar,
30:21
or I send the modulo symbol with bar as an argument, and I get different results. Is that a bug, or is that a feature? Oh, yeah, cool. I'm very happy that most people found it. It's a bug. It really should give you the same result. I mean, if you call a method and you send it,
30:42
there's really no reason to get a different result. So when I saw that, I actually couldn't believe my eyes. I was like, I didn't see how this was even possible. I mean, what's going on in the C code that it knows if it was called with send or if it was called directly with the operator?
31:04
And well, a second question is, what's the right result? Is it not a number, or is it 4.2? So I'm going to start with the second question, because that's the easiest to find. If you ask 4.2 and you take the modulo with big numbers, you get 4.2.
31:20
So at the limit, you really should be getting 4.2. And we can even think of what's going on a bit, because when you actually want to calculate the modulo, the easiest way is to make the division, truncate the result to an integer, and then subtract that number from what you started from.
31:42
But if you do that, then with infinity, you have a problem. You're multiplying 0 with infinity. This gives you another number. And another number is going to propagate to basically any mathematical expression you have. All right, so let's get to how is this possible. And to that, we have to delve in the C code.
32:02
So this is the method that actually does both the division and the modulo in CRuby. And you see there's this if def, have fmod, and there's two things. So you either call this fmod method, which is a method from math.h, or you do it by yourself
32:25
in exactly the same way I said. You first divide, then you re-multiply, and you remove that from what you started with. You get your modulo. And fmod is actually the built-in method of C,
32:41
but only on some machines. And Ruby wants to be compatible with, really, a lot of platforms. So that's why there's an if def. That still doesn't explain anything. What it really means is on some platforms that have fmod, you might not get the same result as on platforms where you don't. But on my platform, it's not going
33:01
to decide that it has fmod or not, depending on if I call send or not. So there was something else going on. And I found a solution in a file called insns.def. Now, this is not even real C code. There's kind of weird stuff going on here.
33:20
And I'm not going to go too deep into this. But if you want to learn, here we are actually in YARV. We're in the bytecode interpreter of Ruby. And if you call the modulo operator before calling the method, it's going to check.
33:41
Is the receiver a float? Is the argument a float? And was the modulo method redefined? If it's not been redefined, then we know what to do. And it goes on and does the simplistic version of calculating the mod.
34:00
So that's what happens, actually, because YARV optimizes when you call directly a plus, an equal, and a couple of methods. Some of the code might be duplicated. So the solution here was just to call the same code and actually be a little bit more careful with the highlighted code to check for infinities.
34:22
And we're back to sanity. All right, a little example here. X and Y, you call X smaller than Y, and you get neither true nor false. Bugger feature. You guys are good. A lot of green.
34:42
And yeah, I cheated a bit. We're off the floats. If you ask that for classes, it could return nil if the classes are not related. So we just have to remember that some of the operators are actually not. They're just method calls in the end. And YARV will not optimize that.
35:01
All right, X and Y are some objects. And X is equal to Y, or Y is equal to X. Do not return the same thing. Well, I'm putting big question marks here.
35:21
And I'm cheating a little bit because the question marks were replaced by actually two expressions. I'm building here an array called X. And I am adding X to itself. And I'm comparing it to an array containing
35:42
an array containing X. And I'm getting true and false. So I think, in general, equal should be reflective. I'm not sure what the word is. But it really should be independent of the order. So I delved into the code and was wondering what's going on.
36:00
So I need to talk a bit about recursion. You all know the definition of recursion. See recursion from the hacker's dictionary. Usually, we talk of recursion for functions or methods. And recursion is the calling of a function from within that same function.
36:22
But we have recursive structures like arrays here. And these can be quite fun to play with. So here, I'm just giving an example where my array contains 1, 2. And then I'm adding myself. I'm adding X to X. And I can go on
36:41
and asking for the last element, last element, last element, last element. And I can go on forever. And I'm still going to get X. And Ruby is actually really good in handling recursion. If you ask a string representation, I don't know if you've ever seen the little three dots saying, I'm not going to go there because otherwise, I'm just never going to finish.
37:01
And you can actually serialize recursive structures both with Marshall and Yama. And if you try to do something a bit crazy, like flattening it or joining it, it's going to tell you, I can't do it. And it's going to tell you why. But for comparison, I wasn't sure that the behavior was OK. So let's see exactly how Ruby does it.
37:24
This here is the array inspect function. And it calls RB exec recursive with a function argument inspect array. So RB exec recursive will pass an argument
37:42
to detect recursion and gives the chance to the function to do something special if we're in the recursive case. So in this case, we have the little three dots signifying that we are in a recursive structure. Otherwise, it just builds the inspect as you would think it would. And it has to call, of course, RB inspect
38:02
to call inspect on each element. And this is how you could get a recursion. Now, to get into what RB exec recursive does, I'm going to simplify it a bit for you. We're going to go into Rubinius, where the implementation is in Ruby. So it's going to be a bit easier to understand.
38:24
And the 2S and inspect version are exactly very, very similar to the C version. And the equivalent here is thread detect recursion. So let's see what's the definition of thread detect recursion. I don't know if you guys have played with Rubinius.
38:41
But if you ask the source location of a method, even if it's a built-in method, it actually gives you the right place to look at. So that's really, really cool compared to having to dig through the CRuby code to know where each method and functions are. So the idea is quite simple.
39:01
When we visit an object, we're going to remember. We're going to store that, that we've been here. And then we're going to let the flow of instruction go. If we ever go back and we've encountered ourselves, we've seen ourselves, we stop, and we just return, yes, we are in a recursive mode.
39:21
So this doesn't actually work for comparing because you might detect recursion at the wrong time. In one case, we're detecting it early. So we stop. And in the other case, we don't detect it at the same time.
39:44
So the right way to do that for comparison is actually to stop the behavior when you've encountered both objects being verified at the same time. You don't just store the idea of the caller.
40:00
And when you do that, you're going to get the same results because you're going to detect recursion at the same time. Now, I'm going to ask you one last question. Should these two arrays be the same or not? So if we had x and bracket, bracket x be false,
40:23
would that be a bug or a feature? I think half of the room is sleeping right now. OK, that is an evenly matched thing. So you have to think of it in the other way.
40:44
When are they not equal? And they're not equal if the size is different or if when you go through them, you get to a different element. And the right solution is to return true. And you have other cases where the structure
41:02
might seem a bit different, and you still want to be true. So this is a little graph. What we have here, the top box is x that contains itself. And if you put that in an array, and in an array, you get this thing here is this one, this one is this, and this is the other example I gave.
41:23
And you might say, oh, the graphs are very different. But if you actually try to unfold them and imagine that you've got infinite time, they all give you the same straight line. And the shape is the same, so they really should be equal. And after that patch, Ruby really
41:41
supports recursion perfectly for serialization and comparison. That was my second bug report. There's been quite a few in the meantime. I'm going to conclude by quoting Matt's. Ruby is quite simple when you use it.
42:02
But if you dig a little bit further, you get some strange and weird cases and behavior. I invite you to question what you see in Ruby. Is this really a feature? Is this really a bug? Is this what I expected? Why didn't I expect that? And why has it been designed this way? It might actually be a bug, or it might be a feature.
42:22
You can ask Matt. Thank you.