-
Notifications
You must be signed in to change notification settings - Fork 102
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
How to unwrap Monads for return statements that expect a value? #499
Comments
Check out the example here, https://crocks.dev/docs/crocks/Result.html#either or https://crocks.dev/docs/crocks/Maybe.html#either I think it's the closest to what you're trying to do. What you're essentially touching on is folding out the value. In this scenario you need to ensure that you can take a |
This is exactly what I'm looking for! My understanding is that IO monads don't allow for an unfolding function like this, right? In that case would you suggest I wrap getting IO values by throwing them immediately into a Result? Also, if you want free internet points, you can paste what you wrote into my stackoverflow question I'll accept the answer. |
@JustinHoyt So these type of functions have a really cool name called For Exponential Types, Like If you |
Here is an example of something simple I'm trying to do to lift a value out of an const R = require('ramda');
const { Result, IO } = require('crocks');
const { Err, Ok } = Result;
const first = IO.of(() => process.argv[2] ? Ok(process.argv[2]) : Err('first arg invalid'));
const second = IO.of(() => process.argv[3] ? Ok(process.argv[3]) : Err('second arg invalid'));
const mAdd = R.liftN(2, R.add);
const result = mAdd(chain(first), map(second));
console.log(IO.run(result)); I am running into two issues:
|
@JustinHoyt very nice. So a here are a couple of pointers to get you started.
With those points in mind, i came up with something like this: const IO = require('crocks/IO')
const Result = require('crocks/Result')
const add = require('ramda/src/add')
const compose = require('crocks/helpers/compose')
const curry = require('crocks/helpers/curry')
const constant = require('crocks/combinators/constant')
const ifElse = require('crocks/logic/ifElse')
const isNumber = require('crocks/predicates/isNumber')
const liftA2 = require('crocks/helpers/liftA2')
const { Err, Ok } = Result
const validResult = curry(
(pred, err) => ifElse(pred, Ok, compose(Err, Array.of, constant(err)))
)
const isValid =
validResult(isNumber)
const first =
IO(() => process.argv[2])
.map(isValid('first is bad'))
const second =
IO(() => process.argv[3])
.map(isValid('second is bad'))
const addArgs =
liftA2(liftA2(add))
addArgs(first, second).run() EDIT: Oh, also take note of the |
Oh a neat helper using the const lift2A2 = curry(
compose(liftA2, liftA2)
) |
if you use |
@evilsoft Thanks so much for the revised example of IO! I made a dumbed down solution that I could understand how to write more easily: const IO = require('crocks/IO');
const Result = require('crocks/Result');
const R = require('ramda');
const { Err, Ok } = Result;
const isNumber = (value) => parseInt(value) ? Ok(value) : Err('not a number');
const isPositive = (value) => value >= 0 ? Ok(value) : Err('not a positive integer');
const validate = R.compose(R.chain(isPositive), isNumber);
const first = IO(() => process.argv[2]).map(validate);
const second = IO(() => process.argv[3]).map(validate);
const addArgs = R.lift(R.lift(R.add));
console.log(addArgs(first, second).run().either(R.identity, R.identity)); After taking another look at your example after writing this I now think I understand a little more what your |
Nice! So, if you do not ant to accumulate Also I may recommend using the predicates provided by So with all those suggestions in mind, I put together another example, using const IO = require('crocks/IO')
const Either = require('crocks/Either')
const add = require('ramda/src/add')
const bimap = require('crocks/pointfree/bimap')
const compose = require('crocks/helpers/compose')
const composeK = require('crocks/helpers/composeK')
const concat = require('crocks/pointfree/concat')
const constant = require('crocks/combinators/constant')
const curry = require('crocks/helpers/curry')
const identity = require('crocks/combinators/identity')
const ifElse = require('crocks/logic/ifElse')
const isInteger = require('crocks/predicates/isInteger')
const liftA2 = require('crocks/helpers/liftA2')
const { Left, Right } = Either
// Applicative A => lift2A2 :: (a -> b -> c) -> A (A a) -> A (A b) -> A (A c)
const lift2A2 = curry(
compose(liftA2, liftA2)
)
// gt :: Number -> Number -> Boolean
const gt = curry(
(a, b) => a < b
)
// validateEither :: (a -> Boolean) -> e -> Either e a
const validateEither = curry(
(pred, err) => ifElse(pred, Right, compose(Left, constant(err)))
)
// checkInteger :: a -> Either String Integer
const checkInteger =
validateEither(isInteger, 'not an Integer')
// checkPositive :: Number -> Either String Number
const checkPositive =
validateEither(gt(0), 'not a positive Number')
// validate :: a -> Either String Integer
const validate =
composeK(checkPositive, checkInteger)
// validator :: String -> a -> Either String Integer
const validator = tag => compose(
bimap(concat(` (${tag})`), identity),
validate,
parseFloat
)
// first :: IO (Either String Integer)
const first =
IO(() => process.argv[2])
.map(validator('first'))
// second :: IO (Either String Integer)
const second =
IO(() => process.argv[3])
.map(validator('second'))
// Applicative A => A (A a) -> A (A b) -> A (A c)
const addArgs =
lift2A2(add)
addArgs(first, second)
.run()
.either(identity, identity) (note: purposefully written this way to help build an intuition around these ADTs and how they relate) |
Ohhhhh. I think I may know what is going on with your intuition on this const result = mAdd(chain(first), map(second)); So when using
|
@evilsoft Seeing how you make a simple reusable function with validateEither blows my mind haha. I also didn't know about bimap so that's a super useful function to just learn! Thanks for the example, it's helping me identify opportunities to create generic reusable functions. On a side note, I learned what I know about functional programming entirely from Professor Frisby's Mostly Adequate Guide to Functional Programming. It's a wonderful book, but I'm curious if you'd recommend any other readings or series to improve my functional programming in JS. I just found your ADT video series, so I'll try to work through that this weekend and next. |
Is your feature request related to a problem? Please describe.
In my use case I would like to use functional programming techniques, using ramda for a functional library and Crocks for an algebraic data structure library to write code in a not fully functional codebase. No one else on my team has any familiarity with Functional Programming and I'm new to it myself. I want to be able to pull values out of monads so I can return normal values in a codebase that's not functional in nature and slowly add more functional elements. I'm typically going to be using Either, IO, and Maybe monads to write my code, then extract the final result out of the resulting monad so I can return the value to a function that is not made to accept monads yet.
Describe the solution you'd like
Folktale has a function called getOrElse which will return a value or an undefined/error string. This is super useful and allows me to write functionally in an environment that does not expect to handle monads. For example, I can make some Results/Either monads, chain them together and then carefully unwrap the value for possible return. I get some functioal benefits with a small amount of impurity at the end of my function. Does Crocks have something similar or is there another way I can unwrap Either, IO, or Maybe with an internal function?
Describe alternatives for how you do this now
My current workaround is just to use Folktale, but the maintainer has mentioned that they don't have the bandwidth to maintain the project anymore. Folktale also has a limited amount of monads for me to use.
Code
Additional context
I also made a stackoverflow with this question, but could not tag it properly because I don't have enough reputation to add a Crocks tag.
Thanks in advance for your advice or feedback!
The text was updated successfully, but these errors were encountered: