-
-
Notifications
You must be signed in to change notification settings - Fork 394
Conversation
For the Sorry don't have enough time over the next week to flesh out the idea. |
Thanks, @xaviershay. I pushed a branch with that implementation (brute-force-match-array) -- see 78bdd38. Unfortunately, it's ridiculously slow, because it's an O(N!) algorithm...which is is pretty much the worse time complexity you can get. Check out this growth (from def7863):
Compared to what we had before:
A minute to compare two arrays of 10 elements each is clearly not going to fly. I think I'm going to go back and see if I can get the stable marriage algorithm to work, unless you have any better ideas. On a side note, even what we had before is slower than I would like: 6 ms to evaluate one matcher expression is going to be the primary bottle neck in a true unit test (most of my unit tests average ~2ms or so). I think that's simply the nature of having to do all the comparisons for |
Actually, I realized I just read that wrong: I said it was taking 6 ms to evaluate the matcher expression but it looks like it's actually .6 ms...which isn't so bad for the awesome flexibility |
And, FWIW, here's an implementation of the stable marriage algorithm in ruby: |
I expected slow, but not that slow. |
Yep, it was definitely slower than I expected. Then again, factorial is the fastest growing function I can think of... |
So I've made a bit of progress on the matching algorithm. I eventually decided the stable marriage thing was a dead end (I couldn't find a way to model our problem in terms of the requirements of the stable marriage algorithm), but I've come up with an alternate approach that I think will perform quite well:
I feel pretty good about this algorithm, for a few reasons:
I've got some of the plumbing for this solution already in place in a local branch. The part I'm struggling a bit with is the brute force iteration: we need to have a way to try fixing a pair to see where that goes, w/o destroying what we have so far, so that we can later pop the brute force stack and try a different pairing. I think that doing this easily basically comes down to having good abstractions, but I'm having a hard time naming things because it's already so abstract. (Plus, I've had a lot of distractions every time I've tried to work on this). @xaviershay -- what do you think of that solution? Would you want to pair on it some time? |
Algorithm sounds good. I'm not going to be able to pair on it before the new year, have quite a bit of travel and family stuff over the next week. |
OK, I've implemented the algorithm discussed above. It's working, and it's much faster than the first brute force approach that @xaviershay and I tried. However, it still has some perf issues -- not based on how big the arrays are but based on how many duplicates/multi-matches there are. Also, I'm not super happy with the algorithm implementation -- it could definitely use some refactoring. |
Include it in `failure_message` and `failure_message_when_negated` so the matchers don't have to override all 3 if they are just customization the description part of it.
We can leverage the instance state of the matcher object.
{:a => 1, :b => 2} ...reads much better than: {:a=>1, :b=>2}
- Leverage instance variable state. - Leverage the description/failure messages provided by BaseMatcher. - Remove ternaries.
@@ -39,6 +41,98 @@ def or(matcher) | |||
def ===(value) | |||
matches?(value) | |||
end | |||
|
|||
private unless defined?(::YARD) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haven't seen this before ... because YARD doesn't include private methods? Does it include protected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➜ rspec-mocks git:(master) yard doc --help | grep private
--private Show private methods. (default hides private)
--no-private Hide objects with @private tag
Private methods are hidden in the docs by default. This makes a lot of sense: private methods are generally not part of your public API.
Here I wanted these methods to be private (as they are only ever intended to be called from within a matcher on self
) but since this is a mixin intended for users to use in their custom matchers, I wanted these methods doc'd. This definitely felt like a hack.
Do you think it's better to just make the methods public? I'm on the fence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making them ruby public is fine - we've docced them as api private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, they are doc'd as api public. The problem here is that there are two notions of visibility:
- Ruby's notion: private vs protected vs public. This affects whether you can call a method with an explicit receiver or not.
- API notion: private vs public: Things docs as api private are subject to change in minor or patch releases with no deprecation warnings or notices. They are "off limits" for end users to use directly if they don't want their code breaking when they upgrade. API public means we are committed to not breaking it in a minor or patch release as per SemVer.
Here we've got methods we'd like to be ruby private but API public.
I'm not too worried about this. In my experience most people don't think about explicitly checking types of their objects, and would expect this to pass with a string-like object. (Though it is a bit weird in ruby, string is an array-like object.)
Is there existing code in either library that soft depends on the other like this? If yes, we should keep that direction the same. If no, does pushing some part of the code down into |
rspec-mocks defines a couple matchers (e.g.
Maybe -- I'll think more about that. |
I'm ok with a soft-dependency, just as long as we don't have one both ways :) |
- The `RSpec::Matchers` API was getting very large with all the aliases, which makes it harder to keep all in your head. - It's better to start with fewer and then add more in future releases as there is demand. Once they are there, we can't remove them from a future minor/patch release because of server. - `RSpec::Matchers.alias_matcher` is provided so users can define new aliases.
It was going to be very hard to document how this worked, and what phrasing forms are supported. Instead, we simply allow users to define their own aliases using `alias_matcher`.
We want to keep the solution with the fewest unmatched items, not the most unmatched items.
@xaviershay -- I think the perf is still unsatisfactory -- see 09734a7. I'm going to try to see if I can optimize it a bit. |
There are still perf problems :(.
When recursing during the brute force depth-first search, pass along only the indeterminate indexes, so that it runs on a smaller subproblem. For this to work, we had to switch to using a hash, so that it can handle index gaps. This provides mixed results: it makes some things faster, some things slower. I think it's an improvement overall.
It's surprising how much difference this makes!
This is much, much faster than the full matching algorithm, and in common cases (e.g. arrays of numbers or strings) works just fine. See the benchmarks for how much of a difference this makes!
@xaviershay -- I found some fairly easy optimizations to make that make the common cases blazing fast, and make the slower cases about 2x faster. It's still slower than I would like it for those slower cases, but I'm at a point where I think I'm OK with it for now and can move on to something else. Let me know what you think of my optimizations -- I may have missed something or there may be further low-hanging fruit that these optimizations suggest. |
LGTM |
Support composable matchers
So we can ship 3 now right? :P |
Depends on your definition of "now" :). |
I'm going to work on the formatter stuff more this weekend, when thats done then we can ship RSpec 3 :P |
Well, we can ship beta2. Then I plan to do my core/mocks/expectations code audit and see what else (if anything) we want before RC and final. |
Aye, mostly was just reminding peeps more stuff existed ;) |
This is still a bit WIP but it's much further along than the last WIP PR and I wanted to open the PR and start the code review process. I applied the things discussed with @xaviershay and @JonRowe from #388.
Still TODO:
RSpec::Matchers
module that discusses the matcher protocol so that it mentions how the composing works (e.g. the use of===
).match
matcher to be composable.match_array
matcher to get the pending spec to pass. Edit: I think a stable matching algorithm may work here.match(nested_structure)
)The last item in particular is stumping me -- I need help on that one! See the pending spec I left in
match_array_spec.rb
for what I'm talking about.Even though I have a lot of TODOs left, feedback is welcome whenever anyone wants to give it. I'll be sure to comment here when I everything I'm planning to do is complete.
/cc @xaviershay @JonRowe @soulcutter @samphippen @alindeman
Closes #280.