Skip to content

Instantly share code, notes, and snippets.

@acdcjunior
Created June 29, 2019 09:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acdcjunior/5d8364a93fd8db8ad654d502e9e0e6dc to your computer and use it in GitHub Desktop.
Save acdcjunior/5d8364a93fd8db8ad654d502e9e0e6dc to your computer and use it in GitHub Desktop.
Dependency Injection via Monads in JavaScript/TypeScript
import axios, {AxiosInstance} from 'axios';
interface Context {
get(s: string): any;
headersForResource(id: number): any;
}
class Contextual<T> {
constructor(private g: (ctx: Context) => T) {}
run(ctx: Context): T { return this.g(ctx) };
map<B>(aToB: (A) => B): Contextual<B> { return new Contextual<B>((ctx: Context) => aToB(this.g(ctx))); }
flatMap<B>(f: (A) => Contextual<B>): Contextual<B> { return new Contextual<B>((ctx: Context) => f(this.g(ctx)).run(ctx)); }
}
function combine<RESULT>(ctxls: Contextual<any>[], combinator: (results: any[]) => RESULT): Contextual<RESULT> {
return new Contextual<RESULT>(ctx => {
return combinator(ctxls.map(c => c.run(ctx)));
});
}
///////////////////////////////////////////// USER
interface User {}
let userApi = new Contextual<AxiosInstance>((ctx: Context) => {
return axios.create({baseURL: ctx.get('USERS_URL'), headers: {...ctx.headersForResource(123)}});
});
const userByIdQuery = (id) => (userApi: AxiosInstance) => userApi.get('/users/' + id).then(r => r.data);
const userById = (id: number): Contextual<Promise<User>> => userApi.map(userByIdQuery(id));
///////////////////////////////////////////// POST
interface Post {}
let postApi = new Contextual<AxiosInstance>((ctx: Context) => {
return axios.create({baseURL: ctx.get('POSTS_URL'), headers: {...ctx.headersForResource(123)}});
});
// written as one function only
let postById = (id): Contextual<Promise<Post>> => postApi.map((postApi: AxiosInstance) => {
return postApi.get('/posts/' + id).then(r => r.data);
});
///////////////////////////////////////////// COMBINING RESULTS: USER AND POST
let userOneName = userById(1).map(userPromise => userPromise.then(u => u.name));
let postOneTitle: Contextual<Promise<Post>> = postById(1).map(async pP => (await pP).title);
let userAndPostCombinedManually = (userPromise) => new Contextual<Promise<any>>(async t => {
let us = await userPromise;
let pp = await postOneTitle.run(t);
return {us, pp};
});
let userAndPostCombinedViaHelper = combine([userById(1), postById(1)], async ([userPromise, postPromise]) => {
const u = await userPromise;
const p = await postPromise;
return {...u, ...p}
});
/////////////////////////// EXECUTION, WHEN YOU ACTUALLY PROVIDE THE CTX
(async () => {
const ctx = {
get() { return 'http://jsonplaceholder.typicode.com/'},
headersForResource() { return {Authorization: `Bearer JWT`, 'X-UFP': 'UFP'}; }
};
const actualUserOne = await userOneName.run(ctx);
console.log(actualUserOne);
console.log('--------------------');
const flattened = await userById(1).flatMap(userAndPostCombinedManually).run(ctx);
console.log(flattened);
console.log('--------------------');
console.log(await userAndPostCombinedViaHelper.run(ctx));
})();
@acdcjunior
Copy link
Author

// "Reader Monad" 
export class Reader<DEPENDENCY, RESULT> {
  static of<DEPENDENCY, RESULT>(g: (deps: DEPENDENCY) => RESULT) { return new Reader(g); }
  private constructor(private g: (deps: DEPENDENCY) => RESULT) {}
  run(deps: DEPENDENCY): RESULT { return this.g(deps); }
  map<B>(aToB: (A: RESULT) => B): Reader<DEPENDENCY, B> { return new Reader<DEPENDENCY, B>((deps: DEPENDENCY) => aToB(this.g(deps))); }
  flatMap<B>(f: (A: RESULT) => Reader<DEPENDENCY, B>): Reader<DEPENDENCY, B> { return new Reader<DEPENDENCY, B>((deps: DEPENDENCY) => f(this.g(deps)).run(deps)); }
}

@acdcjunior
Copy link
Author

   function combine<E, R, A, B>(a: Reader<E, A>, b: Reader<E, B>, g: (a: A, b: B) => R): Reader<E, R> {
            return Reader<E, R>((ctx: E) => {
                return g(a.run(ctx), b.run(ctx));
            });
        }

        const reader: Reader<TokenJwt| (() => TokenJwt), string> = combine(sa2Api2, sa2Api2, (sa2Api2a1, sa2Api2a2) => {
            return 'asas'
        });

        interface AA { aa: string; }
        interface BB { bb: string; }
        interface CC { cc: string; }

        const r: Reader<AA, BB> = Reader((a: AA) => ({ bb: 'bb' }) as BB);
        const r2: Reader<AA, CC> = Reader((a: AA) => ({ cc: 'cc' }) as CC);

        function ReaderComb<E, R1, R2>(r1: Reader<E, R1>, r2: Reader<E, R2>) {
            return Reader((t: E) => [r1.run(t), r2.run(t)] as [R1, R2])
        }

        const bc = (b: BB, c: CC) => console.log(b.bb, c.cc)

        r.map(b => r2.map(c => {
            bc(b, c)
        }));

        const reader1x: Reader<AA, string> = ReaderComb(r, r2).map(([bb, cc]) => {
            return bb.bb + cc.cc;
        });

        const reader1: Reader<AA, string> = Reader((t: AA) => [r.run(t), r2.run(t)]).map(([bb, cc]) => {
            return bb.bb + cc.cc;
        });

        Reader((t: TokenJwt2019Usuario) => [sa2Api2.run(t), sa2Api2.run(t)]).map()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment