&:block => proc

PERL X RUBY CONF 2018

2018-08-04

들어가기에 앞서

  • 클로저(closure)란? (위키피디아)
    • a closure (also lexical closure or function closure) is a technique for implementing lexically scoped name binding in a language with first-class functions.
    • 일급 객체 함수(first-class functions)의 개념을 이용하여 스코프(scope)에 묶인 변수를 바인딩 하기 위한 일종의 기술이다. 기능상으로, 클로저는 함수를 저장한 레코드(record)이며, 스코프(scope)의 인수(Factor)들은 클로저가 만들어질 때 정의(define)되며, 스코프 내의 영역이 소멸(remove)되었어도 그에 대한 접근(access)은 독립된 복사본인 클로저를 통해 이루어질 수 있다.
  • 키워드: first-class function, lexical scoping, free variable
  • 클로저는 함수 자신의 영역(스코프) 바깥에서 선언한 변수를 불러다 쓴다.

클로저(Closure)

 

  • lambda에서 유래. lambda (anonymous function)
  • 키워드: lexical scoping, free variable
  • 클로저는 함수이며, 자신의 영역(스코프) 바깥에서 선언한 변수를 불러다 쓴다.
function add(x)
   function addX(y)
       return y + x
   return addX

variable add1 = add(1)
variable add5 = add(5)

assert add1(3) = 4
assert add5(3) = 8

lexical scope

rb(main):001:0> parent = "Hello" # 메인 영역
=> "Hello"
irb(main):002:0> 3.times do
irb(main):003:1*   child = "World" # 코드 블록 안 영역
irb(main):004:1>   puts "#{parent}, #{child}!"
irb(main):005:1> end
Hello, World!
Hello, World!
Hello, World!
=> 3
irb(main):006:0> puts "#{child}"  # 하위 코드 블록 안 영역을 접근할 수 없다
NameError: undefined local variable or method `child' for main:Object
	from (irb):6
	from /Users/wagurano/.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):007:0>

free variable

# 자유 변수는 상위 스코프에서 선언한다

irb(main):001:0> double_lambda = lambda do |parent|
# 'parent'가 자유 변수
irb(main):002:1*   lambda do
irb(main):003:2*     child = "World"
irb(main):004:2>     "#{parent}, #{child}!"
irb(main):005:2>   end
irb(main):006:1> end
=> #<Proc:0x007f8ce211f4b0@(irb):1 (lambda)>
irb(main):007:0> double_lambda.call("Hello")
=> #<Proc:0x007f8ce20f5e08@(irb):2 (lambda)>
irb(main):008:0> double_lambda.call("Hello").call()
# 바로 실행할 수 없고, 실행하려면 .call()를 쓴다
=> "Hello, World!"
irb(main):009:0>

eg. data-binding: class

class Counter
  def initialize
    @x = 0
  end

  def print
    "counter = #{@x}"
  end

  def up
    @x += 1
  end

  def down
    @x -= 1
  end
end
irb(main):001:0> c = Counter.new
=> #<Counter:0x007fbc6309ed28 @x=0>
irb(main):002:0> c.up
=> 1
irb(main):003:0> c.up
=> 2
irb(main):004:0> c.print
=> "counter = 2"
irb(main):005:0> c.down
=> 1
irb(main):006:0> c.down
=> 0
irb(main):007:0> c.down
=> -1
Counter = lambda do
  x = 0
  print = lambda { "counter = #{x}" }
  up = lambda { x += 1 }
  down = lambda { x -= 1 }

  { print: print, up: up, down: down }
end
irb(main):001:0> lc = Counter.call
=> {:print=>#<Proc:0x007f8fd39efbc8@/lambda_counter.rb:4 (lambda)>, 
:up=>#<Proc:0x007f8fd39efba0@/lambda_counter.rb:5 (lambda)>, 
:down=>#<Proc:0x007f8fd39efb78@/lambda_counter.rb:6 (lambda)>}
irb(main):002:0> lc[:up].call
=> 1
irb(main):003:0> lc[:up].call
=> 2
irb(main):004:0> lc[:up].call
=> 3
irb(main):005:0> lc[:down].call
=> 2
irb(main):006:0> xlc = Counter.call
=> {:print=>#<Proc:0x007f8fd397c2e0@/lambda_counter.rb:4 (lambda)>, 
:up=>#<Proc:0x007f8fd397c240@/lambda_counter.rb:5 (lambda)>, 
:down=>#<Proc:0x007f8fd397c218@/lambda_counter.rb:6 (lambda)>}
irb(main):007:0> xlc[:print].call
=> "counter = 0"
irb(main):008:0> lc[:print].call
=> "counter = 2"

eg. callbacks

# 함수를 파라미터로 넘긴다
irb(main):001:0> validation = lambda { |x| not x.nil? }
=> #<Proc:0x007ffd9c217798@(irb):1 (lambda)>
irb(main):002:0> validation.call(nil)
=> false
irb(main):003:0> validation.call("hi")
=> true
irb(main):004:0> def post_param(rule)
irb(main):005:1>   lambda do |value|
irb(main):006:2*     rule.call(value)
irb(main):007:2>   end
irb(main):008:1> end
=> :post_param
irb(main):009:0> post_param(validation).call("hi")
=> true
irb(main):010:0> post_param(validation).call(nil)
=> false
require 'ostruct'

class Crawler
  attr_reader :page
  def initialize(page)
    @page = page
  end
  def get
    @page.body
  end
end

class Mailer
  attr_reader :page, :callbacks

  def initialize(page, callbacks)
    @page = page
    @callbacks = callbacks
  end
  def start
    result = page.get
    if result
      callbacks[:on_success].call(result)
    else
      callbacks[:on_error].call
    end
  end
end
irb(main):001:0> nice_weather = 
OpenStruct.new(body: "sunny day")
=> #<OpenStruct body="sunny day">
irb(main):002:0>
Mailer.new(Crawler.new(nice_weather),
   on_success: lambda { |x| 
     puts "Mail #{x} to user@rorlab.org" },
   on_error:   lambda { 
     puts "Mail to error@rorlab.org" }
).start
Mail sunny day to user@rorlab.org
=> nil
irb(main):008:0* error_weather = 
OpenStruct.new(body: nil)
=> #<OpenStruct body=nil>
irb(main):009:0>
Mailer.new(Crawler.new(error_weather),
   on_success: lambda { |x| 
     puts "Mail #{x} to user@rorlab.org" },
   on_error:   lambda {  
     puts "Mail to error@rorlab.org" }
).start
Mail to error@rorlab.org
=> nil

Block: example

# 반복 탐색 iteration
irb(main):001:0> %w(Hello Ruby World).each { |x| puts x }
Hello
Ruby
World
=> ["Hello", "Ruby", "World"]

# 리소스 관리, 전/후처리
irb(main):001:0> File.open('/etc/passwd', 'r') do |f|
irb(main):002:1*   puts f.path
irb(main):003:1>   puts f.ctime
irb(main):004:1>   puts f.size
irb(main):005:1> end
/etc/passwd
2014-03-14 21:55:30 +0900
5253
=> nil

# 객체 생성
@client = Twitter::REST::Client.new do |config|
  config.consumer_key        = "user key"
  config.consumer_secret     = "user secret"
  config.access_token        = "token"
  config.access_token_secret = "token secret"
end

Block: yield, block_given?

irb(main):001:0> def template
irb(main):002:1>   yield
irb(main):003:1> end
=> :template
irb(main):004:0> template
LocalJumpError: no block given (yield)
	from (irb):2:in `template'
	from (irb):4
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):005:0> template { [1,2] }
=> [1, 2]
irb(main):006:0> def template
irb(main):007:1>   yield if block_given?
irb(main):008:1> end
=> :template
irb(main):009:0> template
=> nil
irb(main):010:0> template { [1,2] }
=> [1, 2]

Block: return (X)

irb(main):001:0> def template
irb(main):002:1>   yield
irb(main):003:1> end
=> :template
irb(main):004:0> template { puts "say somehting" }
say somehting
=> nil
irb(main):005:0> template { return 1 + 2 }
LocalJumpError: unexpected return
	from (irb):5:in `block in irb_binding'
	from (irb):2:in `template'
	from (irb):5
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):006:0> template { [1,2] }
=> [1, 2]
irb(main):007:0> template { [1,2] << 3}
=> [1, 2, 3]
irb(main):008:0> template
LocalJumpError: no block given (yield)
	from (irb):2:in `template'
	from (irb):8
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
파라미터 개수가 많으면 에러가 발생하지 않으나 부족하면 에러 발생
block 안에서 사용하는 변수를 파라미터로 넘기지 않으면 nil
irb(main):010:0* def template(x, y)
irb(main):011:1>   yield(x, y)
irb(main):012:1> end
template("Hello","World") { |a, b| "#{a}, #{b}!" }
=> "Hello, World!"
template("Hello") { |a, b| "#{a}, #{b}!" }
ArgumentError: wrong number of arguments (given 1, expected 2)
	from (irb):10:in `template'
template("Hello", "Ruby", "World") { |a, b| "#{a}, #{b}!" }
ArgumentError: wrong number of arguments (given 3, expected 2)
	from (irb):10:in `template'
irb(main):016:0> def template(x, y)
irb(main):017:1>   yield(x, y, "z")
irb(main):018:1> end
template("Hello", "World!") { |a, b| "#{a}, #{b}!" }
=> "Hello, World!!"
irb(main):020:0>
irb(main):021:0* k, v = 1
irb(main):022:0> k # 1
irb(main):023:0> v # nil
irb(main):024:0> def template(x)
irb(main):025:1>   yield(x)
irb(main):026:1> end
irb(main):027:0> template("Hello") { |a, b| "#{a}, #{b}!" }
=> "Hello, !"
block: local variable
# |; local |

rb(main):001:0> x = "parent"
=> "parent"
irb(main):002:0> 3.times { x = "child" }
=> 3
irb(main):003:0> x
=> "child"
irb(main):004:0> x = "parent"
=> "parent"
irb(main):005:0> 3.times { |;x| x  = "child" }
=> 3
irb(main):006:0> x
=> "parent"

Proc


# proc: block of code as an object
# proc은 block과 달리 객체이다. block은 anonymous function 이다

rb(main):001:0> %w(hello ruby world).map(&:upcase)
=> ["HELLO", "RUBY", "WORLD"]
irb(main):002:0> %w(hello ruby world).map{ |x| x.upcase }
=> ["HELLO", "RUBY", "WORLD"]
# proc 실행하는 방법 

rb(main):001:0> x = proc { |a, b| "#{a}, #{b}!" }
=> #<Proc:0x007fb531954a78@(irb):1>
irb(main):002:0> x.call("Hello", "World")
=> "Hello, World!"
irb(main):003:0> x.("Hello", "World")
=> "Hello, World!"
irb(main):004:0> x === ["Hello", "World"]
rb(main):006:0* x["Hello", "World"]
=> "Hello, World!"

Proc

# lamba is a special proc.

irb(main):007:0> y = lambda { |a, b| "#{a}, #{b}!" }
=> #<Proc:0x007fb5318b3e98@(irb):7 (lambda)>
irb(main):008:0> y.call("Hello", "World")
=> "Hello, World!"
irb(main):009:0> y = ->(a,b) { "#{a}, #{b}!" }
=> #<Proc:0x007fb53204f580@(irb):9 (lambda)>
irb(main):010:0> y.call("Hello", "World")
=> "Hello, World!"
irb(main):011:0>

Proc vs Lambda

# proc은 lambda와 달리 파라미터 개수가 맞지 않아도 된다
irb(main):001:0> x = proc { |x, y| "#{x}, #{y}!" }
=> #<Proc:0x007fcb3c1a0018@(irb):1>
irb(main):002:0> y = lambda { |x, y| "#{x}, #{y}!" }
=> #<Proc:0x007fcb3c867a40@(irb):2 (lambda)>
irb(main):003:0> x.call("Hello", "World")
=> "Hello, World!"
irb(main):004:0> y.call("Hello", "World")
=> "Hello, World!"
irb(main):005:0> x.call("Hello")
=> "Hello, !"
irb(main):006:0> y.call("Hello")
ArgumentError: wrong number of arguments (given 1, expected 2)
	from (irb):2:in `block in irb_binding'
	from (irb):6
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):007:0> x.call("Hello", "Ruby", "World")
=> "Hello, Ruby!"
irb(main):008:0> y.call("Hello", "Ruby", "World")
ArgumentError: wrong number of arguments (given 3, expected 2)
	from (irb):2:in `block in irb_binding'
	from (irb):8
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'

Proc vs Lambda

# lambda는 return을 써도 되지만 proc은 ruturn을 쓸 수 없다
irb(main):010:0* k = proc { return }
=> #<Proc:0x007fcb3c047428@(irb):10>
irb(main):011:0> v = lambda { return }
=> #<Proc:0x007fcb3c034918@(irb):11 (lambda)>
irb(main):012:0> k.call
LocalJumpError: unexpected return
	from (irb):10:in `block in irb_binding'
	from (irb):12
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):013:0> v.call
=> nil

Proc: eater egg

# Proc#curry 파라미터를 하나씩 넘겨서, 중간 과정을 저장할 수 있다.
irb(main):001:0> balance_index = lambda { |x, y, z| 3*x - 2*t + z }
=> #<Proc:0x007fbc8a05ce38@(irb):1 (lambda)>
irb(main):002:0> balance_index.call(3,4,5)
NameError: undefined local variable or method `t' for main:Object
	from (irb):1:in `block in irb_binding'
	from (irb):2
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):003:0> balance_index = lambda { |x, y, z| 3*x - 2*y + z }
=> #<Proc:0x007fbc8a126508@(irb):3 (lambda)>
irb(main):004:0> balance_index.call(3,4,5)
=> 6
irb(main):005:0> curry_index = lambda { |x, y, z| 3*x - 2*y + z }.curry
=> #<Proc:0x007fbc8a8bac38 (lambda)>
irb(main):006:0> balance_index.call(3,4,5)
=> 6
irb(main):007:0> curry_index.call(3,4,5)
=> 6
irb(main):008:0> curry_index.call(3).call(4).call(5)
=> 6
irb(main):009:0> balance_index.call(3).call(4).call(5)
ArgumentError: wrong number of arguments (given 1, expected 3)
	from (irb):3:in `block in irb_binding'
	from (irb):9
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):011:0* balance_index_c = lambda { |x| 
lambda { |y| lambda { |z| 3*x - 2*y + z } } }
=> #<Proc:0x007fbc8a902f88@(irb):11 (lambda)>
irb(main):012:0> balance_index.call(3,4,5)
=> 6
irb(main):013:0> balance_index_c.call(3,4,5)
ArgumentError: wrong number of arguments (given 3, expected 1)
	from (irb):11:in `block in irb_binding'
	from (irb):13
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):014:0> balance_index_c.call(3).call(4).call(5)
=> 6

rb(main):015:0>
irb(main):016:0* xy_index = curry_index.call(3).call(4)
=> #<Proc:0x007fbc8a8d0560 (lambda)>
irb(main):017:0> xy_index.call(5)
=> 6
irb(main):018:0> xy_index.call(0)
=> 1
irb(main):019:0> xy_index.call(10)
=> 11

Proc: eater egg

rb(main):020:0> sum_template = lambda do |op, from, to|
irb(main):021:1*   (from..to).inject { |result, x| result + op.call(x) }
irb(main):022:1> end
=> #<Proc:0x007fbc8a04ea40@(irb):20 (lambda)>
irb(main):023:0> sum = sum.call(lambda{|x| x},1,10)
NoMethodError: undefined method `call' for nil:NilClass
	from (irb):23
	from /.rbenv/versions/2.4.1/bin/irb:11:in `<main>'
irb(main):024:0> sum = sum_template.call(lambda{|x| x},1,10)
=> 55
irb(main):025:0> sum = sum_template.call(lambda{|x| x*x},1,10)
=> 385
irb(main):026:0> sum = sum_template.call(lambda{|x| x*x*x},1,10)
=> 3025
irb(main):027:0> sum = sum_template.call(lambda{|x| x*2},1,10)
=> 109

Proc: eater egg

더 알아 볼 것

감사합니다.

Made with Slides.com