How can I create a Proc to checking whether the elements of an array are nil or not?

660 Views Asked by At

I'm trying to solve this assignment:

Create a Proc that will check whether a given array of elements are nil or not. Use .nil? to evaluate elements. Print 'true' if the elements are nil and 'false' if the element has a [truthy] value.

This is my attempt:

def check_nil(array1)
  p = Proc.new {|x| x.nil?}
  res= p.call(array1)
  if res == true
    return "true"  
  else  
    return "false"  
  end 
end
   
array1 = [{},nil,[]]
result = check_nil(array1)
puts "#{result}"

Here actually, the output should be "true" but this code gives "false" as output. Can someone explain the reason?

4

There are 4 best solutions below

0
On

[nil,nil,nil].all? nil No proc.

0
On

Your method currently checks whether the given object is nil:

check_nill(nil) #=> "true"
check_nill([])  #=> "false"
check_nill(123) #=> "false"

Note that this object can be anything, so your argument name array1 is somehow misleading for this use case. (you might also want to rename the method to check_nil, i.e. with one l)

Here actually, the output should be "true" [...]

I assume that you actually want to check whether any of the array's elements meets a condition, i.e. if the array contains nil. You can use any? with a block for this. From within the block, you can call your proc:

prc = Proc.new { |x| x.nil? }
[{}, nil, []].any? { |element| prc.call(element) }
#=> true

Which can be shortened by converting the proc to a block:

prc = Proc.new { |x| x.nil? }
[{}, nil, []].any?(&prc)
#=> true

You can even get rid of your custom proc and use Symbol#to_proc instead:

[{}, nil, []].any?(&:nil?)
#=> true

There's also all? which checks whether all elements meet the condition, e.g.:

[{}, nil, []].all?(&:nil?)
#=> false

[nil, nil, nil].all?(&:nil?)
#=> true
1
On

If I understand the question correctly, you are trying to check whether all the array elements are nil/blank/false or not. If there is any non-nil value present in the array, you want to return true, else false. Here is how you can determine this:

array1 = [{}, nil, []]
array1.all?(&:blank?) # this returns true

array2 = [{}, nil, [], 1]
array1.all?(&:blank?) # this returns false
0
On

Truthiness in Ruby

Other issues aside, {} and [] respond to #nil? but are actually truthy since they are neither nil nor false.

[].nil?
#=> false

{}.nil?
#=> false

In Ruby, the only falsey values are nil and false. However, Array and Hash objects do respond to #empty? with a Boolean, which isn't really the same thing as nil.

[].empty?
#=> true

{}.empty?
#=> true

[1].empty?
#=> false

{foo: 1}.empty?
#=> false

Why Use Proc Objects in the First Place

The real point of Proc objects and lambas in Ruby is to create closures that carry their execution context with them even when the original binding has gone out of scope. They aren't really first-class functions, and methods are generally more flexible, so you should generally use a Proc or lambda primarily to pass around a closure that carries variables outside their normal scope.

The Quick Answer: Using a Proc to Return a "Truth Table" Hash

The simplest way to return a "truth table" from a Proc that reports on each element will look something like this:

prc = proc { |array| array.zip(array.map(&:nil?)).to_h }

prc.call [1, 2, nil, 4]
#=> {1=>false, 2=>false, nil=>true, 4=>false}

Getting Fancy with Calling Semantics

In Ruby 3.1.2, you could also create a Proc that can handle an Array or multiple arguments, and return a Hash telling you which of the values are nil.

prc = proc { |*array| array.zip(array.map(&:nil?)).to_h }

prc [1, 2, nil, 4]
#=> {1=>false, 2=>false, nil=>true, 4=>false}

prc[1, 2, nil, 4]
#=> {1=>false, 2=>false, nil=>true, 4=>false}

prc.call(*[1, 2, nil, 4])
#=> {1=>false, 2=>false, nil=>true, 4=>false}

prc.(1, 2, nil, 4)
#=> {1=>false, 2=>false, nil=>true, 4=>false}

Note that the use of variadic calling semantics can be a bit tricky, though, as Proc#[] and Proc#call will treat the collected Array as a single argument unless you splat the Array during the call. For example:

prc = proc { |*array| array.zip(array.map(&:nil?)).to_h }

prc.call [1, 2, nil, 4]
#=> {[1, 2, nil, 4]=>false}

Use a Lambda to Enforce Arity

If you want to force the user to pass only a single argument, and maybe check if it's actually an Array while you're at it, then it's probably better to use a lambda. Lambdas enforce arity, and if you don't trust that you're being passed an Array or a duck-typed object that can #respond_to? typical Array methods, you could do something like this:

check_elements_for_nil = lambda do |arg|
  raise TypeError, "expected Array, got #{arg.class}" unless
    arg.kind_of? Array
  arg.zip(arg.map(&:nil?)).to_h
end

# @raises [ArgumentError] wrong number of arguments
check_elements_for_nil.call(1, 2, 3)

# @raises [TypeError] expected Array, got Hash
check_elements_for_nil.call({foo: 1, bar: 2, baz: 3})

# @return [Hash] truth table for nil values
check_elements_for_nil.call([1, 2, nil, 4])
#=> {1=>false, 2=>false, nil=>true, 4=>false}

More Fun with Stabby Lambdas and Closures

You could make it shorter, too, using a one-liner with a stabby lambda and chaining the input to the lambda definition itself:

->(a) {a.zip(a.map(&:nil?)).to_h}.([1, 2, nil, 4])
#=> {1=>false, 2=>false, nil=>true, 4=>false}

but the practical benefits of this are pretty much zero since you could do the same thing without the lambda. However, since procs and lambdas are closures, it is potentially useful to use something like the following, which demonstrates how to use a lambda with no explicitly-passed arguments:

# method with a hard-coded local variable
#
# @return [lambda] that closes over local variables
def create_a_scope_gate_and_return_a_lambda
  array = [1, 2, nil, 4]
  ->{array.zip(array.map(&:nil?)).to_h}
end

# @note This method doesn't have access to the array
#   defined as a local variable in the method above.
#   It's like magic!
# @return [Hash] but only because we know what calling
#   the lambda will return; pragmatically it could be 
#   whatever calling +proc_or_lambda+ returns
def call_closure_with_out_of_scope_var(proc_or_lambda)
  proc_or_lambda.call
end

a_lambda = create_a_scope_gate_and_return_a_lambda
call_closure_with_out_of_scope_var(a_lambda)
#=> {1=>false, 2=>false, nil=>true, 4=>false}

This works without any explicit arguments to the lambda at all, because the local array variable is in scope when the lambda is defined, and the lambda carries that value with it out through the scope gate of the defining method, and in through the scope gate of a second method.