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

How can I tell if and Either is Left or Right? #285

Closed diegovdc closed 6 years ago

diegovdc commented 6 years ago

Can't find any method in the docs.

evilsoft commented 6 years ago

Curious if you can give an example of needing to do this. There is nothing in crocks that does this for a few reasons.

But you can make a set of functions if you need to do this like so:

const { constant, either } = require('crocks')

// isRight :: Either a b -> Boolean
const isRight =
  either(constant(false), constant(true))

// isLeft :: Either a b -> Boolean
const isLeft =
  either(constant(true), constant(false))

Though if you could post an example of how you would use these functions, I may be able to show you how to not need them, and just rely on the Disjunction that is encoded in the Either type.

EDIT: There really is no reason to build it for each case, as isRight is a predicate function, you can use not for isLeft:

const { constant, either, not } = require('crocks')

// isRight :: Either a b -> Boolean
const isRight =
  either(constant(false), constant(true))

// isLeft :: Either a b -> Boolean
const isLeft =
  not(isRight)

So you will notice that all these functions do is map to Boolean, false for Left, true for Right. And work with the negation for isLeft....That is because this disjunction is what Either encodes.

So if you need to do something based on its instance you can use bimap:

const { bimap } = require('crocks')

// Either is a Bifunctor
// fn :: Bifunctor f => f -> f
const fn =
  bimap(doSomethingIfLeft, doSomethingIfRight)

here we represent the disjunction in the type and process the value bases on the instance.

diegovdc commented 6 years ago

Thanks @evilsoft

I am using Either to validate, the user's input. As part of the codebase if built with Folktale's data.task, I want to to transform my Eithers into a Task, so if there is any Left in the object, it goes to Task.rejected with the contents of the Lefts folded. Else it goes to Task.of also folded.

const validateCreateUser = R.pipe(
  R.applySpec({
    fname: body => isNilOrEmpty(body.fname)
      ? Left('El nombre es requerido')
      : Right(body.fname),

    lname: body => isNilOrEmpty(body.lname)
      ? Left('El apellido es requerido')
      : Right(body.lname),

    email: body =>
      isNilOrEmpty(body.email)  ? Left('El email es requerido') :
      !isValidEmail(body.email) ? Left('El email es no es un correo válido')
                                : Right(body.email),

    password: body =>
      isNilOrEmpty(body.password) ? Left('El password es requerido') :
      body.password.length < 6     ? Left('El password debe ser de al menos 6 caracteres')
                                   : Right(body.password),

    password_confirmation: body => body.password !== body.password_confirmation
      ? Left('El password y su confirmación no coinciden') :
        Right(body.password_confirmation),
  }),
  fromValidatedEitherToTask
)

At the moment I switched to data.either, so fromValidatedEitherToTask. But I guess there must be a better way. But I wanted to start using crocks to get to know the library better.

const fromValidatedEitherToTask = objOfEithers => R.pipe(
  R.partition(either => either.isLeft),
  ([left, right]) => R.isEmpty(left)
    ? Task.of(R.map(r => r.fold(R.identity, R.identity), right))
    : Task.rejected(R.map(l => l.fold(R.identity, R.identity), left)),
)(objOfEithers)
evilsoft commented 6 years ago

Ah. I see, yep you are forced into that with partition. You could move back to crocks Either and use T and F from ramda.

then while it is the same flow, you could do something like:

const { bimap, either } = require('crocks')

const isLeft =
  either(R.T, R.F)

const rejectFirst = R.ifElse(
  R.compose(R.isEmpty, R.head),
  R.compose(Task.of, R.nth(1)),
  R.compose(Task.rejected, R.head)
)

const extract =
  R.map(either(R.identity, R.identity))

const fromValidatedEitherToTask = R.pipe(
  R.partition(isLeft),
  rejectFirst,
  bimap(extract, extract)
)

You will be in the same boat as before, so this really adds nothing.

diegovdc commented 6 years ago

Thanks for taking the time to elaborate on that example. It's very cool!

bnenu commented 6 years ago

Hi, I was following the topic and seems that you could go about using tagged predicates as @evilsoft showed in one of the earlier videos (https://www.youtube.com/watch?v=I7yvTw2Yoo0). The solution is very elegant for taking first error message that comes in the list of validations and it is given back as a Maybe which you can chain further to a Task.