The 'then' Ruby Keyword - What is it?
Have you heard of the then
keyword?
This allows you to curry.
# this is a contrived example
# experiment with it in an IRB console.
# we want to:
# take a string
# add convert it to an int
# add 1 to that
# and then cube it
# returning the result
def operation_1(a)
a_int = (a.to_i)
sum = a_int + 1
cube = sum ** 3
return cube
end
# is it pleasant to read?
puts "operation_1 is: #{operation_1("3")}"
# => operation_1 is: 64
# notice how the input of one function
# is an input into another?
# we can write the same thing like this:
def operation_2_dense(a)
(a.to_i + 1) ** 3
end
puts "operation_2_dense is: #{operation_2_dense("3")}"
# => operation_1 is: 64
# but is it readable?
# you can also curry it like so:
def curry(a)
a.then { |i| i.to_i }
.then { |i| i + 1 }
.then { |i| i ** 3 }
end
puts "curry is #{curry("3")}"
# => curry is 64
def add_one(i)
i + 1
end
def cube(i)
i ** 3
end
def curry_2(a)
a.then { |i| i.to_i }
.then { |i| add_one(i) }
.then { |i| cube(i) }
end
puts "curry_2 is #{curry_2("3")}"
# => curry_2 is 64
# if you want something more readable.
# and if you like, you could put add_one and cube
# into a mixin. The implementation details are hidden
# and you can cleanly / elegantly express
# what you want.
Which do you prefer?
Extracted in the Wild:
I peaked into 37 Signal’s Writebook offering, from their Once suite of products.
### first_run.rb
User.create!(user_params.merge(role: :administrator))
.tap do |user|
DemoContent.create_manual(user)
end
They’re using tap
. We can improve upon the above (IMO), by using then
(which is more naturally expressive than ‘tap’) and by also creating a helper method:
User.create_admin(user_params).then do |user|
DemoContent.create_manual(user)
end
Or take the pagy
codebase:
def next_cursor
hash = keyset_attributes_from(@records.last)
json = @vars[:jsonify_keyset_attributes]&.(hash) || hash.to_json
B64.urlsafe_encode(json)
end
# or would you prefer it like this?
def next_cursor
keyset_attributes_from(@records.last)
.then { |attributes| @vars[:jsonify_keyset_attributes]&.(attributes) || attributes.to_json }
.then { |json| B64.urlsafe_encode(json) }
end
It all depends on what style you prefer.
Ruby is an expressive language. You can say the same thing a million different ways.
If you like being expressive, then you can craft a script that is akin to art - a beautiful poem - for the human reader, but something which a computer can crunch, and to produce an output that someone else finds valuable.
Here is the full ruby file if you’re interested.