Project

lab42_core

0.0
No commit activity in last 3 years
No release in over 3 years
Functional Access to Methods - Memoization - Extending Array, Hash, File, Enumerable with convenience methods, conceptual changes have been moved into lab42_more
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.8
~> 0.10
~> 0.2
~> 2.9
~> 3.5

Runtime

 Project Readme

lab42_core

CI Code Climate Issue Count Test Coverage Coverage Status Gem Version

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.