proc_obj_without_block_not_possible = Proc.new #=> Proc class
proc_obj_without_block_not_possible = proc.new # Kernel#proc
proc_obj_with_block = Proc.new { }
proc_obj_with_block = proc.new { }
proc_obj_without_args = Proc.new do 'Hello'
end
proc_obj_by_proc_class = Proc.new do |str|
  "Hello #{str}"
end
## Now will see the way of calling a proc object of Proc class

p proc_obj_by_proc_class
p proc_obj_by_proc_class.call
p proc_obj_by_proc_class.call('Foo')
p proc_obj_by_proc_class.call('Foo', 'Bar')
p proc_obj_by_proc_class.('Foo')
p proc_obj_by_proc_class['Foo']
proc_obj_by_kernel_proc = proc do |str|
  "Hello #{str}"
end
## Now will see the way of calling a proc object of kernel module

p proc_obj_by_kernel_proc
p proc_obj_by_kernel_proc.call
p proc_obj_by_kernel_proc.call('Foo')
p proc_obj_by_kernel_proc.('Foo')
p proc_obj_by_kernel_proc['Foo']
p proc_obj_by_kernel_proc.call('Foo', 'Bar')

Now About Lambda

proc_obj_by_lambda_without_block = lambda { }
proc_obj_by_lambda_without_block = -> { }
proc_obj_by_lambda = lambda do |str|
  "Hello #{str}"
end

# p proc_obj_by_lambda
# p proc_obj_by_lambda.call
p proc_obj_by_lambda.call('Foo')
p proc_obj_by_lambda.('Foo')
p proc_obj_by_lambda['Foo']
# p proc_obj_by_lambda.call('Foo', 'Bar')

Essentially, Procs are anonymous methods (or nameless functions) containing code. They can be placed inside a variable (we can invoke them based on requirement) and passed around like any other object.

These anonymous method could be create by Proc.new, lambda, and blocks (invoked by the yield keyword).

# wants a proc, a lambda, AND a block
def three_ways(proc, lambda, &block)
  proc.call
  lambda.call
  yield # like block.call
  puts "#{proc.inspect} #{lambda.inspect} #{block.inspect}"
end

anonymous = Proc.new { puts "I'm a Proc for sure." }
nameless  = lambda { puts "But what about me?" }

three_ways(anonymous, nameless) do
  puts "I'm a block, but could it be???"
end
 #=> I'm a Proc for sure.
 #=> But what about me?
 #=> I'm a block, but could it be???
 #=> #<Proc:0x00089d64> #<Proc:0x00089c74> #<Proc:0x00089b34>

First difference between proc and lambda

def return_from_proc
  prok = proc { p 'I am in proc block'}
  prok.call()
  return 'I am proc return value'
end


def return_from_lambda
  lamda = lambda { p 'I am in lambda block'}
  lamda.call()
  return 'I am lambda return value'
end

return_from_proc
return_from_lambda

Second difference between proc and lambda

prok = proc { |a,b| "Hello #{a}===and==#{b}"}
prok.call
prok.call('Ajay')
prok.call('Ajay', 'Vijay')
prok.call('Ajay', 'Vijay', 'Sanjay')

stabby_lambda = ->(a,b) { "Hello #{a}===and==#{b}"}
stabby_lambda.call
stabby_lambda.call('Ajay')
stabby_lambda.call('Ajay', 'Vijay')
stabby_lambda.call('Ajay', 'Vijay', 'Sanjay')