lab42_core
Simple Ruby Core Module Extensions (for more see lab42_more)
** Attention v0.6.0 pushes functional behavior to lab42_function**
Programming Paradigms
Functional
Fn/Fm - Functional Access To Methods
Can be used after require 'lab42/core/fn'
only.
Might be moved into gem lab42_more in the future .
API will remain the same, require will change to require 'lab42_more/fn'
fn like function
Dir.files [APP_ROOT, 'spec', 'support', '**', '*.rb'], &Kernel.fn.require
Dir.files( %w{.. assets ** *.txt} ).sort_by &File.fn.mtime
fm like function/method
%w{ alpha beta gamma delta }.sort_by &String.fm.size
N.B. This only works because the object behind the scenes of Class#fm
knows how to bind
upon call, once it has been transformed by #to_proc
For details see the corresponding Speculations.
Behave or B for Message Sending
This is a more general approach than fn
or fm
as we do not even know the class of the future receiver of the
message.
The subtle difference can be made clear with an example
adder = B( :+ )
# can be used for Integers
adder.(1,41) # --> 42
# or Arrays
adder.(%w/a b/, %w&c d&) #--> %w%a b c d%
Which can of course not be accomplished by Integer.fm.+
For details see the corresponding Speculations.
All Behavior is Composable
The above methods all return instances of Behavior
and Behavor
has a much richer API than Ruby's core callables like Proc
or Method
Memoization and Lazy Attributes
Memoization
is a, slightly forgotten, programming technique protecting against double calcultions.
This became extremly useful with Dynamic Programming .
A much more simle example is allowing us to express and implement the Fibonacci Sequence in the same, some might say naïve, way.
Compared to the explicit memoization as shown in the Wikipedia article, which would read as follows in Ruby
def fibo n, cache=[0, 1]
return cache[n] if cache[n]
cache[n] = fibo( n.pred, cache ) + fibo( n.pred.pred, cache )
end
It is still amazing how the specialized cache initialisation allows us to get rid of the original if statement.
However the general case would read like this
def f n, cache = {}
args_hash = some_hash_fn n # n is all args here
return cache[args_hash] if cache[args_hash]
cache[args_hash] = f_implemenetation( some_fn(n), cache )
end
While a memoization mechanisme built into the language allos to write things like
def_memoized f *args
...
end
def f *args
...
end
memoize :f
# Which can be written as
memoize \
def f *args
...
end
memoized do
def f *args
...
end
end
This gem opts for the memoize
method in the Module
class as this allows for
two different syntaxes
memoize def f ...
end
#
def f ...
end
memoize :f
Lazy Attributes
Are just parameterless memoized methods, excatly the same as let
bindings in RSpec.
lazy_attr( :config ){ YAML.read config_file }
One could say they are just syntactic sugar for
memoize def config
YAML.read config_file
end
One would be correct, but lazy attributes are many (in some of my modules and classes) and have a semantic role often very similar to the example above. They are by nature static while methods like the shortest path or fibonacci are highly dynamic.
For details see the corresponding Speculations.
Gotchas
Do not, I repeat, Do not memoize methods with side effects!
The exception is cached reading as in the example above.
Do not call memoized methods with arguments that cannot be used as Hash keys like e.g. BasicObject instances or other objects not responding to the original hash
method.
Core Extensions
Array
Can be used after require 'lab42/core'
or require 'lab42/core/array'
#flatten_once
[].flatten_once.assert.empty?
[[2, {a: 3}, [4]], {a: 5}].flatten_once.assert ==
[2, {a: 3}, [4], {a: 5}]
For details see the corresponding Speculations.
Dir
Can be used after require 'lab42/core'
or require 'lab42/core/dir'
Dir.files "**/*" do | partial_path, full_path |
end
If only the relative or absolute pathes are needed there are the two variations avaiable:
Dir.abs_files ...
Dir.rel_files ...
For details see the corresponding Speculations.
Enumerable
grep2
enum.grep2 expr # ===>
enum.partition{ |ele| expr === ele }
to_proc
And also Enumerable#to\_proc
as e.g.
counter = (1..3).to_proc
counter.().assert == 1
counter.().assert == 2
counter.().assert == 3
StopIteration.assert.raised? do
counter.()
end
For details see the corresponding Speculations.
File
#expand_local_path
expand_local_path
to get rid of the __FILE__
inside expand_path
.
For details see the corresponding Speculations.
#if_readable
File.if_readable 'some_file' do | file | # openes file as readable
end
#if_writeable
Hash
#only
{a: 42, b: 43}.only :a, :c # ===> {a: 42}
#fetch! (read fetch and set)
a = {a: 42}
a.fetch!(b, 43) # or a.fetch!(b){43}
a == {a: 42, b: 43 } # true
N.B. Unlike Hash#fetch
Hash#fetch!
will not warn you that the block superseeds the default arg if both
are provided (after all there is a !).
For details see the corresponding Speculations.
replace_rec
Recursive Replacement
Original Object untouched of course
a = {a: 42, x: {a: 43}}
b = a.replace_rec( :a, &:succ )
a.assert == {a: 42, x: {a: 43}}
b.assert == {a: 43, x: {a: 44}}
For bulk replacements and how to specify limits, please refer to Speculations.
without
h = {a: 1, b: 2, c: 3}
h.without( :b, :c, :d ).assert == {a: 1}
h.assert == {a: 1, b: 2, c: 3}
Object
Backport of #itself
for versions < 2.2
OpenObject
Immutable Open Objects
x = OpenObject.new a: 42
x.a.assert == 42
x[:a].assert == 42
Immutability
All modifications just return a new instance.
x = OpenObject.new a: 42, b: 43
y = x.update a: 44
y.to_hash.assert == {a: 44, b: 43}
x.a.assert == 42
For details see the corresponding Speculations.
Tools
Console Tools
Can be used only after 'lab42/core/console_tools'.`
N.B. Never use in production code or applications. This code is extremly oriented console monkeypatching core classes massively.
This part is documented in QED Console Tools.
Author
Copyright © 2020,1 Robert Dober robert.dober@gmail.com
LICENSE
This software is licensed under Apache License v2.0. Please refer to LICENSE for details.
Versions strictly less to 0.5.2 of the Software can still be used under the MIT license.