Ruby Basic Functionnal Tools

Nov 18, 2024

Ruby Basic Functionnal Tools

This is a series of very basic lambdas:

# We base our examples from this object:
user = { name: "Joe", age: "23", created_at: "2023-03-01" }

id = same = ->a { a }
at = ->key, a { a[key] }.curry
fetch = ->key, a { a.fetch(key) }.curry
get = ->key, a { a.send(key) }.curry
set = ->key, value, a { a.send(:key, a) }.curry
size = get.(:size)
count = get.(:count)
uniq = get.(:uniq)
map = ->fn, a { a.map(&fn) }.curry
maybe = ->fn, a { a.nil? ? a : fn.(a) }.curry
default = ->default, a { a.nil? ? default : a }.curry

# validations
is_a = ->klass, a { a.is_a?(klass) ? a : (raise "Invalid class, expected #{klass} was #{a.class} for #{a}") }.curry

# Conversion
to_sym = get.(:to_sym)
to_s = get.(:to_s)
to_i = ->a { a.to_i }
require 'time'
parse_time = ->a { Time.parse(a) }
to_date = get.(:to_date)

require "json"
to_json = ->a { a.to_json }
from_json = ->a { JSON.parse(a) }

# Pretty

pretty_inspect = ->a { a.pretty_inspect }
pretty_json = ->str { JSON.pretty_generate(JSON.parse(str)) }

require "nokogiri"
pretty_xml = ->(xml_str) do
  doc = Nokogiri::XML(xml_str) { |config| config.default_xml.noblanks }
  doc.to_xml(indent: 2)
end

pretty_html = ->str { Nokogiri::HTML(str).tap { |a| a.to_html(indent: 2) } }

# Printers

p_ = ->a { p a }
debug = ->label, a { puts "#{label}: #{a.inspect}"; a }.curry

##############
### hash
##############
map_keys = -> fn, a { a.map{|k, v| [fn.(k), v]}.to_h}.curry
keys_to_sym = map_keys.(to_sym)

# Return ssss the given fields, in the same order as the list of params
only = ->(*keys) { ->h { keys.map { |k| [k, h[k]] }.to_h } }
only.(:name, :age).(user)
#=> {:name=>"martin", :age=>12}

except = ->(*keys) { -> h { h.reject { |k, _| keys.include?(k) }} }
except.(:name, :age).(user)
#=> {:name=>"martin", :age=>12}

# Filters the input hash to include only the keys specified in the `defn` hash.
# For each key, applies the corresponding lambda function from `defn` to the
# value from the input hash. Returns a new hash with the transformed values.
def_h = ->defn, input {
  defn.each_with_object({}) do |(key, fn), result|
    result[key] = fn.(input[key])
  end
}.curry

conv = ->(conversion_hash, target_hash) do
  target_hash.map do |key, value|
    [key, conversion_hash.key?(key) ? conversion_hash[key].(value) : value]
  end.to_h
end.curry

# Example

user_def = def_h.(name: same,
                  created_at: is_a.(String) >> parse_time >> to_date,
                  country: default.("Canada"))
user_def.(user)
# => {:name=>"Joe", :created_at=>"2023-03-01", :country=>"Canada"}