-
-
Notifications
You must be signed in to change notification settings - Fork 504
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 Reader
usage example
#1049
Comments
Thats the high level benefit. I would describe it like you can use a function without calling it or a Here a simple example. Please not that import * as Rr from 'fp-ts/lib/Reader'
import * as RTE from 'fp-ts/lib/ReaderTaskEither'
import * as TE from 'fp-ts/lib/TaskEither'
import * as b from 'fp-ts/lib/boolean'
import { pipe } from 'fp-ts/lib/pipeable'
interface Config {
logLevel: number
}
const logLevel: Rr.Reader<Config, number> = config => config.logLevel
declare const foo: RTE.ReaderTaskEither<Config, Error, void>
const bar: RTE.ReaderTaskEither<Config, Error, void> = pipe(
logLevel,
Rr.map(logLevel =>
pipe(
logLevel > 5,
b.fold(
() => TE.right(undefined),
() => TE.rightIO(() => console.log('Some side effect'))
)
)
)
)
const main: RTE.ReaderTaskEither<Config, Error, void> = pipe(
foo,
RTE.chain(() => bar)
)
RTE.run(main, {
logLevel: 10
}) |
@mlegenhausen thanks. Hmmm, all the |
@vicrac @mlegenhausen what about: The purpose of the One of the ideas presented here is to use the The first thing you need to know is that the type interface Reader<R, A> {
(r: R): A
} where ExampleLet's say we have the following piece of code const f = (b: boolean): string => (b ? 'true' : 'false')
const g = (n: number): string => f(n > 2)
const h = (s: string): string => g(s.length + 1)
console.log(h('foo')) // 'true' What if we want to internationalise interface Dependencies {
i18n: {
true: string
false: string
}
}
const f = (b: boolean, deps: Dependencies): string => (b ? deps.i18n.true : deps.i18n.false) Now we have a problem though, const g = (n: number): string => f(n > 2) // error: An argument for 'deps' was not provided We must add an additional parameter to const g = (n: number, deps: Dependencies): string => f(n > 2, deps) // ok We haven't finished yet, now it's const h = (s: string, deps: Dependencies): string => g(s.length + 1, deps) finally we can run const instance: Dependencies = {
i18n: {
true: 'vero',
false: 'falso'
}
}
console.log(h('foo', instance)) // 'vero' As you can see, Can we improve this part? Yes we can, we can move
|
@gcanti Thanks for great explanation 😄 I'd still have one thing confusing me: what does really const g = (n: number) => (deps: Dependencies) => pipe(
deps,
chain(deps => fn(n > deps.lowerBound))
) wouldn't it be exactly the same? Isn't |
@vicrac it doesn't compile
|
So |
No, your You can rewrite // this compiles v-- no chain here
const g = (n: number) => (deps: Dependencies) => pipe(deps, f(n > deps.lowerBound)) I mean, that's fine, however if you come from this // v-- signature --------------------------v
const g = (n: number): Reader<Dependencies, string> => f(n > 2) and at some point you want to access the environment, you may want to tweak only the implementation rather than the signature, so // v-- signature stay the same ------------v
const g = (n: number): Reader<Dependencies, string> =>
// while the implementation changes
pipe(
ask<Dependencies>(),
chain(deps => f(n > deps.lowerBound))
) EDIT: in conclusion all the following const g = (n: number) => (deps: Dependencies) => pipe(deps, f(n > deps.lowerBound))
const g = (n: number): Reader<Dependencies, string> => deps => pipe(deps, f(n > deps.lowerBound))
const g = (n: number): Reader<Dependencies, string> => deps => f(n > deps.lowerBound)(deps)
const g = (n: number): Reader<Dependencies, string> =>
pipe(
ask<Dependencies>(),
chain(deps => f(n > deps.lowerBound))
) |
@gcanti Thanks, that makes a lot of thing clearer 😄 Another question though: How does |
@vicrac not sure I understand the question, they are the usual monadic operations. They are responsible for the same things they are responsible for in the case of, say, Let's say you have the following snippet import * as E from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/pipeable'
declare function f(s: string): E.Either<Error, number>
declare function g(n: number): boolean
declare function h(b: boolean): E.Either<Error, Date>
// composing `f`, `g`, and `h` -------------v---------v-----------v
const result = pipe(E.right('foo'), E.chain(f), E.map(g), E.chain(h))
const pointFreeVersion = flow(f, E.map(g), E.chain(h)) and at some point you must refactor import * as RE from 'fp-ts/lib/ReaderEither'
interface Dependencies {
foo: string
}
declare function f(s: string): RE.ReaderEither<Dependencies, Error, number>
// before
const result = pipe(E.right('foo'), E.chain(f), E.map(g), E.chain(h))
const pointFreeVersion = flow(f, E.map(g), E.chain(h))
// after
const result = pipe(
RE.right('foo'),
RE.chain(f),
RE.map(g),
RE.chain(b => RE.fromEither(h(b)))
)
const pointFreeVersion = flow(
f,
RE.map(g),
RE.chain(b => RE.fromEither(h(b)))
) p.s. As a curiosity, import * as R from 'fp-ts/lib/Reader'
import { flow } from 'fp-ts/lib/function'
declare function len(s: string): number
declare function double(n: number): number
declare function gt2(n: number): boolean
const composition = flow(len, double, gt2)
// equivalent to
const composition = pipe(len, R.map(double), R.map(gt2)) |
btw this is basically boilerplate RE.chain(b => RE.fromEither(h(b))) we could add some helper, like // ReaderEither.ts
export declare function chainEither<E, A, B>(
f: (a: A) => E.Either<E, B>
): <R>(ma: RE.ReaderEither<R, E, A>) => RE.ReaderEither<R, E, B> so we can do const pointFreeVersion = flow(
f,
RE.map(g),
RE.chainEither(h)
) What do you think? I'll open a PR |
I think that would be great 😄 Ok, it seems very clear to me now. I'll give it some try and dig around on how I can use it in my projects, and when I come to some conclusion, I'll open a PR adding docs to Btw. all the stuff you just wrote looks like a perfect base for an article on how it works and how to use |
@vicrac yeah, I'll publish my first comment as a blog post as it is (too many things to do..). Perhaps I'll find the time to add some details in the next few weeks |
📖 Documentation
It would be great to have some minimal example usage of
Reader
monad. As far as I understand, it's purpose is to avoid threading arguments through multiple functions in order only to get them where they belong (at the bottom of calls). It would be nice if there's an example of how to refactor e.g. such a code usingReader
:The text was updated successfully, but these errors were encountered: