Skip to content

Instantly share code, notes, and snippets.

@bkerley
Last active April 8, 2018 02:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bkerley/754df43c98e116e82003 to your computer and use it in GitHub Desktop.
Save bkerley/754df43c98e116e82003 to your computer and use it in GitHub Desktop.
elixir-style function chaining that is also awful!
require 'pp'
class Object
def mutate
Mutagen.new(self)
end
end
class Mutagen < BasicObject
def initialize(collection)
@collection = collection
end
def method_missing(method_name, *args, &block)
return const_method_missing(method_name) if probably_constant(method_name)
instance_method_missing(method_name, *args, &block)
end
private
def const_method_missing(method_name)
const_callee = ::Kernel.const_get method_name
::ConstMutation.new const_callee, @collection
end
def instance_method_missing(method_name, *args, &block)
callee = @collection
::SelfMutation.new(@collection, method_name, args, block).invoke
end
def probably_constant(method_name)
method_name_start = method_name.to_s.chars.first
is_uppercase = method_name_start.upcase == method_name_start
end
end
class SelfMutation < BasicObject
def initialize(collection, method_name, args, block)
@collection = collection
@method_name = method_name
@args = args
@block = block
end
def invoke
::Mutagen.new @collection.send(@method_name, *@args, &@block)
end
end
class ConstMutation < BasicObject
def initialize(callee, collection)
@callee = callee
@collection = collection
end
def method_missing(method_name, *args, &block)
returned = @callee.send method_name, @collection, *args, &block
::Mutagen.new returned
end
end
module Manipulator
def self.filter(collection, block)
collection.select &block
end
end
module Printer
def self.pretty_print(collection)
pp collection
end
end
pp @collection = (1..20).to_a
@collection.mutate
.Manipulator.filter( ->(x) { x.even? } )
.shuffle
.take(5)
.Printer.pretty_print
"steve".mutate
.upcase
.chars
.to_a
.Manipulator.filter( ->(x) { x.ord > 'F'.ord})
.join
.Printer.pretty_print
# visualize:
# collection
# |> Manipulator.filter(fn(x) -> is_even(x) end)
# |> Printer.pretty_print
@akitaonrails
Copy link

Would you mind if I borrow your const_method_missing idea and add it to my chainable_methods gem? Or if you want to send a PR with that I'd be thrilled :-)

@andriytyurnikov
Copy link

@akitaonrails, @bkerley, have you seen this trick with >-> 'operator' https://github.com/txus/kleisli
;)

# check out this more verbose, but explicit style
CM("hello http:///www.google.com world")
  |-> text { URI.extract(text) }
      .first
  |-> uri { URI.parse(uri) }
  |-> url { open(url) }
      .read
  |-> html { HTML.parse(html) }
      .css("h1")
      .first
      .text
      .strip

It feels like current solution with . is not very friendly to method's like Array(42), URI("http://foo.com/"), etc...
I wonder if single argument functions (with unnamed arguments) are too simple?
Good DSLs are pretty often chainable out of the box without pipelines -- but 'connecting' others sure sounds very interesting.

Thank you for sharing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment