Why does specifing a named parameter change the parameters type

I recently ran into this and am curious if I am using named parameters incorrectly or to better understand why they behave this way.

def test_param(b=false)
    if b
        puts 'param is true'
        puts b
        puts b.class
    else
        puts 'param is false'
        puts b
        puts b.class
    end
end

when I test this function in a REPL I am seeing

2.5.3 :213 > test_param(true)
param is true
true
TrueClass
 => nil
2.5.3 :214 > test_param(false)
param is false
false
FalseClass
 => nil
2.5.3 :215 > test_param(b:true)
param is true
{:b=>true}
Hash
 => nil
2.5.3 :216 > test_param(b:false)
param is true
{:b=>false}
Hash
 => nil
2.5.3 :217 >

How come when I use a named parameter the variable data type is changed to a Hash, this seems wrong.

Answer

Unlike in other languages (such as Python), Ruby strictly separates positional arguments and keyword arguments. You can’t provide a keyword parameter as a positional argument when calling a method and vice-versa.

In your case, you have define a single positional parameter in your method (namely b). In your last two examples, you are passing a Hash to it. Note that the curly braces you generally use to define a hash are optional when calling methods and passing a Hash as its last argument. In older Ruby versions, this was used as a convention to allow/pass an optional list of arguments which works similar to keyword arguments.

As a Hash object is always truthy (remember that only false and nil are falsey in Ruby), you are always using the if branch of your conditional and outputting the information you see there.

With that being said, if you want to accept keyword arguments with your method, you have to define it accordingly, e.g.:

def test_param(b: false)
  # ...
end

Note the difference in the parameter definition here. You can learn more about keyword arguments in Ruby from the Ruby language documentation.