question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to unwrap Monads for return statements that expect a value?

See original GitHub issue

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

const simpleFunction = (a) => {
    myMaybe = Maybe.of(a);

    // some random transformations on myMaybe

    // this will fall back to the second case if the maybe is empty
    return myMaybe.getOrElse() || doSomethingElseOnError();
}

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!

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
evilsoftcommented, Oct 8, 2020

@JustinHoyt very nice. So a here are a couple of pointers to get you started.

  • We get the most out of these types when they are separate and are “stitched” together to combine their effects/embellishments. Take for instance the creation of the Result inside of the IO, this will limit ALL interactions with the process arguments to the Result
  • lift functions usually take instances of Applicatives as opposed to functions. You can use things like fanout/merge or converge/compose2 for pushing the results of functions into the lift.
  • As there are two effects here, we need to lift a lift so that both effects are applied. Kinda like doing map(map(fn)) like we do for Functor composition, but instead of covarient Functors, these are Applicative Functors.
  • The run function is on the instance, as opposed to the TypeRep, so it would have been result.run()

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 Array.of in that compose on the Err side. This is because String is a Monoid and Result accumulates Monoids in the Err for Applicative interaction. If both params where invalid, it would concat the two Strings. By throwing the value into an Array, it will accumulate the errors in separate entries.

2reactions
JustinHoytcommented, Oct 14, 2020

@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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to extract value from monadic action - Stack Overflow
A monad only supplies two functions: return :: Monad m => a -> m a (>>=) :: Monad m => m a ->...
Read more >
How to get the value out of the monad by Mark Seemann
You can do this because Maybe is a monad. The result at this stage is a Maybe<Reservation> object.
Read more >
A Fistful of Monads - Learn You a Haskell for Great Good!
Just what we expected. If the monadic value on the left is a Nothing, the whole thing is Nothing. And if the function...
Read more >
Monads as containers - HaskellWiki
The list monad, in some sense, models computations which could return any number of values. Bind pumps values in, and catches all the...
Read more >
Making Sense of Monads! - Monday Morning Haskell
Certain lines have no result (returning () ), so they are just IO () expressions. Other lines "get" values using <- . For...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found