Skip to content

Latest commit

 

History

History
575 lines (440 loc) · 20.5 KB

File metadata and controls

575 lines (440 loc) · 20.5 KB

Project notes


Todo [16/48]

Fix Applicative sequence() types

Semigroup and Monoid in terms of Semigroup

Implement MonoidInstance in terms of Semigroup. Haskell’s interface to making a Monoid is to define types and override defaults of either mempty or mconcat. mappend is defined in terms of Semigroup. I think `template M of SemigroupInstance` would be what we are looking for here so that mappend can have a default implementation of returning the associative function from the Semigroup.

It might be easier for users to implement just Monoid sometimes?

Curry?

List comprehension?

How we handle newtype ?

Do we need to ?

Explore use cases

Haskell is good with constructing hexagons. What aspects of haskell do that? Are they included? Just providing a base to work with the given structures (ie extending Maybe or reusing it) The adapters Implementations of the interfaces + test helpers for the laws

Write the above in the docs.

Improve types in partial / composition

Also rethink and add any aliases/names. Partial might need an object instead of returning an anonymous function.

Some things can be implemented lazy

By returning a function and with the help of partial application.

Would mocking of functions be part of this?

Cheers to Olle from phpc

Could this be implemented:

fmap :: (a -> b) -> (f a -> f b)

So that we return a function and once that function is called with arg: f a then we call fmap on it? I suppose so. Does it work now?

function partialFmap(callable $f) {
    return partial (fmap(...), $f)
}

Should be it. Leaving this here still as it might be useful?

Implement MoreAccurateFunctor

https://stackoverflow.com/questions/53854853/why-is-there-a-distinction-between-co-and-contravariant-functors-in-haskell-but Also called Categorical Functor but seems like this generalisation can be very useful. https://hackage.haskell.org/package/categories-1.0.7/docs/Control-Categorical-Functor.html

Add missing law helpers for testing

Add tests & docs for Wrapper and clean up older tries

Definition Dependency diagram for composition & typeclasses

I think after looking around at typeclasses and different definitions of composition, I need a diagram. Maybe it is actually a category!

Something like this https://wiki.haskell.org/index.php?title=Foldable_and_Traversable#What_do_these_classes_all_mean?_A_brief_tour:

Inline multiple functions composition

And inline them all in a single Closure rather than one on top of the other. Would that be possible?

Notations can do multiple composition but not with inline as per above.

Consider generalising the wrappers

Consider analysing the mapping between Category -> Haskell | PHP or Haskell -> PHP

So there are things that are functional and are written elsewhere. These things have a different mapping from Concepts to Implementations of those concepts. For example a type class might be mapped with an interface and each instance as a class implementing that interface. Alternatively might be mapped from an abstract class or a simple function. As programmers, we do that daily, we map from one abstract concept of our understanding into the structures defined in the language, with varying success and precision / abstraction as we see fit.

Kleisli categories mapping might be interesting? Or we are looking to something more abstract? Ie. Morphisms in Kleisli are the embelished version, separating an implementation from the abstraction. Bridge pattern rings a bell here.

Trying to better explain, in a Kleisli category one will define two things when defining the composition function of the category. How the non-embelished functions compose and how you `mappend`. So the question here is, is this all we need to generalise the mapping from Category to some implementation of it (ie Haskell / PHP)? The non-embelished functions will provide the mapping to the implementation and the `mappend` operation will provide the way to add/append/combine those together (sequentially?). Essentially giving better semantics, or better API to work with these things (as “implementation” details have been now abstracted).

Could touch on this, at least?

Writer?

Reader?

Continuation?

Abstract notations

Like do notation for example. In PHP (or anywhere) we could abstract a notation to a function if we project the elements/parts as arguments of the function and then define how they compose. For example imagine the below types:

runNotation :: [a] -> b

Takes a list of “a”s and runs them and produces a b (which also could be again an “a” but just to signify the difference).

So the `dn` function we have now is like:

dn in php: (MonadInstance $ma, MonadInstance|\Closure/*|Composition*/ …$fs) enforcing the first argument to be a MonadInstance, fine, lets add this restriction for a second

runNotation :: a’ -> [a] -> b

Where a’ is a “restricted” part of what a is.

dn :: a’ -> [a] -> b dn :: MonadInstance -> [MonadInstance|Closure] -> MonadInstance

a “then” (or notation for (>>)) would look like this:

notationForThen :: a’ -> [a] -> b notationForThen :: MonadInstance -> [MonadInstance] -> MonadInstance

So we could really generalise here and write even: notationForThen :: [MonadInstance] -> MonadInstance

However in general for notations, we could project the elements into the argument list and decide how these compose:

someNotation(
    $element1,
    $element2,
    $element3
);

So we need 2 things:

  1. what the type of the elements is
  2. how to compose those
  3. (optionally) how you fold the list of elements (for more flexibility in expression) - ie foldr or foldl

So with CategoryInstance we could define notations as long as we define those two above. Restrictions can go in the helper function.

So do-notation could be

doNotationDefinition :: [elementsType] -> result doNotationDefinition :: [m a | (a -> m b)] -> m b

A notation for function composition (right-to-left, if I am correct), ie:

// g after f
// g . f
// = \x -> g ( f x )
//
functionComposition(
    $g = fn ($x) => $x + 3,
    $f = fn ($x) => $x * 3,
);

Would be in types:

functionCompositionNotation :: [(a -> b)|(b -> c)] -> (a -> c)

Could use orders to make sure they come correctly instead of “any” from the sum type above, although this would still work but a list of [(a->b)] would just -> (a -> a) basically making all type variables the same (because in this case they actually are), but just in the case of function composition maybe?

Nevertheless, it is still true:

functionCompositionNotation :: [(a -> b)|(b -> c)] -> (a -> c) functionCompositionNotation xs = foldTheListSomehow (myCompositionFunction) xs myCompositionFunction = (.) foldTheListSomehow = <define the fold from right to left, i guess, or generally how we iterate and consume>

So could make it a type-class thing.

This makes a lot of more sense now: https://www.haskellforall.com/2012/08/the-category-design-pattern.html in the “lens” of making notations. It is just that haskell allows infix functions, in PHP we do not have the ability to define infix functions ourselves, however we can “project” this infix notation to an list of arguments, as if we were composing them with the infix operator. Might actually seem trivial, now.

Additionally you could say also apply “Foldable” after “Category”, but probably is better to “inline” it for performance.

Ie: Some notation would be implemented by folding a list (right to left) and composing according to the composition function assigned to the Category of those elements.

Add Kleisli category

Now that we have notations, so that we can re-implement dn on top of it.

Consider consts can be used to simplify passing functions around

Consts or defines. See https://github.com/ihor/NSPL/blob/master/nspl/f.php#L135

Improve memoize implementation

In Haskell, you might memoize with recursion. See https://wiki.haskell.org/index.php?title=Memoization It’s worth looking into this.

Fix phpstan errors

Test coverage & tests improvements

Add more type class laws

Many instances do not have helper assertions for their laws.

Refactor or remove the FunctorAdapter now that we have typeclass instances

It seems FunctorAdapter now have less use, maybe can move it to a function that returns an anonymous class? These “adapters” are not so convenient as the typeclass instance registration though but they are probably faster unless you keep combining them (magic method __call will keep delegating further in this hierarchy of inheritance).

It is a use case decision. For one-off it might be still worth it. Nevertheless anyone can implement their own FunctorInstance class and be done.

I am just not 100% against it yet.

Phpstan extension for linear types

Phpstan extension for construction of types according to some property

Example in Haskell

data State = Unbound | Bound | Listening | …
data Socket (s :: State)

bind :: Socket Unbound -> IO ()
listen :: Socket Bound -> IO ()

I think phpstan documentation implements a “kind”.

Essentially implementing “typestates”.

Either could use a union instead of A,B template types?

Add a generic implementation of array_unique

PHP’s array_unique does a (string) $a == (string) $b comparison, so it cannot be used if the elements cannot be casted to string.

----- DONE ----

Separate data and control functors

https://www.tweag.io/blog/2020-01-16-data-vs-control/

And also here we could elaborate on the Data.IO and Control.Monad.IO so that the latter implements in terms of monadic operations. What about the first though?

This has taken place, more or less.

Implement these on Composition

-- | @since base-2.01
instance Applicative ((->) r) where
    pure = const
    (<*>) f g x = f x (g x)
    liftA2 q f g x = q (f x) (g x)

-- | @since base-2.01
instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

Implemented with Arr.

(ergonomics) Refine Syntax helpers ?

Example for : x -: f = f x

SyntaxHelper::start(3)
   ->apply(f(...));

    // or "stack" based

StackSyntaxHelper::push(3)
    ->push(f(...))
    ->apply();

StackSyntaxHelper::push(3, 4, 6)->apply('max');
StackSyntaxHelper::push(3, 4, 6, 'max')->applyFirstCallable();

First two do not look much of a helper really.

Implemented in terms of notations.

Consider single object per typeclass instance or why not

Lets take for example Maybe. Maybe has 1 type variable `a`. If Maybe is implemented in a single class there might be a type `a` that cannot be an instance of Show but is Functor for example. Implementing it in a single class may be quite restrictive as the type variable `a` will have to implement all requirements coming from the PHP interfaces (that try to map a typeclass) ? Is this right?

Implemented the MethodContainer.

Implement ReverseComposition or swap names

I think “Composition” became natural because of partial application. Implemented rl / lr

Fix IO

What needs to be fixed here?? Will never remember.

Implement Category

Calling this Done and if there is an issue will solve it there.

Implement Op

Calling this Done and if there is an issue will solve it there.

Change callable to \Closure

TimWolla and Edorian helped my understand that callable is scope specific and how to benchmark a little better.

It makes sense to change all type declarations from callable to \Closure for performance reasons, even if it forces the user to use \Closure::fromCallable it probably is faster as that creates a more performant \Closure.

There’s more for performance but this is a little start.

Monoid

Make `fmap` accept F|callable

Now it is fmap(Composition|callable $f, F $g): F But as the argument $g has to be a functor, we can accept a callable if we wrap it in composition (that will also apply partial).

Contravariant could be used for the wrappers

See https://stackoverflow.com/questions/38034077/what-is-a-contravariant-functor specially https://stackoverflow.com/a/56150133

instance Contravariant (Op a) where
  contramap :: (b' -> b) -> (Op a b -> Op a b')
  contramap f g = Op (getOp g . f)

contramap :: (b -> a) -> f a -> f b

The (b -> a) in Contravariant defines the “medium”, the way we are going to produce something that can consume b out of something that was consuming a.

Generalise the callable so that can pass Composition too

Partial function application depends on signatures.

How do we reflect types from Composition ?

I think this actually is easy to solve if we return the ReflectionFunction instance from Composition. After handling the infinite loop.

Partial application

This seems such a central piece.

Roadmap to v1.0 [0/5]

Fix all static analysis errors

Complete tests

Complete assertions and laws

Make phpunit tests run with executionOrder=random

Probably have left some stuff from the containers.

Docs

Other notes

Trying the pipe operator

Try flipping __invoke with `fmap` or `bind` at will (some dynamic front object).

Or contramap once implemented, something along the lines of:

cFlippedWithFmap ('abs') |> $maybeInt // 

Could then mean

fmap ('abs', $maybeInt);

// coming from calling `fmap()` in the $maybeInt object, cFlippedWithFmap would have to implement
// that in its __invoke

Play with contramap and the pipe operator

Random Insights

Implementing fmap with fmap function and a composition

When implementing fmap for an instance, we can always fmap() over a Composition to have a central place where we can control things like partial application or other `utilities`. It is however slightly more expensive as it wraps things again and again and does a few extra calls.

Random ideas

Is there a any tricky/hacky way to use variable variables?

Ie for creating something with “referential transparency” See https://wiki.haskell.org/index.php?title=Referential_transparency

Or list comprehension? Some snippets that could be inspirational.

$some = 123456;
$var1 = $var2 = 'some';

foreach (['var1', 'var2'] as $k => $outer)
    foreach (['new value', 'next value'] as $$outer)
        funcWithTwoArgs($var1, $var2);

print "\n" . $var1;
print "\n" . $var2;
function funcWithTwoArgs($var1, $var2) {
    print_r ( compact('var1', 'var2') );
}

function listComp(iterable $variables, iterable $values) {
    foreach ($variables as $k => $outer)
        foreach ($values as $$outer)
            funcWithTwoArgs($var1, $var2);
}

listComp(['var1', 'var2'], ['one', 'two']);

Could avoid func(…) notation

Could avoid func(…) notation if every function (ie in Composition::__invoke) we check how many arguments we have been given and either return a function or call the function. Isn’t that essentially what we already do with partial ? Yes.. So this code:

function f($a, $b) { return $a + $b };

$f = c('f');
$partiallyApplied = $f(); // does not need to be c('f')(...) because `partial` returns a function now
$partiallyAppliedA = $f(123) // again partial returns a function but with one arg

On inlining

I think there is inlining you can do on function composition but there is also inlining you can do on types, since types can be represented by functions, right?

Random links

https://en.wikipedia.org/wiki/Typestate_analysis https://www.tweag.io/blog/tags/linear-types/

Journeys [1/3]

Safety journey

It is a little vague how “safety” is defined. There is type safety or safe from side-effects.

Concentrating on the second, side-effects safety, one can define a function that wraps a given function call in a try-catch block. Generalising a little bit, one can define a “control” structure for the same.

Generalising even further, a type class for things that safely do something. This is a bit reverse from the idea of Maybe and Either on their face value.

Generalising to a different direction, if that even makes sense, one could say that anything that can be called can be called safely or unsafely. Therefore a control structure could capture all cases, allowing to opt-in for safety? More or less the implementation of try-catch is doing that.

One could also go sideways, defining towards the direction of Catch, providing a way to not only safely run something but also handle one or multiple catch cases.

There must be some intuition from Haskell and/or monads that do the same, or partial functions. See Control.Exception.

However, the level of generalisation is not something you can choose for all cases. Different generalisation is chosen depending on different solutions or implementations. One cannot say X implementation is all you need and captures all your possible scenarios and use-cases. This seems like an obvious observation now but maybe put some things into perspective.

The meaning of this last paragraphs concludes this journey, meaning that all different ways may or may not be interesting for some use-case or scenario therefore different approaches are still interesting to be implemented in this lib and it is up to the user to decide as they are the one controlling the level of generalisation in their “safety” choices that is desired each time.

Partial application improvement journey

On partial application currently there is a single function that sorts out the situation, however this seems to be hard to deal with the types. Would a more elaborate implementation, one that analyses the terms help with the types or even provide more features?

Previous notes from the todo item that turned into a journey:

Write tests for the actual usage, see that it works passing one argument at a time. Somewhere had to pass both at one call. Would an object help rather than a closure for the types?

I think fixing partial in respect to the types will help a lot with the static analysis errors.

What do I miss if..

Instead of Either a b we use the union types: a|b (sum types) And instead of Tuple<a,b> we use a&b (intersection types). This way we have some native support from PHP for product and coproduct (product and sum types). One thing I miss is that I cannot really make “new types” and define functions on them? Like I have to go write a function for every combination I want to use, as we do not have generics. But we did not have generics anyway. However let’s consider Maybe.

Maybe is Nothing | Just x, therefore a sum type, therefore coproduct. So I could write null|int (for Maybe Int) and null|object (for Maybe object) okay, and this also transforms to the more convenient nullable type ?int or ?object. Fine.

So let’s say I want to make a functor on that “Maybe” type. We need to define fmap.

fmap :: (a -> b) -> f a -> f b

Taking arbitrary types for a (int) and b (bool) we could have:

function fmap (\Closure(int):bool $f, ?int $a): ?bool {
    return $a === null ? null : $f ($a);
}

Let’s generalise wherever we still can and use type annotations

 /**
  * @template A1
  * @template A2
  * @template B1
  * @template B2
  * @param \Closure(A1|A2):B1|B2 $f
  * @param A1|A2 $a
  * @return B1|B2
  */
function fmap (\Closure $f, mixed $a): mixed {
    return $a === null ? null : $f ($a);
}

Now that is nice and good for something that only has two constructors, what if it had 3 ? Also notice the implementation cannot handle anything but “null” as one of the two types, as we do not know the types!

Feedback

Discord

adrian.2688 — i’d stick those utility methods into the associated classes, as static methods ditch functions.php

also when your traits refer to a method it expects the using class to have, you should define that method on the trait as abstract more broadly, as you work with this i’d suggest thinking more about how the ideas could apply / be more “naturally” implemented in php, rather than just trying to port them directly. i don’t do haskell, but some of these concepts seem like they don’t accomplish much as php tools

Crell — Functions are fine, but they does seem a bit over engineered. And Left/Right eithers s**k. 🙂 Explicit Result eithers are better DX. I have my own composition centric library I’ve been using, and am now trying again to get into core.

https://github.com/Crell/fp