Coder Social home page Coder Social logo

phln's Introduction

Build Status Scrutinizer Code Quality


baethon/phln

Set of small utility functions.

Heavily inspired by Ramda.js, adapted for PHP needs.

Installation

composer require baethon/phln

Example usage

use Baethon\Phln\Phln as P;

$aboveMinPoints = P::compose(P::lte(50), P::prop('score'));
$onlyPhp = P::pathEq('language.name', 'PHP');

$topScores = collect($users)
    ->filter(P::both($aboveMinPoints, $onlyPhp));

Note: in the docs P will be used as an alias to Baethon\Phln\Phln.

Currying

Phln methods are loosely curried. A N-ary method will return a function until all arguments are provided.

$foo = P::curryN(2, function ($left, $right) {
    return $left + $right;
});

$foo(1); // returns instance of \Closure
$foo(1, 2); // 3
$foo(1)(2); // 3

Partial application

Partial application is possible with combination of P::partial() and P::__ const. Partial returns a function which accepts arguments which should "fill" gap of missing arguments for callable.

$foos = [1, 2, 3];
$mapFoos = P::partial('\\array_map', [P::__, $foos]);
$mapFoos(function ($f) {
    return $f + 100;
}); // [100, 200, 300]

Function composition

For function composition phln provides pipe() and compose() functions.

$allFoos = P::pipe(
    P::filter(P::lte(5)),
    P::map(P::always('foo'))
);

$firstFoo = P::compose(P::head(), $allFoos);

$allFoos([4, 5, 6]); // ['foo', 'foo']
$firstFoo([4, 5, 6]); // 'foo'

Using methods as references

Some of phln methods accept callable as an argument.

To pass another macro as a reference call it without any arguments.

$collection = [1, 2, 3, 4];
P::reduce(P::sum(), $collection); // 10

Also, you can use P::raw() method wich returns uncurried macro, or pointer to Phln method.

Extending

Baethon\Phln\Phln is macroable. This means that it can be extened using macro() method:

P::macro('foo', function () {
    return 'foo';
});

P::foo(); // 'foo'

Note about objects

The library takes terminology from Ramda. In most cases, it's perfectly fine, until one gets to the concept of object.

Ramda treats objects as dictionaries. In JavaScript, there's only one type which can act as a dictionary. It's ... object.

In PHP things get complicated. It's possible to use arrays and objects as dictionaries. This way Phln has to treat both of those types as an object.

For compatibility reason, all functions which return object will return array.

Testing

./vendor/bin/phpunit

phln's People

Contributors

jdreesen avatar radmen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

phln's Issues

Support string in `collection` functions

Functions from collection namespace should also support "strings" (and treat them as an array of chars).

This should be applied only for functions in which this makes sense:

  • contains()
  • concat()
  • append()
  • prepend()
  • reverse()
  • slice()
  • chunk()?
  • check against Ramda docs if there're other functions which require update
  • contains()
  • concat()
  • append()
  • prepend()
  • reverse()
  • slice()
  • chunk()
  • head()
  • init()
  • last()
  • tail()
  • Update docs (if required)
  • create:bundle
  • create:docs

Add `collection\partition()`

From Ramda docs:

Takes a predicate and a list or other Filterable object and returns the pair of filterable objects of the same type of elements which do and do not satisfy, the predicate, respectively. Filterable objects include plain objects or any object that has a filter method such as Array.

Function/partialRight

Similar to partial() yet arguments are applied starting from right side of the array.

Introduce `RegExp` object

There's little confusion with those functions:

  • match()
  • matchAll()
  • replace()
  • replaceAll()
  • split()
  • splitRegexp()

Some of them require regexp, some of them require plain string. In fact all those functions could be simplified to only three:

  • match()
  • replace()
  • split()

This can be done by introducting RegExp object which represents.. RegExp. It should be simply a ValueObject initialized in same fashion as in JS: new RegExp($regex, $modifiers).

It should be able to handle global (g) modifier. For convenience it should be possible to create RegExp instance via regexp() function helper.

This should allow to:

  • determine whether match or replace should perform action for all matches
  • determine whether split should do it by regexp or plain text delimiter

Examples

match('foo', 'foo bar foo'); // 'foo'
match(regexp('/foo/'), 'foo bar foo'); // 'foo'
match(regexp('/foo/', 'g'), 'foo bar foo'); // ['foo', 'foo']

replace('foo', 'foo bar foo'); // ' bar foo'
replace(regexp('/foo/'), 'foo bar foo'); // ' bar foo'
replace(regexp('/foo/', 'g'), 'foo bar foo'); // ' bar '

TODO

  • RegExp class foundation
  • automatic delimiter wrapping of pattern (e.g new RegExp('^foo') -> /^foo/)
  • add regexp() function helper
  • add support for RegExp in match()
  • add support for RegExp in replace()

String/test

Add test() function which validates string with given regex.

Add `collection\groupBy()`

From Ramda docs:

Splits a list into sub-lists stored in an object, based on the result of calling a String-returning function on each element, and grouping the results according to values returned.

Dispatches to the groupBy method of the second argument, if present.

Version 2: draft of proposed changes

For some time I was wondering about library structure.

In terms of DX I think that phln\Phln class is easy to use. Yet, in terms of "internal" structure and further development it's quite complicated.

In most cases each function brings to scope two functions (curried and lamda fn) and a const. Later this has to be "compiled" so that Phln knows how to handle stuff. There's also problem with PHPUnit tests which work basically on a hack.

After some time of usage I realized that I'm not using in projects any particular functions outside of Phln context. This made me thinking and I think I've found better way to structure things.

Below is "draft" of mine idea. I'm leaving it here so that I won't loose it.


Namespace

First I thought that phln is good namespace name. Mostly because it was meant to introduce functions, not Phln class. Yet, the latter is mostly used and this naming convention seems odd.

I'd like to change it to Baethon\Phln.

Make Phln macroable

Phln should be extended by macros.

bundle.php should be responsible for require_once all of defined macros.

  • 💡add curriedMacro($name, $arity, $fn) method as a shorthand for Phln::macro($name, Phln::curryN($arity, $fn))

Structure of function files

Get rid of functions declarations. Register Phln macro.

<?php
// math/sum.php

use Baethon\Phln\Phln;

if (Phln::hasMacro('sum')) {
    return;
}

Phln::macro('sum', Phln::curryN(2, function ($a, $b) {
    return $a + $b;
});

It should be possible to require_once those macros which are required:

<?php
// collection/head.php

use Baethon\Phln\Phln;

if (Phln::hasMacro('head')) {
    return;
}

require_once (__DIR__."/../fn/compose.php");
require_once (__DIR__."/../logic/cond.php");
// ...

Phln::macro('head', Phln::curryN(1, function ($collection) {
    // ...
});

Function "references"

Phln contains a list of constants which are meant to be used as "references" of functions.
I realized that this can be avoided just by calling method without any arguments.

Since functions are curried, when called without arguments, they will return a closure (aka "reference") to the function. This should be enough to use in different contexts.

<?php

use Baethon\Phln\Phln;

$pipe = Phln::pipe([
    Phln::prop('values'),
    Phln::apply(Phln::sum()),
]);

This means that some of the "aliases" (T, F, otherwise) should by default return a function which, when called, returns given value.

<?php

$t = Phln::T();

$t(); // true

This rule applies to every function with arity = 0.


Impact

  • remove the possibility to import individual functions. It will be possible only in the context of the package
  • given the proposed structure it won't be possible to use PHPDocs as the source of generated documentation. It will be required to update generated MD docs manually.
  • there won't be any need to have create:bundle and create:docs - they need to be removed
  • there will be no function constants (aka function references) - everything has to be done via Baethon\Phln\Phln

Generators support

An example comes from kapolos/pramda: basically, all array-related functions are using generators.

It's quite a nice idea. Worth implementing.

Replace ƛfunctions with something more suitable

This prefix is horrible. It has to be removed.

TODO:

  • find better name for ƛand() / ƛor()
    IF NOT:
    • make both() / either() polymorphic - when arguments are primitives (booleans) they should return primitive value
    • remove ƛand() / ƛor()
  • Create bundle
  • Update docs

Refactor `reject`

Right now it looks like this function is not curried properly.

This function should be split to reject() (curried) and 𝑓reject(), as in template.

Function/invoker

Add invoker() function which will calls given method with selected arguments.

Remove `nil` in favour of `null`

I've thought that nil is a required thing to define undefined values.

Turns out that using null will be enough. Current functions do not accept null values, probably the future ones also won't do that.

TODO:

  • remove nil const
  • update curry(), curryN() to eliminate null values and curry function as long as all arguments are non-null
  • update type hints
  • create bundle
  • update docs

Unify variadics

There's some inconsistency with usage of variadics.

Some functions (eg. apply, partial) require to pass array of an arguments where others (curry, curryN, pipe) accept variadics.

This behaviour should be unified.

Possible solutions:

  1. use variadics everywhere
  2. use array argument
  3. use array in cases when passing arguments to function (apply, partial, curry); for rest (pipe, compose) use variadics

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.