Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fromReaderEither and fromReaderTaskEither #33

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

konker
Copy link
Contributor

@konker konker commented Sep 16, 2022

The use-case is for jest tests.
E.g.

      await P.pipe(
        model,
        someReaderTaskEither(),
        P.Task_.map(
          P.Either_.fold(fail, (result) => {
            expect(stub1.calls().length).toBe(1);
            expect(result).toStrictEqual({ Contents: [{ Key: 'bar/test-file.txt' }] });
          })
        ),
        (invoke) => invoke()
      );

Or possibly:

      const result = await P.pipe(
        model,
        unit.s3ListObjects({
          Type: FileType.Directory,
          Bucket: 'foobucket',
          Path: 'bar/' as DirectoryPath,
          FullPath: 'bar/',
        }),
        fromTaskEither
      );
      expect(stub1.calls().length).toBe(1);
      expect(result).toStrictEqual({ Contents: [{ Key: 'bar/test-file.txt' }] });

can become:

      const result = await fromReaderTaskEither(model, someReaderTaskEither());

      expect(stub1.calls().length).toBe(1);
      expect(result).toStrictEqual({ Contents: [{ Key: 'bar/test-file.txt' }] });

Although, maybe it's better to look at this:
https://github.com/relmify/jest-fp-ts

NOTE: if this is accepted, need to add new functions to README

@cyberixae
Copy link
Contributor

cyberixae commented Sep 16, 2022

Originally, I wrote ruins so people who don't want to learn functional programming could work with a functional API without having to learn new things. Writing fromIO, fromTask, fromOption and fromEither were relatively easy because all of them have a straightforward non-funtional analog. Implementing fromReader is a bit more tricky. First of all, it is not clear to me whether or not wrapping the call in a non-curried call makes calling the reader any easier. For, example consider the following three alternative syntaxes for calling reader foo. It is unclear to me whether or not doing this is useful or not.

const foo: Reader<Deps, string> = ...
cont deps: Deps = ...

foo(deps)  // no ruins
fromReader(deps, foo)  // proposed by this PR
fromReader(foo, deps)  // deps as second argument, more conventional?
fromReader(deps)(foo)  // curried variant, too functional?
fromReader(foo)(deps)  // the other possible curry, fromReader(foo) is foo :D

Let's now consider how the same example would work with ReaderTask.

const bar: ReaderTask<Deps, string> = ...
cont deps: Deps = ...

fromTask(bar(deps))  // no ruins for Reader
fromReaderTask(deps, bar)
fromReaderTask(bar, deps)
fromReaderTask(deps)(bar)
fromReaderTask(bar)(deps)  // fromReaderTask(bar) is flow(bar, fromTask)

If this seems like worth doing, and if we decided to go with non-curried implementation, then it would perhaps make sense to have deps as second argument. I would imagine that deps would more typically be the second argument in this kind of calls, since that might make using some default parameter solutions easier.

@cyberixae
Copy link
Contributor

For a moment I thought we could perhaps create overloads to make the dependencies optional in situations where they have some trivial type like null or void or zero tuple [] or some other constant value that can be generated without outside knowledge. Not sure if that would ever be useful. However, I just realized it is not even possible because the interpretation of JavaScript function arguments is dynamic, and a function does not reveal the type of arguments it is expecting. In the following example code implementing wantsNull is not possible because we can never know at runtime whether or not the function we got is expecting null as input.

function ruinsFromReader<R>(reader: Reader<null, R>, model?: null): R
function ruinsFromReader<M, R>(reader: Reader<M, R>, model: M): R {
  if (wantsNull(reader) {
    return reader(null)
  }
  return reader(model)
}

This would work better in the curried form since we could necessitate a specific kind of Reader through the initial call.

function fromReader<M>(model: M): <R>(r: Reader<M, R>) => R {
  return (reader) => reader(model);
}
const fromNullReader = fromReader(null)
const result = fromNullReader(reader)  // only Reader<null, R> would be accepted

However, at this point it becomes a question of whether we want a more powerful FP-esque implementation, or a more procedural less powerful one.

@cyberixae
Copy link
Contributor

Technically it would be possible to also implement "kontra position" versions of these and all other tools already present in the ruins package. For example fromTaskK would be something like.

function fromTaskK<
  A extends readonly Array<unknown>
>(f: (...a: A) => Task<R>): (...a: A) => R {
  return (...args) => f(...args)( )
}

If we had such formTaskK, it could also be used to turn a ReaderTask into just a Reader. In that case, the ReaderTask from above could be terminated as follows.

const result = fromTaskK(bar)(deps)

I was hoping we could use such kontra position variants in their point-free form. However, TypeScript seems to have some short comings in how it deals with typings of point-free functions. I think it is somehow related to how the official ResultType utility type does not take into account generics and overloads. See gcanti/fp-ts#1769

Also, kontra position variants feel a bit more advanced in general than the other utils we have so far included in the package. It is tempting to add all kinds of powerful bells and whistles but I am wondering how far we can stretch the feature set before this becomes an advanced utility library rather than a newbie survival kit.

Copy link
Contributor

@cyberixae cyberixae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we close this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants