Skip to content

Instantly share code, notes, and snippets.

@freakhill
Last active December 17, 2015 06:19
Show Gist options
  • Save freakhill/5564328 to your computer and use it in GitHub Desktop.
Save freakhill/5564328 to your computer and use it in GitHub Desktop.
having fun with "haskell like list comprehension in ruby" - https://gist.github.com/andkerosine/3356675
#!/usr/bin/env ruby-head
class VeryBasicObject < BasicObject
undef :==
undef :!=
undef :!
end
class Captured < VeryBasicObject
def method_missing meth, *args
if meth == :< and ::Enumerator === args[0] # #is_a? not available
::Draw.new(@__name, args[0])
elsif meth == :__ # escape mechanism to avoid globbing, kludge for quotes i guess
::Escaped.new(self)
else
::Message.new self, meth.inspect, args
end
end
end
class Escaped < VeryBasicObject # with an escaped value the following processing is delegated to #__id__
attr_accessor :val
def initialize val
@val = val
end
def method_missing meth, *args
@val.__id__.send(meth, *args)
end
end
class Draw < Struct.new(:var, :from); end
class Message < Captured
attr_accessor :__self, :__method, :__args
def initialize s,m,a
@__self, @__method, @__args = s,m,a
end
end
class PrivateMessage < Captured
attr_accessor :__method, :__args
def initialize m,a
@__method, @__args = m,a
end
end
class Var < Captured
attr_accessor :__name
def initialize n
@__name = n
end
end
class Blank < VeryBasicObject
def method_missing var, *args
if args.empty?
::Var.new var
else # called a method from the surrounding bindings
# var is actually a method name there
::PrivateMessage.new var.inspect, args
end
end
end
def make_proc_from exp, freevars
r = proc do |exp|
case exp
when Var
exp.__name
when Escaped
r[exp.val]
when Message
"(#{r[exp.__self]}).send(#{[exp.__method, *exp.__args.map {|a| r[a]}].join ','})"
when PrivateMessage
"send(#{[exp.__method, *exp.__args.map {|a| r[a]}].join ','})"
when Enumerable
mapped = exp.map { |entry| r[entry] }.join ','
case exp
when Hash
"Hash[[#{mapped}]]"
when Array
"[#{mapped}]"
else
"#{exp.class}.new([#{mapped}])"
end
else
exp.inspect
end
end
eval "proc { |#{freevars.join ','}| #{r[exp]} }"
end
def extract_var_draws_conds_from draws_and_conditions
vars, draws, conditions = [], [], []
draws_and_conditions.each do |dc|
if Draw === dc
vars << dc.var
draws << dc.from.to_a # easy way to get product working...
else
conditions << dc
end
end
[ vars, draws, conditions ]
end
def lk &exp
exp, *draws_and_conditions = Blank.new.instance_eval(&exp) # mainly to isolate #method_missing
vars, draws, conditions = extract_var_draws_conds_from draws_and_conditions
exp, *conditions = [exp, *conditions].map {|ast| make_proc_from ast, vars}
[].tap { |res| draws.shift.product(*draws).each { |draw| res << exp[*draw] if conditions.all? {|c| c[*draw]} } }
end
module RefinedArray
refine Array do # syntactic sugar
def -@
case map(&:class).index Range
when 0 then first.to_enum
when 1 then last.step(last.min.ord - first.ord)
else self.to_enum
end
end
end
end
using ::RefinedArray
z = 7
def inc x
x + 1
end
res = lk{[
[ inc(x.to_i * y), {(x+y).__ => x+y, :z => z}, s % [x], rand(10) ], # expression
x <- [1..5], # draw with syntactic sugar
y < [2,3,4,5].to_enum, # raw draw
x + y > 4, # condition
x < 4, # condition
s <- [">>%s"] # draw
]}
# can't use "#{something}"... can't use if-like syntax etc.
# (x+y).__ will behave like a random integer exclusive to all other similar escaped expressions
# so be careful... it might clash with something else
puts res.inspect
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment