evilsoft / crocks

A collection of well known Algebraic Data Types for your utter enjoyment.
https://crocks.dev
ISC License
1.59k stars 102 forks source link

Crocks don't play well with some inheritance patterns #406

Closed creatorrr closed 5 years ago

creatorrr commented 5 years ago

Describe the bug Method override does not work correctly with "polytype" library.

To Reproduce Steps to reproduce:

const Arrow = require("crocks/Arrow");

class K extends classes(Arrow, Object) {
  compose(...args) {
    console.log("THIS WILL NOT RUN");
    return super.compose(...args);
  }
}

const k = new K([x => x]);
const l = new K([x => x]);
k.compose(l);  // returns an instance of Arrow but no console.log

Expected behavior Overriden method definition should be called as expected

Additional context Relevant issue and discussion on polytype repository: https://github.com/fasttime/Polytype/issues/2

evilsoft commented 5 years ago

Bummer. Sorry about the in-:corn:-venience with that. Crocks is designed specifically to not allow this. The reasoning is to avoid unlawful implementation as a lot of the interplay between the components/functions in this library assume lawful implementations. This also hides/encapsulates the internal values as well.

Out of curiosity, what is the need you have for such extension.

creatorrr commented 5 years ago

Gotcha @evilsoft , that makes sense. Monad laws need to be ensured. That being said, perhaps expose a lower-level API for advanced use cases?

In my case, I wrote an abstraction on top of TransformStreams (more info here) that extends them with Arrow and thus allows them to compose neatly.

Sample (abridged) implementation:

export class Pipe extends classes(TransformStream, Arrow) {
  constructor( fn /* A => B */ ) {
    // Run transform function through underlying arrow
    const transform = async ( chunk, { enqueue } ) => {
      const transformed = this.runWith(chunk);
      enqueue(transformed);
    };

    // Initialize arrow and transform stream
    // ([...argsForTransformStream], [...argsForArrow]) etc
    super([{ transform }], [ transformFn ]);
  }
}

Usage example:

const r1 = new ReadableStream(); // 1,2,3,4,... 10
const w1 = new WritableStream(); // console.log(<value>)

const m10Stream = new Pipe(c => c*10);
const p5Stream = new Pipe(c => c+5);
const p5m10Stream = p5Stream.compose(m10Stream)   // ~= Pipe( c => 10*(c+5))

await r1.pipeThrough(p5m10Stream).pipeTo(w1); // 60,70...150