1
\$\begingroup\$

I am learning more about functional programming via loop recur's online book Mostly Adequate Guide to Functional Programming, and Brian Londorf's video series on egg head.

I started using curry more (from lodash), and my last foray was actually installing folktale and trying it out for a spin.

My basic task here, is to provide a skeleton for a forgot password workflow. There are a few places where I've skipped over the details, or left things out, but these are pieces of real code I want to ship in a product. I'm hoping that the smaller functions will yield more reusable parts as time goes on, but I'm having trouble with the Result and Task types (result's are, more generally, the Either type).

Here's some nodejs javascript code for Node.js 8.5.x:

const crypto = require('crypto');
const util = require('util');
const curry = require('lodash/curry');
const moment = require('moment');
const Result = require('folktale/result');
const { task, fromPromised, waitAll, of } = require('folktale/concurrency/task');
const compose = require('folktale/core/lambda/compose');

// isValidForgotPasswordRequest :: ForgotPasswordInput -> boolean 
const isValidForgotPasswordRequest = data =>
  data != null
  && data.usernameOrEmail != null
  && typeof data.usernameOrEmail == 'string'
  && data.usernameOrEmail.length > 0;

// usernameOrEmailExists :: ForgotPasswordInput -> Result string
const usernameOrEmailExists = data => isValidForgotPasswordRequest(data)
  ? Result.Ok(data.usernameOrEmail)
  : Result.Error('No username or email');

// toHex :: Buffer -> string
const toHex = b => b.toString('hex');

// getRandomBytes :: () -> Promise Buffer
const getRandomBytes = util.promisify(crypto.randomBytes);

// genToken :: () -> Task string
const genToken = fromPromised(() => getRandomBytes(40).then(toHex));


// TODO ?? Task Result, could I traverse this?

// simulate db query that returns a promise
// fetchUser :: string -> Task Result User
const fetchUser = fromPromised(un => Promise.resolve(Result.of({
    id: 1,
    email: 'test@test.com',
    username: un,
    resetToken: null,
    resetTokenExpiration: null
})));

// run fetching user and generating token in parallel,
//  to test that out / learn how to use tasks better
// getUsernameAndToken :: (getUser) -> (getToken) ->
//  Result string -> Result Task [ Result User, string ]
const getUsernameAndToken = curry((getUser, getToken, res_un) =>
  res_un.map(un =>
    waitAll([ getUser(un), getToken() ])));

// TODO validate getUser returns correctly (so add an extra Result in there)

// TODO come back to this....
// chainTokenAssign :: Result Task [ Result User, string ] -> 
const chainTokenAssign = res =>
  res.map(jobs => 
    jobs.chain(([ res_user, token ]) =>
      of(res_user.map(usr => {
        usr.resetToken = token;
        usr.resetTokenExpiration = 'tmrw';

        return usr;
    }))
  )
);

// TODO after successful chainTokenAssign, we can
//  email user (call microservice)


// toPromise :: Result a -> Promise a
const toPromise = res =>
  res.matchWith({
    Ok: ({ value }) => value.run().promise(),
    Error: ({ value }) => Promise.reject(value)
  });


// compose it all together!
// f :: ForgotPasswordInput -> (string eventually, for now it is whatever object)
let f = compose.all(
  toPromise,
  chainTokenAssign,
  getUsernameAndToken(fetchUser, genToken),
  usernameOrEmailExists);


f({ usernameOrEmail: 'admin' })
  .then(console.log)
  .catch(console.error);

Note the above was written against Node version 8.5.

Specific questions:

  • Are those type signatures correct?
  • Am I wielding Result and Task properly?
  • Is there a way to avoid chainTokenAssign's deep nesting? (Result mapped over a task chained with a new task, which is further mapped over an inner Result)
  • Related to above, could traverse help combine some of these types?
  • Are there any general improvements or style guidelines?
\$\endgroup\$
  • \$\begingroup\$ I can't answer all your questions, but one thing that jumps out at me is that you are redefining eval. This isn't a good idea. \$\endgroup\$ – Gerrit0 Oct 17 '17 at 16:25
  • \$\begingroup\$ Yeah I renamed it to toPromise - I had forgotten eval existed, but then I remembered that eval is evil. \$\endgroup\$ – Sean Newell Oct 17 '17 at 16:32
  • \$\begingroup\$ So the folktale docs say that Tasks have rejected and of as constructors, so I think I can write a natural transformation that will simplify my code to convert Results to Tasks where convenient. Perhaps I could even write my own traverse method (folktale 2 doesn't have traversables yet, applicative tasks are on the horizon) - but perhaps not... \$\endgroup\$ – Sean Newell Oct 17 '17 at 19:09
  • \$\begingroup\$ I've refactored the code to basically just use Task everywhere. I'm not sure if it is more natural to use Result or Task in any particular function... Especially if using Task everywhere in this particular code simplifies how to combine everything well. Is there any benefit to mixing the types and needing to convert from Result to Task? Here's a gist of that refactor. \$\endgroup\$ – Sean Newell Oct 17 '17 at 20:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.