Blogged by Ujihisa. Standard methods of programming and thoughts including Clojure, Vim, LLVM, Haskell, Ruby and Mathematics written by a Japanese programmer. github/ujihisa

Tuesday, November 23, 2010

Ruby 1.9.2 in Production with Tatsuhiro Ujihisa

(This is the presentation slides set for vancouver ruby meetup)

Before the talk...

  • two questions
  • easy one and normal one

Question 1

An easy Ruby quiz: How to define a method without using "def" keyword? (only with pure Ruby)

def a
  :something
end
p a #=> :something

Sample answer

self.class.module_eval do
  define_method(:a) do
    :something
  end
end
p a

Or

eval 'def a; :something; end'
p a
  • There's no keyword def but string containing 'def'

I think there are a lot more answers

Question 2

A normal Ruby quiz: How to assign a value to a local variable without using assignment operator "="? (only with pure Ruby)

a = :something
p a #=> :something

Sample answer 1

for a in [:something]; end

p a
  • "Local variable" is very special in Ruby
  • Declaring a new local variable is completely static

One more answer which is only on 1.9

Sample answer 2

/(?<a>).*/ =~ ''
eval 'a=:something'

p a

There are a match operator =~ and a string which contains =, but no assignment operator =.

Ruby 1.9.2 in Production with Tatsuhiro Ujihisa

image

image

This talk contains

  1. What is Ruby 1.9.2?
  2. Differences between 1.8..1.9
  3. Differences between 1.9.1..1.9.2
  4. Differences between 1.9.2..1.9.3

The summary of this talk

"Ruby 1.9.2 makes your code cleaner and easier to maintain."

Ruby versions

  • Ruby 1.8.6
    • Mar 2007
  • Ruby 1.8.7
    • May 2008
  • Ruby 1.9.0
    • Dec 2007
  • Ruby 1.9.1
    • Jan 2009
  • Ruby 1.9.2
    • Aug 2010

Ruby versions

  • Ruby 1.8.6
    • Mar 2007
  • Ruby 1.9.0
    • Dec 2007
    • Rails 2.0
  • Ruby 1.8.7
    • May 2008
  • Ruby 1.9.1
    • Jan 2009
  • Ruby 1.9.2
    • Aug 2010
    • Rails 3.0

Background: Ruby versions

  • All of them are officialy stable version
  • But Ruby 1.9 series didn't look like stable

What's new?

  • Which problems did 1.9 solved?
  • How can you write code easily?
  • -> Examples

Problems and solutions

  • Verbose hash notation
  • Difficulty in process handling
  • etc..

Problem 1

About syntax

Problem 1

JSON is cool. Dictionary in Erlang is cool. Hash in Ruby is...?

JavaScript or Erlang:

{a: 1, b: 2, c: 3}

Ruby:

{:a => 1, :b => 2, :c => 3}

1.9.2 new Hash syntax

This solved the problem

Ruby:

{a: 1, b: 2, c: 3}

This issue mattered particularly in..

Haml, the tool everyone is using, needs a lot of hash.

HTML:

<img src="a.jpg" width="64" height="128"></img>

Haml with ruby variables:

%img{:src => icon_path, :width => icon[:width], :height => icon[:height]}

(the below doesn't work)

%img(src=icon_path width=icon[:width] height=icon[:height])

This issue mattered particularly in..

Using 1.9.2 new hash syntax

%img{src: icon_path, width: icon[:width], height: icon[:height]}

Problem 2

  • About builtin methods

Problem 2

system or `` operator lacked some important functionalities.

  • Ruby is a good shell script (Rakefile!),
  • Ruby has some file/process handling methods,
  • But..
    • You couldn't retrieve the output or error of system
    • You could run a command asynchronously with system with "&", but couldn't kill the process directly
    • You couldn't run a command asynchronously with ``

Example

Start a Sinatra app by a Ruby script and kill the app by the script

in shell script:

#!/bin/sh
ruby sinatra_app.rb &
PID=$!
# something...
kill $PID

in ruby?

Bad solution

system 'ruby sinatra_app &'
  • You cannot get the proccess ID, so you cannot kill the process

Better solution

pid = fork do
  exec 'ruby', 'sinatra_app'
end
# something..
Process.kill 'KILL', pid
  • This doesn't work on NetBSD4 or Windows due to fork()

Best solution

pid = spawn 'ruby', 'sinatra_app'
# something...
Process.kill 'KILL', pid
  • :)
  • spawn =~ {fork + exec} or {system + &}
  • portable

New system and spawn spec

system({'A' => 'b'}, 'c', in: input_io, [:out, :err] => :out)
#=> true/false

spawn(...)
#=> Fixnum

Even more..

open3 standard library

  • Open3.capture2e
  • powered by spawn (read the source!)

Problem 3

  • Local variable shadowing (potantial bug!)

    a = :hello
    [1, 2, 3].each do |a|
      p a
    end
    p a #=> 3 (in 1.8)
    

Problem 4

  • "Most libraries didn't work"
  • Yes it was (particularly on 1.9.1)

now?

Active libraries work for sure!

  • nokogiri
  • rails

Problem 5

Installation

  • install ruby, and then install rubygems, ...

rubygems is builtin!

  • rake as well

Problem 6

Ruby is slow

YARV!

fib 31

  • 1.8.7
    • 7.99sec
  • 1.9.2
    • 0.64sec
  • (jruby)
    • 3.00sec

How to make legacy code 1.9 compatible?

Changes between 1.8 and 1.9

  • String isn't Enumerable
  • when clause doesn't accept colon as semicolon

change between 1.9.1 and 1.9.2

  • $: doesn't have the current dir.
    • require_relative is handy (but long...)

Demo: make a gem library 1.9.2 compatible

  • github gem library
  • github create etc..
  • it's very 1.8 even though it's new
    • 0.1.0 was March 3, 2008
    • 0.4.5 was Oct 25, 2010; after 1.9.2 public release!

The summary of this talk (again)

"Ruby 1.9.2 makes your code cleaner and easier to maintain."

end

thanks!

by

  • Tatsuhiro Ujihisa
  • @ujm
  • HootSuite Media, inc
  • Ruby, Haskell, JavaScript, Vim script, etc..

hootsuite hootsuite

appendix

  • JRuby
    • --1.9 option
    • almost compatible with 1.9 except spawn() or etc

Conditional Operator Associativities

In Ruby:

a = true ? :a : true ? :b : :c
p a

Guess the answer! Yes, as you thought, the answer is :a.

In JavaScript:

var a = true ? 'a' : true ? 'b' : 'c';
alert(a);

Guess the answer! Yes, as you thought, the answer is a.

In Vim script:

let a = 1 ? 'a' : 1 ? 'b' : 'c'
echo a

Guess the answer! Yes, as you thought, the answer is a.

In PHP:

$a = true ? 'a' : true ? 'b' : 'c';
print_r($a);

Guess the answer! No, the answer if "b".

$a = true ? 'a' : (true ? 'b' : 'c');
print_r($a);

The code above is the equivalent code to the examples in Ruby, JS and Vim script. It's impossibly difficult to imagine why the specification is so.

Thursday, November 18, 2010

Something like closure for Vim script

Vim script doesn't have lambda as a language feature, but you can fake similar thing with the following tricks.

When you want to pass a procedure with variables into a function in Vim script, how do you achieve it?

Let me explain with other language first, show a well-known function map.

Ruby(*1):

a = 10
p [1, 2, 3].map {|i| i + a }
#=> [11, 12, 13]

Python:

a = 10
print map(lambda x: x + a, [1, 2, 3])
#=> [11, 12, 13]

or more strictly

a = 10
def f(x):
    return x + a
print map(f, [1, 2, 3])
#=> [11, 12, 13]

On the other hand, Vim script doesn't have such fancy feature but have normal function and dictionary function. The former is like just a global function, scoped globally or file. The latter is like a method which has the concept of this of self in other languages.

let obj = {'a': 10}
function! obj.f(x)
  return a:x + self.a
endfunction

echo obj.f(1)
"=> 11

Note that the prefix a: means the identifier is an argument of the function.

This feature is compatible with lexical scope lambda except for the aspect of the anonymity. But it is very inconvenient that you always have to declare which variables the dictionary function will use.

The solution is to use a special variable l:.

function! Main()
  let a = 10
  let obj = copy(l:)
  function! obj.f(x)
    return a:x + self.a
  endfunction

  echo obj.f(1)
endfunction

call Main()

There are three notes for using this trick:

  • l: is only available in a function.
  • it can cause SEGV if you forget writing copy
  • a: as well if you use not only local variables

Acknowledge

thinca and tyru taught me this trick for my problem presentation. thinca is using a kind of this trick in his product.

Footnote

The following code also works on Ruby.

a = 10
f = ->(x) { x + 1 }
p [1, 2, 3].map(&f)

Friday, November 12, 2010

Memoized recursive fibonacci in Python

A slow literal implementation of fibonacci function in Python is like the below:

def fib(n):
    return n if n < 2 else fib(n-2) + fib(n-1)

This is slow but you can make it faster with memoize technique, reducing the order.

__fib_cache = {}
def fib(n):
    if n in __fib_cache:
        return __fib_cache[n]
    else:
        __fib_cache[n] = n if n < 2 else fib(n-2) + fib(n-1)
        return __fib_cache[n]

This is fast, but obviously dirty. Fortunately Python has decorator feature that gives you much better way of writing.

def memoize(f):
    cache = {}
    def decorated_function(*args):
        if args in cache:
            return cache[args]
        else:
            cache[args] = f(*args)
            return cache[args]
    return decorated_function

@memoize
def fib(n):
    return n if n < 2 else fib(n-2) + fib(n-1)

The @memoize decorator is not only for this fib() function but also for general purpose. I referred this page. This is succinct and clean.

But I felt that the definition of memoize(), particularly decorated_function(), was too long. If the else clause consists of single return statement, you may re-write decorated_function() in 1 line.

Takeshi Abe taught me how to make the two lines into one single line.

cache[args] = f(*args)
return cache[args]

is equivalent to

cache.update({args: f(*args)}) or cache[args]

So let here is the final version of fib with memoize:

def memoize(f):
    cache = {}
    return lambda *args: cache[args] if args in cache else cache.update({args: f(*args)}) or cache[args]

@memoize
def fib(n):
    return n if n < 2 else fib(n-2) + fib(n-1)

It's fast, short, succinct, and cool.

Footnote

This approach may be against the Python golden way.

Monday, November 8, 2010

How to make a Unite plugin

Unite

The hottest Vim plugin these days is unite.vim.

:Unite file command opens a unite dialog that you can specify file names with inputting text.

1

You can search not only files but also other resources like buffers you've opened. Unite also allows users to create your own "resource" and to define corresponding actions.

Here I'll make a demonstrative unite plugin unite-colorscheme.

colorscheme

Vim, particularly GUI version of Vim implementations like MacVim, has colorscheme that allows you to change the appearance very much. You may change the colorscheme of the Vim just by :colorscheme ujihisa, but it's not trivial to find which colorschemes you already have. (*1)

2

unite-colorscheme.vim

https://github.com/ujihisa/unite-colorscheme

unite-colorscheme consists of the following two files.

autoload/unite/sources/colorscheme.vim:

let s:unite_source = {
      \ 'name': 'colorscheme',
      \ }

function! s:unite_source.gather_candidates(args, context)
  " [(name, dir)]
  " e.g. [('adaryn', '/Users/ujihisa/.vimbundles/ColorSamplerPack/colors'), ...]
  let colorlist = map(split(globpath(&runtimepath, 'colors/*.vim'), '\n'),
      \'[fnamemodify(v:val, ":t:r"), fnamemodify(v:val, ":h")]')

  return map(colorlist, '{
        \ "word": v:val[0],
        \ "source": "colorscheme",
        \ "kind": "colorscheme",
        \ "action__path": printf("%s/%s.vim", v:val[1], v:val[0]),
        \ "action__directory": v:val[1],
        \ }')
endfunction

function! unite#sources#colorscheme#define()
  return s:unite_source
endfunction

And autoload/unite/kinds/colorscheme.vim:

let s:kind = {
      \ 'name': 'colorscheme',
      \ 'default_action': 'execute',
      \ 'action_table': {},
      \ 'parents': [],
      \ }
let s:kind.action_table.execute = {
      \ 'is_selectable': 1,
      \ }
function! s:kind.action_table.execute.func(candidates)
  if len(a:candidates) != 1
    echo "candidates must be only one"
    return
  endif
  execute "colorscheme" a:candidates[0].word
endfunction

function! unite#kinds#colorscheme#define()
  return s:kind
endfunction

After you installed the plugin, you can search by :Unite colorscheme and set the colorscheme on the Vim just by selecting one colorscheme from the choice.

3

References and footnote

Sunday, November 7, 2010

The Implementation of Enumerable#map of JRuby

See src/org/jruby/RubyEnumerable.java.

@JRubyMethod(name = {"map"}, frame = true, compat = CompatVersion.RUBY1_9)
public static IRubyObject map19(ThreadContext context, IRubyObject self, final Block block) {
    return collectCommon19(context, self, block, "map");
}

This looks like defining the map19 method with declaring it's the map method in Ruby, particularly in 1.9 mode. For some reason this is internally collectCommon19() not mapCommon19().

private static IRubyObject collectCommon19(ThreadContext context, IRubyObject self, final Block block, String methodName) {
    final Ruby runtime = context.getRuntime();
    if (block.isGiven()) {
        final RubyArray result = runtime.newArray();

        callEach19(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                IRubyObject larg = checkArgs(runtime, largs);
                IRubyObject value = block.yield(ctx, larg);
                synchronized (result) {
                    result.append(value);
                }
                return runtime.getNil();
            }
        });
        return result;
    } else {
        return enumeratorize(runtime, self, methodName);
    }
}

This is the definition of collectCommon19. Focus on the then clause of the main if condition block. It's basically calling callEach19() with an instance of BlockCallback, redefining call(). This looks like block-passing style in Ruby.

Focus also on the line just before the callEach19(). The variable result looks like an empty Ruby array for storing values in map process. The definition of the method newArray() is the following.

public RubyArray newArray() {
    return RubyArray.newArray(this);
}

OK. How about RubyArray.newArray().

/** rb_ary_new
 *
 */
public static final RubyArray newArray(final Ruby runtime) {
    return newArray(runtime, ARRAY_DEFAULT_SIZE);
}

Ok, next. Note that the constant ARRAY_DEFAULT_SIZE is an int value.

public static final RubyArray newArray(final Ruby runtime, final int len) {
    RubyArray array = new RubyArray(runtime, len);
    RuntimeHelpers.fillNil(array.values, 0, array.values.length, runtime);
    return array;
}

Looks like [0, ..., 0] with size ARRAY_DEFAULT_SIZE. So you may wonder is it OK to define a longer array if the receiver of map has longer size than ARRAY_DEFAULT_SIZE? I also wonder.

Friday, November 5, 2010

The Problem and Solution of Haml

When I write an HTML file I always write in Haml first and generate HTML by haml command. Haml is so handy that Haml makes us free from writing closing tags like </p>. The problem in Haml is that it is very difficult to type % very often. The symbol is the second most difficult location (*1) in a keyboard.

The solution is to write the following code on you ~/.vim/ftplugin/haml.vim which let : key as % only in Haml file and when the cursor is on the top of the line.

inoremap <buffer><expr> : <SID>on_the_top() ? '%' : ':'
function! s:on_the_top()
  return col('.') == 1 || getline(line('.'))[0:col('.')] =~ '^\s*$'
endfunction

Note that I swapped : and ;. If you didn't swap them, just to swap your keyboard or the code.

  • (*1) The most difficult location key is ^ (shift + 6). If you want to make a programming language which is highly difficult to code, use ^ a lot.

Followers