Posted by matijs
26/09/2024 at 16h54
I think a good process is something like this:
- Add the standard library gems to the dependencies so the warnings go away. For gem projects I think it’s fine to put them in the development dependencies because the published gems don’t need these dependencies right now
- Figure out which gems load the standard library gems
- File bug reports for those gems to make them add the standard library gems as dependencies
- Wait for those gems to be updated and then update the dependencies on those gems in my project
- Remove the standard library gems from the dependencies again
To be honest, I think I have only ever gotten as far as point 2.
Tags
maintenance, programming, ruby
no comments
no trackbacks
Posted by matijs
20/09/2018 at 09h03
I happened upon
this comment.
But more important, it just doesn’t work sensibly to explain why many people
decline modest bets (e.g. that someone not on the brink of starvation would
decline a 50/50 lose $100 vs gain $110) bet.
You can look at this bet in two ways. The first is the single bet. Then, you
can think about how bad you feel about losing $100, versus how good you feel
about gaining $110.
The second way is as a repeated bet. And I think this is how people do think
about it: If I bet yesterday, why not bet today? Or, I lost yesterday, I need
to bet again today to ‘make up for it’.
Emotions aside, the reason given that the bet is a good one, is that in the
long run the better will come out ahead. But how long is the long run?
Let’s fire up irb
. (I’ve reformatted the lines a bit to fit in an article layout.)
>> def bet; rand < 0.5 ? -100 : 110; end
>> count = 0; sum = 0; while sum < 1; count+= 1; sum += bet; end; [count, sum]
=> [81, 90] # Oops!
>> min = 0; count = 0; sum = 0; \
> while sum < 1; count+= 1; sum += bet; min = sum if sum < min; end; \
> [count, min, sum]
=> [35, -530, 70] # OOPS!
Maybe you can spare $100, but can you spare $530? (Not to mention the fact that
many people can’t spare $100.).
Or even $1340, leading to a $50 win after 136 bets?
=> [136, -1340, 50]
What are the chances of a repeated bet ruining you before you gain anything at all?
>> def compound_bet; min = 0; count = 0; sum = 0; \
> while sum < 1; count+= 1; sum += bet; min = sum if sum < min; end; \
> [count, min, sum]; end
>> def killer_bet(threshold); count, min, sum = compound_bet; min < -threshold; end
>> def killer_chance(threshold); 100000.times.select { killer_bet(threshold) }.count / 1000.0; end
>> killer_chance(500) #=> 8.017
>> killer_chance(1000) #=> 3.532
A betting scheme with a 3.5% chance of losing $1000 doesn’t sound so good…
(The commenter goes on to point to an article that actually doesn’t make the
claim that the given debt is a ‘modest debt’, and seems far more interesting
than that.)
Tags
programming
no comments
no trackbacks
Posted by matijs
25/07/2018 at 06h59
I started reading this, and came upon this line:
Many people claim to know how SemVer works, but have never read the specification.
And I thought: Yes! This is exactly the problem. Everyone talks about SemVer, but no-one reads the specification, so the discussions don’t make sense. Finally, someone is going to Make Things Clear!
…
And then I read this:
Note: Stop trying to justify your refactoring with the “public but internal” argument. If the language spec says it’s public, it’s public. Your intentions have nothing to do with it.
What!? This person complains about people not reading the specifications, and then proceeds to contradict the very first article of the SemVer specification? Here it is (highlight mine):
Software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it should be precise and comprehensive.
Whether the language spec says it’s public has little to do with it.
Now, there’s a discussion going on on Hacker News about this article, and clearly I’m not the only one bothered by the quote above, but the commenters are focused on whether languages allow you to control what part of your API is exposed, rather than what the SemVer spec actually says.
No-one understands SemVer.
Tags
annoyance, programming, SemVer
no comments
no trackbacks
Posted by matijs
10/04/2016 at 09h21
This is an anti-pattern that has bitten me several times.
Suppose you have an object hierarchy, with a superclass Animal, and several
subclasses, Worm, Snake, Dog, Centipede. The superclass defines the abstract
concept move
, which is realized in the subclasses in different ways, i.e., by
slithering or walking. Suppose that due to other considerations, it makes no
sense to derive Worm and Snake from a SlitheringAnimal, nor Dog and Centipede
from a WalkingAnimal. Yet, the implementation of Worm#move
and Snake#move
have a lot in common, as do Dog#move
and Centipede#move
.
One way to solve this is to provide methods walk
and slither
in the
superclass that can be used by the subclasses that need them. Because it makes
no sense for all animals be able to walk and slither, these methods would need
to be accessible only to subclasses (e.g., private in Ruby).
Thus, the superclass provides a toolbox of methods that can only be used by its
subclasses to mix and match as they see fit: a Private Toolbox.
This may seem an attractive course of action, but in my experience, this
becomes a terrible mess in practice.
Let’s examine what is wrong with this in more detail. I see four concrete
problems:
- It is not always clear at the point of method definition what a method’s
purpose is.
- Each subclass carries with it the baggage of extra private methods that
neither it nor its subclasses actually use.
- The superclass’ interface is effectively extended to its non-public methods,
- New subclasses may need to share methods that are not available in the
superclass.
The Animal superclass shouldn’t be responsible for the ability to slither and
to move. If we need more modes, we may not always be able to add them to the
superclass.
We could extract the modes of movement into separate helper classes, but in
Ruby, it is more natural to create a module. Thus, there would be modules
Walker and Slitherer, each included by the relevant subclasses of Animal. These
modules could either define move
directly, or define walk
and slither
.
Because the methods added in the latter case would actually makes sense for the
including classes, there is less need to make them private: Once could make a
instance of Dog walk, either by calling move
, or by calling walk
directly.
This solves all four of Private Toolbox’ problems:
- The module names reveal the purpose of the defined methods.
- Subclasses that do not need a particular module’s methods do not include it.
- The implementor of Animal is free to change its private methods.
- If a new mode of transportation is needed, no changes to Animal are needed.
Instead, a new module can be created that provides the relevant functionality.
Tags
patterns, programming, ruby
no comments
no trackbacks
Posted by matijs
28/07/2015 at 10h52
Because of a pull request I was working on, I had cause to benchmark activesupport’s #try
. Here’s the code:
require 'benchmark'
require 'active_support/core_ext/object/try'
class Bar
def foo
end
end
class Foo
end
bar = Bar.new
foo = Foo.new
n = 1000000
Benchmark.bmbm(15) do |x|
x.report(’straight’) { n.times { bar.foo } }
x.report(’try - success’) { n.times { bar.try(:foo) } }
x.report(’try - failure’) { n.times { foo.try(:foo) } }
x.report(’try on nil’) { n.times { nil.try(:foo) } }
end
Here is a sample run:
Rehearsal ---------------------------------------------------
straight 0.150000 0.000000 0.150000 ( 0.147271)
try - success 0.760000 0.000000 0.760000 ( 0.762529)
try - failure 0.410000 0.000000 0.410000 ( 0.413914)
try on nil 0.210000 0.000000 0.210000 ( 0.207706)
------------------------------------------ total: 1.530000sec
user system total real
straight 0.140000 0.000000 0.140000 ( 0.143235)
try - success 0.740000 0.000000 0.740000 ( 0.742058)
try - failure 0.380000 0.000000 0.380000 ( 0.379819)
try on nil 0.210000 0.000000 0.210000 ( 0.207489)
Obviously, calling the method directly is much faster. I often see #try
used defensively, without any reason warrented by the logic of the application. This makes the code harder to follow, and now this benchmark shows that this kind of cargo-culting can actually harm performance of the application in the long run.
Some more odd things stand out:
- Succesful
#try
is slower than failed try plus a straight call. This is because #try
actually does some checks and then calls #try!
which does one of the checks all over again.
- Calling
#try
on nil
is slower than calling a nearly identical empty method on foo
. I don’t really have an explanation for this, but it may have something to do with the fact that nil
is a special built-in class that may have different logic for method lookup.
Bottom line: #try
is pretty slow because it needs to do a lot of checking before actually calling the tried method. Try to avoid it if possible.
Tags
benchmark, programming, ruby
no comments
no trackbacks
Posted by matijs
19/02/2012 at 12h46
My list of all-time-favorite books for programmers. I’m not saying everyone should read these, but each of these had an important impact on my growth as a programmer. These are not necessarily in chronological order, by the way.
First, books that are mostly independent of your choice of programming language:
Design Patterns and Refactoring are not books to be read cover to cover, since they they devote quite a large part of their volume to catalogueing. The other two definitely are.
The following books are each really about a particular language. They’re well written, but it’s hard to separate the impact of the books from the impact of the languages.
-
Programming Perl (a.k.a. The Camel Book). This book made me grasp object-oriented programming for the first time by breaking it down to a very basic level. I did most of my learning Perl from this book.
-
Programming Ruby (a.k.a. The Pickaxe Book). I learned Ruby from the free online edition. It got me hooked.
Tags
books, life, programming
no comments
no trackbacks