Skip to content

Instantly share code, notes, and snippets.

@NIA
Last active December 10, 2015 16:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NIA/4461055 to your computer and use it in GitHub Desktop.
Save NIA/4461055 to your computer and use it in GitHub Desktop.
Very laconic functional-style working with collections in Scala
// Example 1: join two collections of filenames, find existing ones and load them
(configFiles ++ extraConfigFiles) map { new File(_) } filter { _.canRead } foreach load
// Example 2: invoke `prompt` function in an infinite loop, transform the result with trim method, filter out non-empty and process the others
Iterator continually(prompt) map { _.trim } filterNot { _.isEmpty } foreach { input => ... }
# Example 1 in Ruby: almost the same, but we always have to waste some characters each time for lambda parameter definition
# and we cannot leave out `.` or simply pass `load` as argument to each without wraping it into block
(config_files + extra_config_files).find_all {|s| File.exists? s}.map {|s| File.new s }.each {|f| load(f)}
# The 2nd Scala example is simply inavailable in idiomatic Ruby.
# While we can emulate Scala's Iterator.continually with Enumerator.new (Ruby 1.9 feature),
# the following 'naive' approach will NOT work :(
Enumerator.new{|y| loop{ y << prompt } }.map(&:strip).reject(&:empty?).each{|input| ... }
# It is almost as short as Scala's, but... last block will never be invoked.
# That's because Enumerable#map and Enumerable#reject are not lazy and return arrays instead of Enumerator.
# So the very first map will not return until the Enumerator ends (that is, never)
#
# As the last resort we can patch Enumerator class as suggested there: http://en.wikibooks.org/wiki/Ruby_Programming/Reference/Objects/Enumerable#Lazy_evaluation
class Enumerator
def defer(&blk)
self.class.new do |y|
each do |*input|
blk.call(y, *input)
end
end
end
# And also add some shortcuts
def lazy_map
defer { |out, inp| out.yield yield(inp) }
end
def lazy_reject
defer { |out, inp| out.yield inp unless yield(inp) }
end
def self.continually(&blk)
self.new do |y|
loop { y << blk.call }
end
end
end
# So we can finally write
Enumerator.continually { prompt }.lazy_map(&:strip).lazy_reject(&:empty?).each{|input| ... }
# Exactly like in Scala! But for what cost...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment