Блоки, лямбды, замыкания
DESCRIPTION
Как в Руби внутри устроены блоки, лямбды и замыкания.TRANSCRIPT
Лямбды, блоки, замыкания
А давайте посмотрим, что у них внутри
Дмитрий Кириенко
Twitter: @DimKiriyenko
Github: dmitriy-kiriyenko
rb_block_t
???
10.times do str = "Hello world." puts strend
putstring "Hello world."setlocal str, 0putselfgetlocal str, 0send :puts, 1leave
rb_block_t
iseq
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
"Чтобы решить эту проблему мы вводим понятие замыкания как структуры, содержащей лямбда-выражение и окружение, которое будет использовано, когда это выражение будет применено к своим аргументам."Scheme: An Interpreter for Extended Lambda Calculus
Внутренний стек YARV
locals: str_ext
rb_control_frame_t
EP
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
Кадр стека верхнего уровня
Внутренний стек YARV
locals: str_ext
rb_control_frame_t
EP
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
Кадр стека верхнего уровня
rb_block_t
iseq
EP
Внутренний стек YARV
locals: str_ext
rb_control_frame_t
EP
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
Кадр стека Fixnum#times
Внутренний стек YARV
locals: str_ext
rb_control_frame_t
EP
Кадр стека Block#yield
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
locals: str
EP
rb_block_t
iseq
EP
DEFINE_INSNgetlocal(lindex_t idx, rb_num_t level)()(VALUE val){ int i, lev = (int)level; VALUE *ep = GET_EP();
for (i = 0; i < lev; i++) { ep = GET_PREV_EP(ep); } val = *(ep - idx);}
https://github.com/ruby/ruby/blob/trunk/insns.def#L43L67
"Чтобы решить эту проблему мы вводим понятие замыкания как структуры, содержащей лямбда-выражение и окружение, которое будет использовано, когда это выражение будет применено к своим аргументам."Scheme: An Interpreter for Extended Lambda Calculus
typedef struct rb_control_frame_struct { VALUE *pc; VALUE *sp; rb_iseq_t *iseq; VALUE flag; VALUE self; VALUE klass; VALUE *ep; rb_iseq_t *block_iseq; VALUE proc; const rb_method_entry_t *me;
#if VM_DEBUG_BP_CHECK VALUE *bp_check;#endif} rb_control_frame_t;
Control frame Block
typedef struct rb_block_struct { VALUE self; VALUE klass VALUE *ep; rb_iseq_t *iseq; VALUE proc;} rb_block_t;
require 'benchmark'require 'benchmark/ips'
Benchmark.ips do |b| b.report 'while' do sum = 0; i = 0 while i <= 10 sum += i; i += 1 end end
b.report 'block' do sum = 0 (1..10).each do |i| sum += 1 end end
b.report 'reduce' do (1..10).reduce {|acc, i| acc+i} endend
while 6763524 in 5.036406sblock 4267105 in 5.016071sreduce 3606540 in 5.054937s
def get_helloer str_ext = "Hello" lambda do |who| puts "#{str_ext} #{who}!" endend
helloer = get_helloerhelloer.call('there')
В 1930-х годах Алонзо Чёрч ввёл лямбда-нотацию в своём исследовании "Лямбда-исчисление"
def get_helloer str_ext = "Hello" lambda do |who| puts "#{str_ext} #{who}!" endend
helloer = get_helloerhelloer.call('there')
rb_lambda_t
???
Стек
Куча
locals: str_ext rb_control_frame_t
Кадр стека get_helloer
"Hello"
RString
Стек
Куча
locals: str_ext rb_control_frame_t
Кадр стека get_helloer
str_extrb_block_t
iseq
EP
rb_proc_t
envval
is_lambda
rb_env_t
env
Стек
Куча
locals: str_ext rb_control_frame_t
Кадр стека внешнего кода
helloer
rb_proc_t
rb_env_t str_ext
Стек
Куча
locals: str_ext rb_control_frame_t
Кадр стека helloer.call
str_extrb_block_t
iseq
EP
rb_proc_t
envval
is_lambda
rb_env_t
env
argument: whoEP
def get_helloer str_ext = "Hello" res = lambda do |who| puts "#{str_ext} #{who}!" end str_ext = "Goodbye" resend
helloer = get_helloerhelloer.call('there')
def get_helloer str_ext = "Hello" res = lambda do |who| puts "#{str_ext} #{who}!" end str_ext = "Goodbye" resend
helloer = get_helloerhelloer.call('there')
Goodbye there!
Стек
Куча
locals: str_ext rb_control_frame_t
Кадр стека get_helloer
str_ext
rb_env_t
env
EP
501491
def create_counter(start) value = start { inc: ->{value+=1}, dec: ->{value-=1}, get: ->{value} }end
counter = create_counter(500)counter[:inc].callputs counter[:get].call #=> 50110.times { counter[:dec].call }puts counter[:get].call #=> 491
block 19659752 in 5.000587slambda 4574745 in 5.033561s
require 'benchmark/ips'
def do_with_block yieldend
def do_with_lambda(&block) block.callend
Benchmark.ips do |b| b.report 'block' do do_with_block { 1+1 } end b.report 'lambda' do do_with_lambda {1+1} endend
JRuby
_file_
RubyFixnum.times
block_0$RUBY$__file__DynamicScope
DynamicScopestr_ext
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
родитель
Rubinius
Integer.times
код блокаVariableScope
VariableScopestr_ext
str_ext = "Hello"10.times do str = "world." puts "#{str_ext} #{str}"end
код верхнего уровня
родитель