Attack of the Codosaurus!
Adventures of a dinosaur evolving to meet today's challenges.
MOST OLD CONTENT HAS MOVED; see new home at
https://www.codosaur.us/blog/
Sunday, March 20, 2022
Comments off due to spam
Due to a spate of spam comments, commenting has been turned off for this blog. Eventually, all posts will be moved to a new home at https://www.codosaur.us/blog anyway, and this Blogger account will be turned off.
Tuesday, December 6, 2016
And this is crazy . . . .
Just a bit of Ruby silliness to brighten your day. You can save this to a file (like, oh, say,
carly.rb
), and run it a bunch of times, or you can just paste it into irb
and run just the last line a bunch of times.
me = ->{42} class Proc def maybe rand(2)==0 ? self : ->{} end end def call(it) it.call end puts call me.maybe
Thursday, November 10, 2016
x == x = WTF?!
A short time ago in a project close by, someone (not me, but ironically enough someone who has been hacking Ruby about twice as long as me) had committed code similar to:
First, since "status = 1" would always return a truthy value, the rest of it would get short-circuited. So, we can omit the rest from further investigation, and just use "status == status = 1".
But more importantly, this got me thinking, what would that even DO? The rest of this post is a quote from the analysis I wrote up just because I thought he would find it amusing. If anybody is familiar with how MRI works under the hood, please let me know if I'm guessing right!
===8<---cut here---
This LOOKS like it might mean "assign status, and check if it still has the value we just assigned to it", but after some experimentation in irb, it's really "assign status, and check if that matches its previous value". Enlightening experimental irb session:
Apparently the == receives the original value of status, as though the order of ops under the hood is "push status, assign status, push status, call ==". With most Ruby objects, this would always result in true... but Fixnums are odd ducks, in that they are all unique values. 1 is 1 (and all alone) and ever more shall be so. ;-) Any Ruby object that is a Fixnum with the value of 1 is the same object and will never have another value. If you reassign the variable, you're setting the variable itself to refer to a different object. In C terms, you're changing the pointer, not some field in what it points to. So, the odd-numbered lines above were equivalent to "push something that doesn't exist so barf with a NameError", "push nil, assign x 1, push 1, call ==", and "push 1, assign x 1, push 1, call ==". If we were to now do "x == x = 2", it would be "push 1, assign x 2, push 2, call ==", so that the comparison would fail but afterwards we would find that x is indeed 2 -- and doing that same Ruby command again would succeed the second time, as it would then start with "push 2". Mind you though, that's in MRI; I have no idea if it's the same in mruby.
That probably makes most of you go WTF, and you probably figure the author meant "if status == 1 || status == 2 || status == 3" but somehow screwed up to the point of insanity, and rightly so. But bear with me, it's a fun little exploration of some bizarre Ruby usage.if status == status = 1 || status = 2 || status = 3
First, since "status = 1" would always return a truthy value, the rest of it would get short-circuited. So, we can omit the rest from further investigation, and just use "status == status = 1".
But more importantly, this got me thinking, what would that even DO? The rest of this post is a quote from the analysis I wrote up just because I thought he would find it amusing. If anybody is familiar with how MRI works under the hood, please let me know if I'm guessing right!
===8<---cut here---
This LOOKS like it might mean "assign status, and check if it still has the value we just assigned to it", but after some experimentation in irb, it's really "assign status, and check if that matches its previous value". Enlightening experimental irb session:
At line 001, we get a NameError because we haven't told it about x. At 002, x now exists (but is nil), because referencing it brought it to Ruby's attention. At 003, there is no NameError, so it can get to the assignment, but the old value was nil, so the comparison is effectively "nil == 1". At 004, we can see that it did indeed get assigned 1. At 005, the values finally match.$ irb :001 > x == x = 1 NameError: undefined local variable or method `x' for main:Object Did you mean? x from (irb):1 [...] :002 > x => nil :003 > x == x = 1 => false :004 > x => 1 :005 > x == x = 1 => true :006 >
Apparently the == receives the original value of status, as though the order of ops under the hood is "push status, assign status, push status, call ==". With most Ruby objects, this would always result in true... but Fixnums are odd ducks, in that they are all unique values. 1 is 1 (and all alone) and ever more shall be so. ;-) Any Ruby object that is a Fixnum with the value of 1 is the same object and will never have another value. If you reassign the variable, you're setting the variable itself to refer to a different object. In C terms, you're changing the pointer, not some field in what it points to. So, the odd-numbered lines above were equivalent to "push something that doesn't exist so barf with a NameError", "push nil, assign x 1, push 1, call ==", and "push 1, assign x 1, push 1, call ==". If we were to now do "x == x = 2", it would be "push 1, assign x 2, push 2, call ==", so that the comparison would fail but afterwards we would find that x is indeed 2 -- and doing that same Ruby command again would succeed the second time, as it would then start with "push 2". Mind you though, that's in MRI; I have no idea if it's the same in mruby.
Saturday, March 14, 2015
TMI from RSpec
When using
RSpec, there is a small
annoyance waiting to getcha, when you compare numbers.
TL;DR: using "
Frex, if you are sending an HTTP request and expecting to get back the OK status code, you might say:
The minor, trivial, nitpicky, but still a bit annoying, problem, comes when the status is not 200. Sure, you'll still get a test failure... but you'll get some added little distractions, the bold parts here:
(And okay, granted, four short lines of additional feedback aren't that much of a pain... but still, they add up. If you're new on a project with lots of failing tests, as I am now, it can make it harder to wade through all the failures and see which ones actually mean anything.)
So how do you get rid of it? You might try using "
But... if you take it one step further, and use "
TL;DR: using "
be
" (or "equal
", which
does exactly the same thing) produces distracting and usually meaningless extra
verbiage, while "eq
" (or "eql
", which is a little
different but close enough for this purpose) does not.
Frex, if you are sending an HTTP request and expecting to get back the OK status code, you might say:
This will work fine, and so long as the status actually is 200, then all is right with the world.
expect(response.status).to be 200
The minor, trivial, nitpicky, but still a bit annoying, problem, comes when the status is not 200. Sure, you'll still get a test failure... but you'll get some added little distractions, the bold parts here:
All you really needed is this:
Failure/Error: expect(response.status).to be 200 expected #<Fixnum:401> => 200 got #<Fixnum:1001> => 500 Compared using equal?, which compares object identity, but expected and actual are not the same object. Use `expect(actual).to eq(expected)` if you don't care about object identity in this example.
(Okay, granted, you didn't really need to know it was compared using "
expected: 200 got: 500 (compared using ==)
==
", but still, that's not too bad... at least it's a lot
less distracting verbiage to wade through than the prior message.)
(And okay, granted, four short lines of additional feedback aren't that much of a pain... but still, they add up. If you're new on a project with lots of failing tests, as I am now, it can make it harder to wade through all the failures and see which ones actually mean anything.)
So how do you get rid of it? You might try using "
==
" instead of "be
", especially if you're used to
RSpec's older "should
" syntax rather than the new
"expect
" syntax (which I'll ass-u-me you are now using)... but
then you'd see:
But despair not, dear reader, all is not lost! It's still very simple. Instead of "
Failure/Error: expect(response.status).to == 200 ArgumentError: The expect syntax does not support operator matchers, so you must pass a matcher to `#to`.
be
", use "eq
".
Then you get:
As you may recall from my Ruby Gotchas presentation, there are several ways to compare things in Ruby:
Failure/Error: expect(response.status).to eq 200 expected: 200 got: 500 (compared using ==)
- "
==
" compares the value, without regard for the actual class(es) of the objects. So, frex, "1.0 == 1
" istrue
. This is what RSpec uses when you say "eq
" (or "==
" in the older syntax); hence the message "(compared using ==)
". - "
.eql?
" compares the value and class! So, "1.0.eql? 1
" isfalse
! - "
.equal?
" is object identity. That is, even if two things have the same value, and the same class, if they're not the exact same object, they are not ".equal?
". One of the oddities of the Ruby object system is that an object is created for each new unique Float value, so the result of:
x = 1.0 y = 1.0 x.equal? y
true
! However, if we use a more complex class, likeString
, we can see that "'foo'.equal? 'foo'
" isfalse
.
expect(response.status).to eql 200
"? Pretty much the same
thing, just now it says "(compared using eql?)
".
But... if you take it one step further, and use "
expect(response.status).to equal 200
"... then you're back to
Square One. That shouldn't surprise any of you who paid close attention
to the original verbose error message: it told you that when we used
"be
", it "Compared using equal?, which compares object
identity
".
Saturday, February 21, 2015
Case Study: TDD Is Worth It!
One of my most popular blog
posts, counting its reading elsewhere, is Is TDD Worth It? Recently I had a great example of
how, to quote myself, "It's almost like giving you
guard rails."
Suppose the application domain is as follows:
But... how do I get from Point A to Point B? This is where TDD came in handy!
Of course, I had been TDD'ing the original functions, using the classic "red, green, refactor" cycle. (Actually, I use a variant that adds a step: "refactor the tests".) So, I substituted a simple call to my proposed new function, for the guts of one of the old ones:
Long story short, I followed this pattern, over and over:
Had it not been for having an existing test suite, which I had because I had TDD'ed the original code, I would have had to be a lot more slow, careful, and methodical in that process. Instead, I could just quickly try what came to mind, and see if it worked without breaking anything else.
The resulting function, including some extracted functions to keep that code dry, runs to a mere 43 lines of code, and is structured in such a way as to make it very easy to add additional items to filter on, such as the Material a Part Type is made of, the color a given Part is painted when used in that Thing, etc.
(Yes, yes, I could have created the test suite just before embarking on this... but seriously, what are the chances? Most developers would not bother, for something smallish like this. Perhaps for a larger code archaeology expedition, where writing a test suite to ensure continuing the current behavior, whether correct or not, is a common first step.)
Suppose the application domain is as follows:
- Things have Parts.
- Parts have Types, which have Categories.
- Within a given Thing, Parts are used with various specific Connectors.
- Parts of a given Type or Category.
- Parts of any Type or Category out of a set of Types or Categories.
- Parts of all the Types or Categories out of a set of them.
- The negatives of all of the above, i.e., those using:
- no Parts of that Type or Category,
- no Parts of any Type or Category of a set,
- and those not using Parts of all (though they may use some) Types or Categories of a given set.
Thing
with
names like:
- find_with_connector_and_part_type(conn_type,
part_type)
- find_with_connector_and_any_part_types(conn_type,
part_types)
- find_with_connector_and_all_part_types(conn_type,
part_types)
- find_with_connector_and_part_category(conn_type,
part_category)
- find_with_connector_and_any_part_categories(conn_type,
part_categories)
- find_with_connector_and_all_part_categories(conn_type,
part_categories)
- find_without_connector_and_part_type(conn_type,
part_type)
- find_without_connector_and_any_part_types(conn_type,
part_types)
- find_without_connector_and_all_part_types(conn_type,
part_types)
- find_without_connector_and_part_category(conn_type,
part_category)
- find_without_connector_and_any_part_categories(conn_type,
part_categories)
- find_without_connector_and_all_part_categories(conn_type,
part_categories)
I kept the code as DRY as I could, making the negatives simply call the
positives and invert the finding, and making the singles pass a single-element
set to the "any" case. That still left a lot of structural
duplication. Since they were class-methods used as scopes, they all
looked like this:
def self.find_with_connector_and_part_SOMETHING(conn_type, SOMETHINGS) id_query = assorted_magic_happens_here(several_lines) where(id: id_query) # negative version: where("id NOT IN (?)", id_query) endThat got me thinking about how to combine this set of literally a dozen different functions into one, something like
Thing.find_with_parts(part_options)
, where
part_options
would include what Connector, what Types (and whether
we want all or just any, which would be the default), what
Categories (ditto), and whether to invert the finding. When the
client later said they didn't in fact want a bunch of separate methods,
I was ready, and had a lot of the idea already thought out.
But... how do I get from Point A to Point B? This is where TDD came in handy!
Of course, I had been TDD'ing the original functions, using the classic "red, green, refactor" cycle. (Actually, I use a variant that adds a step: "refactor the tests".) So, I substituted a simple call to my proposed new function, for the guts of one of the old ones:
def self.find_with_connector_and_part_type(conn_type, part_type) self.find_with_parts(connector_type: conn_type, part_types: [part_type]) endand reran its tests. Of course it failed, as I hadn't written
find_with_parts
yet... but that came pretty easily, based on the
logic that had previously found Things having Parts of any of several Types,
used with the given Connector. That test quickly passed.
Long story short, I followed this pattern, over and over:
- Substitute a call to
find_with_parts
for the guts of a specific method. - Run its test.
- If it works, break it by changing
find_with_parts
! You always want to start with a failing test! - Fix
find_with_parts
to make the test pass. - Run the rest of the tests!
- If any of them fail, go back to fixing
find_with_parts
.
Had it not been for having an existing test suite, which I had because I had TDD'ed the original code, I would have had to be a lot more slow, careful, and methodical in that process. Instead, I could just quickly try what came to mind, and see if it worked without breaking anything else.
The resulting function, including some extracted functions to keep that code dry, runs to a mere 43 lines of code, and is structured in such a way as to make it very easy to add additional items to filter on, such as the Material a Part Type is made of, the color a given Part is painted when used in that Thing, etc.
(Yes, yes, I could have created the test suite just before embarking on this... but seriously, what are the chances? Most developers would not bother, for something smallish like this. Perhaps for a larger code archaeology expedition, where writing a test suite to ensure continuing the current behavior, whether correct or not, is a common first step.)
Tuesday, December 2, 2014
HookLyingSyncer: gem to keep method_missing and respond_to_missing? in sync
A while back, I wrote about the need to keep
(A brief refresher: in 2011, Avdi Grimm wrote a blog post about that. In the comments, I wrote up a quick and dirty hack to do so, in very restricted cases, and then a still-dirty improvement, that unfortunately has since been mangled somehow.)
At RubyConf 2014, Betsy Haibel spoke on Ruby metaprogramming, including that same need. That inspired me to work on the concept again, taking a different approach (essentially a decorator) that I had briefly considered in those comments.
The result is my new gem HookLyingSyncer. (I was going to call it FirstResponder, but that name was already taken.) The code is at https://github.com/davearonson/hook_lying_syncer. For now, the code looks like:
This is my first time actually making a gem, and I haven't done much with metaprogramming before, especially something that other people are going to use to do their metaprogramming. So, any feedback would be greatly appreciated!
method_missing
and
respond_to_missing?
in sync.
(A brief refresher: in 2011, Avdi Grimm wrote a blog post about that. In the comments, I wrote up a quick and dirty hack to do so, in very restricted cases, and then a still-dirty improvement, that unfortunately has since been mangled somehow.)
At RubyConf 2014, Betsy Haibel spoke on Ruby metaprogramming, including that same need. That inspired me to work on the concept again, taking a different approach (essentially a decorator) that I had briefly considered in those comments.
The result is my new gem HookLyingSyncer. (I was going to call it FirstResponder, but that name was already taken.) The code is at https://github.com/davearonson/hook_lying_syncer. For now, the code looks like:
class HookLyingSyncer
def initialize(object, matcher, &block)
@object = object
@matcher = matcher
@block = block
end
private
def respond_to_missing?(sym, include_all=false)
matches = find_matches(sym)
matches.any? ? true : @object.send(:respond_to?, sym, include_all)
end
def method_missing(sym, *args, &blk)
matches = find_matches(sym)
if matches.any?
@block.call(@object, matches, *args)
else
@object.send(sym, *args, &blk)
end
end
def find_matches(sym)
result = @matcher.call(sym)
result ? result : []
end
end
The tests contain some examples, with further usage explanation
in the README. Long story short, it can be used on
instances and classes, to add or override method definitions,
including overriding new
so as to add methods to all new instances
of a class.
This is my first time actually making a gem, and I haven't done much with metaprogramming before, especially something that other people are going to use to do their metaprogramming. So, any feedback would be greatly appreciated!
Thursday, November 6, 2014
Windows and Linux and Mac, Oh My!
Someone recently asked in the Google Plus Ruby on Rails community: Which platform would be the best to use for Rails? My answer got so long, and is so applicable to working in most other languages, that I decided to turn it into a blog post, so here it is, with a few minor edits.
===8<--- cut here ---
Basically, any reasonably popular platform EXCEPT Windows.
Windows (ignoring the Server variants) is made for the desktops of non-technical people (and people developing software specifically for Windows). It comes with essentially zero development tools, and you have to pay for most of the serious ones, especially for the MS stack. You can kinda-sorta fake a lot of Unix by installing Cygwin, but that's just a kluge and falls way short.
Linux and other Unix variants such as BSD, Solaris, etc. are made for servers, and the desktops of VERY technical people. They come with (optional on installation) lots of serious development tools, with many more easily available, most of them free. Of these OSes, Solaris is pretty much dead, and BSD is very rare outside the server room (so there's nowhere near as many resources for help and education), and the others except Linux have very tiny market share, so let's focus on Linux. However, Linux has a bad reputation for requiring a lot of tweaking to get it to work reasonably well, especially if you're using hardware that is in any way not absolutely standard, such as a graphics card from within the past year or a maker that's not one of the top three. This was reality, and why I switched to a Mac, in 2004, but I've heard Linux has gotten a LOT better about this since then, especially the Ubuntu "distro" ("what's a distro" is a whole 'nother question!), which (I've heard) places great emphasis on working right out of the box. Linux is also free (though you can buy boxed sets with docs, support, and so on), and generally efficient enough to make good use of older PCs that won't run well under recent versions of Windows.
A Mac is a reasonable compromise, at least as of when OSX first came out. (Before then, it was aimed mainly at graphics people, like artists and people who put together print newsletters and magazines.) The tooling situation is similar to Unix, except that it doesn't come with quite so many, and usually older versions. It also doesn't require anywhere near as much tweaking as Linux does, because you're running it on exactly the hardware it was designed for. It's even more consistent in its UI behavior and look-and-feel than Windows, and about as easy to understand -- but it's different, so you'll have a lot to "unlearn" if the Windows way of doing things is deeply ingrained in your habits. It also used to be much more stable and secure than Windows, but MS has made great strides in their security and stability, catching up and, depending how you measure things, possibly surpassing OSX's security (not sure about stability). On the other claw, it's a good bit more expensive than a bog-standard Windows box, never mind Linux, but frankly, they're so good that putting together a Windows PC with the same performance will usually cost about just as much, even before factoring in the cost of serious dev tools, OS upgrades (usually dirt-cheap or even free on a Mac), etc. You can get some good bargains on a used Mac, one or two generations old; ask a Mac-using friend to sell (or even give, if you're lucky!) you one of his old castoffs, or take your chances on eBay.
===8<--- cut here ---
Basically, any reasonably popular platform EXCEPT Windows.
Windows (ignoring the Server variants) is made for the desktops of non-technical people (and people developing software specifically for Windows). It comes with essentially zero development tools, and you have to pay for most of the serious ones, especially for the MS stack. You can kinda-sorta fake a lot of Unix by installing Cygwin, but that's just a kluge and falls way short.
Linux and other Unix variants such as BSD, Solaris, etc. are made for servers, and the desktops of VERY technical people. They come with (optional on installation) lots of serious development tools, with many more easily available, most of them free. Of these OSes, Solaris is pretty much dead, and BSD is very rare outside the server room (so there's nowhere near as many resources for help and education), and the others except Linux have very tiny market share, so let's focus on Linux. However, Linux has a bad reputation for requiring a lot of tweaking to get it to work reasonably well, especially if you're using hardware that is in any way not absolutely standard, such as a graphics card from within the past year or a maker that's not one of the top three. This was reality, and why I switched to a Mac, in 2004, but I've heard Linux has gotten a LOT better about this since then, especially the Ubuntu "distro" ("what's a distro" is a whole 'nother question!), which (I've heard) places great emphasis on working right out of the box. Linux is also free (though you can buy boxed sets with docs, support, and so on), and generally efficient enough to make good use of older PCs that won't run well under recent versions of Windows.
A Mac is a reasonable compromise, at least as of when OSX first came out. (Before then, it was aimed mainly at graphics people, like artists and people who put together print newsletters and magazines.) The tooling situation is similar to Unix, except that it doesn't come with quite so many, and usually older versions. It also doesn't require anywhere near as much tweaking as Linux does, because you're running it on exactly the hardware it was designed for. It's even more consistent in its UI behavior and look-and-feel than Windows, and about as easy to understand -- but it's different, so you'll have a lot to "unlearn" if the Windows way of doing things is deeply ingrained in your habits. It also used to be much more stable and secure than Windows, but MS has made great strides in their security and stability, catching up and, depending how you measure things, possibly surpassing OSX's security (not sure about stability). On the other claw, it's a good bit more expensive than a bog-standard Windows box, never mind Linux, but frankly, they're so good that putting together a Windows PC with the same performance will usually cost about just as much, even before factoring in the cost of serious dev tools, OS upgrades (usually dirt-cheap or even free on a Mac), etc. You can get some good bargains on a used Mac, one or two generations old; ask a Mac-using friend to sell (or even give, if you're lucky!) you one of his old castoffs, or take your chances on eBay.
Subscribe to:
Posts (Atom)