Keyword vs. Positional Arguments

Last week was our 3rd week at the Flatiron School, and we were each paired up with another person to work on our Ruby projects. Over the course of 2 days, I worked on a small command line application with Emily Kowal (@emottk), which we dubbed the Giphinator, that allows users to query Giphy for gifs via its API and turn them into animated ASCII in their terminals (thank you to @stefania_druga for the looping gif frames idea!).

We were pretty happy with how it turned out:

We were so happy, in fact, that we decided to create a pared-down version and package it as a Ruby Gem called AsciiGif (currently a WIP) so other developers could use it too — an exciting and also terrifying prospect.

We wanted to allow the programmer to easily customize the gif attributes when they used our gem, enabling or disabling color and adjusting width and the number of times the gif prints to the terminal. We didn’t want it to matter in which order they passed in the arguments or how many they wanted to change, and we wanted it to be very clear what attributes they were setting when they did. So we decided to use keyword arguments, rather than positional arguments, in order to facilitate this plan.

What are keyword arguments and positional arguments?

If you’re a novice programmer, you’ve probably seen or used positional arguments:

def lemonade_stand(earned, spent)    
total = earned — spent

if total > 0
puts “You made a profit!”
else
puts “You lost money. :( “
end
end
lemonade_stand(200, 150) # => “You made a profit!”

This is fine but if you were to read code that used the lemonade_stand method, you wouldn’t know what the ‘200’ and ‘150’ stood for. You also couldn’t reverse the way you enter the ‘earned’ and ‘spent’ arguments (i.e. lemonade_stand(200, 150)) as their meaning is determined by their position. This is where keyword arguments are handy:

def lemonade_stand(earned:, spent:)
total = earned — spent
    if total > 0
puts “You made a profit!”
else
puts “You lost money. :( “
end
end
lemonade_stand(earned: 200, spent: 150) # => “You made a profit!”

With keyword arguments, we don’t need to look up the lemonade_stand method to understand what the arguments ‘200’ and ‘150’ mean. You could also now rearrange the order you pass in the arguments without affecting the behavior of the method (ex. lemonade_stand(spent: 150, earned: 200) #=> “You made a profit!”). This is because, when you use keyword arguments, you’re actually defining your method to take in a hash, and the method will use the key, instead of the position, to assign the value.

In our case, our code went from this:

class AsciiGif
...
def initialize(url, loops= 2, color = true, width = 70)
@gif = MiniMagick::Image.open(url)
@loops = loops
@color = color
@width = width
end
    def write_frames
...
end
...
end
## What the programmer would include in his/her program to run our gem
AsciiGif.new(‘http://media.giphy.com/media/26BkN2Uh9wImWDXLa/giphy.gif', 3, false, 100).write_frames

to this:

class AsciiGif
...
def initialize(url:, loops: 2, color: true, width: 70)
@gif = MiniMagick::Image.open(url)
@loops = loops
@color = color
@width = width
end
    def write_frames
...
end
...
end
## What the programmer would include in his/her program to run our gem
AsciiGif.new(url: ‘http://media.giphy.com/media/26BkN2Uh9wImWDXLa/giphy.gif', loops: 3, color: false, width: 100).write_frames

This change was an improvement, since it allows the programmer to very clearly specify the attributes he/she wants without having to enter them in a certain order. As our gem needs a gif url to run, the ‘url’ argument was required, but we set the rest (‘loops’, ‘color’ and ‘width’) to be optional and include default values. Without keyword arguments, if the programmer wanted to change, for example, just the width of the ASCII gif, he/she would need to include the ‘loops’ and ‘color’ arguments before it (as well as the required ‘url’) so the ‘width’ argument would be in the correct position for the method to know what to do with it.

In the end, we also used mass assignment to abstract out the arguments for cleaner and more readable code:

## The lines below output the ASCII gif
attributes = {url: ‘http://media.giphy.com/media/26BkN2Uh9wImWDXLa/giphy.gif', loops: 3, color: false, width: 100}
AsciiGif.new(attributes).write_frames

When you’re learning to program, you’re generally just thinking about your own work and figuring out how to make it function. It’s pretty exciting to get to a place where you start thinking about how others will use or read your code, and become a better programmer because of it.