Monthly Archives: August 2011
Ruby, blocks and procs
| 30-Aug-2011 | Posted by Sonia Hamilton under Ruby |
Some notes on ruby, blocks, and procs.
Manually creating blocks
Ruby has three ways of manually creating blocks: Proc.new, lambda, and proc. They have slightly different behaviour, and the behaviour also varies between Ruby 1.8 and 1.9!
- lambda checks that the number of arguments passed matches the number of block parameters
- whereas Proc.new doesn’t check (however the block may raise an error, depending on it’s code)
- and proc behaves like lambda in Ruby 1.8, and like Proc.new in Ruby 1.9. So, avoid using proc!
A bit of code to demonstrate this:
multiplier_l = lambda { |a, b| puts "a * b is: #{a*b}" }
multiplier_p = Proc.new { |a, b| puts "a * b is: #{a*b}" }
multiplier_l.call( 3,4,5 )
ArgumentError: wrong number of arguments (3 for 2)
multiplier_p.call( 3,4,5 )
a * b is: 12
> multiplier_p.call( 1 )
TypeError: nil can't be coerced into Fixnum # in this case, Proc handled one param, but block errored
And now using rvm to switch between Ruby versions:
RUBY_VERSION
=> "1.8.7"
multiplier_p = proc { |a, b| puts "a * b is: #{a*b}" }
multiplier_p.call( 3,4,5 )
ArgumentError: wrong number of arguments (3 for 2)
RUBY_VERSION
=> "1.9.2"
multiplier_p = proc { |a, b| puts "a * b is: #{a*b}" }
multiplier_p.call( 3,4,5 )
a * b is: 12
Scoping
In Ruby 1.8, block parameters can overwrite parameters of the same name in the current scope; in Ruby 1.9 they’re protected.
> hello = "hello"
> def frenchy
> x = "bonjour"
> yield x
> end
> puts hello
hello
> frenchy { |hello| puts hello }
bonjour # as expected
> puts hello
bonjour # ouch! In 1.9 you'd get "hello"
&block
Some of the Rails and Ruby library code define methods with &block as the last parameter to capture an anonymous block.
- anonymous blocks are ignored if they’re not used, and &block is an optional parameter that must appear as the last parameter
- it’s effectively a type-checked parameter – it will only accept an anonymous block or a proc (if proceeded with &)
- the block can be called with call or yield
- you can check if a block was passed using block_given?
- &block is sort of an “invisible parameter” at the end of all methods. But by explicitly using &block, callers get more flexibility when using your method ie they can pass in a proc (perhaps defined elsewhere and used multiple times)
Anonymous blocks are ignored if they’re not used:
> def foo(a)
> puts "a is #{a}"
> end
> foo(1)
a is 1
> foo(1) { puts "2" }
a is 1
Conversely if &block is declared as a parameter, using it is optional:
> def foo(a, &block)
> puts "a is #{a}"
> end
> foo(1)
a is 1
Procs can be called with call; anonymous blocks can be called or yielded to.
> hello = lambda { puts "good bye" } # define a proc for later use
> def foo(a, b, &block)
> puts "a is #{a}"
> b.call # proc with call
> block.call # block with call
> yield # block with yield
> end
> foo(1, hello) { puts "fred" } # with an anonymous block
a is 1
good bye
fred
fred
> foo(1, hello, &hello) # with a proc; notice & syntax
a is 1
good bye
good bye
good bye
You can check if an anonymous block was supplied using block_given?
> def foo(a, &block)
> puts "a is #{a}"
> block.call if block_given?
> yield if block_given?
> end
> foo(1) { puts "mary" }
a is 1
mary
mary
> def foo(a) # or, without defining the block parameter
> puts "a is #{a}"
> yield if block_given? # therefore can only yield not call
> end
&block is sort of an “invisible parameter” at the end of all methods. But by explicitly using &block, callers get more flexibility when using your method:
> def foo(a) # no &block defined in parameters
> puts "a is #{a}"
> yield if block_given?
> end
> foo(1) { puts "john" } # works as expected
a is 1
john
> foo(1, hello)
ArgumentError: wrong number of arguments (2 for 1) # dang! I can't use my super-duper hello proc
Precedence
do .. end has weaker precedence than { }. For example, if foo and bar are both methods:
These are both the same ie the method foo receives two parameters, bar and a block.
foo bar do |s| puts(s) end foo(bar) do |s| puts(s) end
And these are both the same ie foo and bar both receive one parameter; foo the call to bar, and bar a block:
foo bar { |s| puts(s) }
foo( bar { |s| puts(s) } )
Of course the moral of story is not to rely on obscure precedence rules, rather use parentheses whenever something is unclear – as always, in any language.
Closures
Blocks are closures ie they store or carry the value local variables from the the original scope into a different scope. They’re another way of reusing the same logic with slightly different values. For example:
> def build_header( level )
> return lambda { |text| "<#{level}>#{text}</#{level}>" }
> end
> h1 = build_header("h1")
> h1.call("Examples")
<h1>Examples</h1>
> h2 = build_header("h2")
> h2.call("Details")
<h2>Details</h2>
git – make an existing git branch track a remote branch
| 09-Aug-2011 | Posted by Sonia Hamilton under Git |
A script to make an existing git branch track a remote branch. For example when you’ve cloned from somewhere else and now want to track your normal remote.
% cat ~/bin/gittrack
#!/bin/bash
# vim: ai ts=4 sts=4 et sw=4 ft=sh
current_branch=$(git symbolic-ref -q HEAD)
current_branch=${current_branch##refs/heads/}
current_branch=${current_branch:-HEAD}
if [ $current_branch = 'HEAD' ] ; then
echo
echo "On a detached head. Exiting..."
exit 1
fi
remote=${1:-origin}
git branch --set-upstream $current_branch $remote/$current_branch
Of course your global gitconfig should also have autosetupmerge (below), but this script handles situations where you want to setup/change the tracking branch.
[branch]
autosetupmerge = true
Setting up Gitweb on your Ubuntu workstation
| 02-Aug-2011 | Posted by Sonia Hamilton under Git |
If you work in an IT environment, it’s nice to be able to quickly share some of your git repositories from your workstation, without setting up accounts and ssh keys ie using http. Unfortunately, a lot of the posts out there on “how to setup Gitweb on Ubuntu” seem to make a meal of the whole process. I got it going after heading down a few dead-ends; here’s how I did it on Ubuntu 11.04 (Natty).
Basic Setup
Let’s start with a simple git repository:
sudo aptitude install git-all apache2
cd ; git init foo ; cd foo
echo "hello world" > file1.txt
git add . ; git commit -m "initial commit"
You probably only want to share out some of your repositories, not all of ${HOME}! So, do a bare clone of foo repository to /var/www:
cd /var/www
sudo git clone --bare ~/foo foo.git
Next, a little gotcha. You need to enable the post-update hook, so that the required info is generated for the http server. The “gotcha” is that you need to enable the hook then do a push to your server repository, otherwise the server info isn’t updated:
cd ~/foo/.git/hooks
mv post-update.sample post-update
git remote add web /var/www/foo.git
## do a test push
cd ~/foo
echo "test update" >> file1.txt
git add . ; git commit -m "test update"
sudo git push web
Now, if you browse to http://localhost you should see foo.git listed, and your workmates can now easily clone your work:
otherpc$ git clone http://yourpc/foo.git
Gitweb
The next step is to setup gitweb, so your workmates can easily browse your code, search for commits, etc. Gitweb is already installed as part of git-all, or you can do:
sudo aptitude install gitweb
The only change to make is to edit /etc/gitweb.conf to point to /var/www:
sudo vi /etc/gitweb.conf
...
## $projectroot = "/var/cache/git";
$projectroot = "/var/www";
...
And that’s it! Part of the gitweb install on Ubuntu adds in /etc/apache2/conf.d/gitweb, so there’s no other files to edit (unlike what blog1, blog2, or blog3 say).
Browse to http://localhost/gitweb/, and there’s your browse-able, search-able, git repository
But wait, there’s more – automatically push your changes…
As you work, /var/www/foo.git is going to get out-of-date. You could remember to regularly push but that’s boring – automate it:
% cd ~/foo ; cat git.web.push
#!/bin/bash
cd ${HOME}/foo
git remote add web /var/www/foo.git >& /dev/null
sudo git push -f web
…and automate it:
crontab -l
* * * * * ${HOME}/foo/git.web.push
And finally…
Make the layout pretty. Edit ~/foo/.git/description, add an index.html, a custom theme, some javascript, …
Recent Comments