Closed spik3r closed 4 years ago
Could you clarify what is the usecase for such helper?
I'm basically trying to get the value from within the optional if it exists.
Otherwise return a sensible default. Similar to Java's Optional.orElse()
Seems like a recurring pattern if you're using Optional to avoid nulls.
This is essentially getOrElse
It seems odd that putting something into an optional is as simple as const foo = some("foo")
but getting it back out seems to require a bit of ceremony every time and is a little confusing
pipe(
foo,
getOrElse(() => "nothing here")
Sorry if it's a noob question. Is there a simpler way to using it without needing the pipe like in the docs?
pipe
is the idiomatic way to work with fp-ts
.
On practice it's better to use transformations (monadic interface: map
, chain
etc.) instead of manually picking data from Option
's and use getOrElse
in the very end of effectful chains.
If you could provide some example of your problem we could help you with the solution.
Essentially I'm calling a function that given certain inputs returns a string and otherwise doesn't. I'd prefer not to use nulls for the latter and from what I've read it looks like Option
or Either
are the ways to do it with fp-ts
I'm open to suggestions around how you would do it as I'm new to the framework & typescript in general.
So you have something like this?
declare const process: (input: string) => Option<string>
Then when you deal with the result of getValue
the preferred way is to use option.map
or option.chain
depending on the situation:
import { option } from 'fp-ts'
const result1 = pipe(
process('foo'),
option.map(result => {
// ^---- type is `string`
return `Result: ${result}`
})
) // type is `Option<string>`
// or sequence multiple possibly "nullable" operations
const result2 = pipe(
process('foo'),
option.chain(process),
option.chain(process)
) // type is `Option<string>`
I think you do get used to the verbosity. And yes it is verbose.
You could write a wrapper util function that does what you want.
@raveclassic @gcanti Just a suggestion...
A thought that comes to mind is providing a similar function to toUndefined()
that returns the value or a default toValueOrElse()
Thanks @raveclassic Yea essentially that's it. @gkamperis I probably would :) Although I'd prefer some kind of wrapper to encapsulate it as you suggest since I doubt I can convince my colleagues adding a new library & then all this extra verbosity is worth it to avoid nulls, especially since most of us come from an OO background.
@spik3r The reasoning behind pipe
starts to be clear when you work with other data structures like Either
, Reader
, Task
, ReaderTaskEither
, RemoteData
etc. All of them have the same API which gracefully fits into pipe
.
A thought that comes to mind is providing a similar function to toUndefined() that returns the value or a default
@gkamperis that's getOrElse
import * as O from 'fp-ts/lib/Option'
import { pipe } from 'fp-ts/lib/pipeable'
declare const x: O.Option<string>
pipe(
x,
O.toUndefined
)
pipe(
x,
O.getOrElse(() => '')
)
@gcanti I know... :)
But at same token toUndefined
is
pipe(
x,
O.getOrElse(() => undefined )
)
Yet it still exists and is helpful because it reduces verbosity.
In my mind
toUndefined = toValueOrElse(undefined)
@gkamperis This doesn't type-check
declare const x: O.Option<string>
pipe(
x, // Argument of type 'Option<string>' is not assignable to parameter of type 'Option<undefined>'
O.getOrElse(() => undefined)
)
toValueOrElse
is getOrElse
with "auto widening":
declare function getOrElse<B>(f: () => B): <A>(ma: O.Option<A>) => A | B
// const toUndefined: <A>(ma: O.Option<A>) => A | undefined
const toUndefined = getOrElse(() => undefined)
However I'm not a fan of auto widening functions.
@gcanti I meant it as a conceptual example I did not mean it as an exact implementation.
Consider this though... it compiles:
pipe(none, getOrElse(() => undefined as unknown))
toUndefined
is defined as
export function toUndefined<A>(ma: Option<A>): A | undefined {
return isNone(ma) ? undefined : ma.value
}
toValueOrElse
could be
export function toValueOrElse<A>(ma: Option<A>, defaultVal: A): A {
return isNone(ma) ? defaultVal : ma.value
}
it('toValueOrElse', () => {
assert.deepStrictEqual(O.toValueOrElse(O.none, undefined), undefined)
assert.deepStrictEqual(O.toValueOrElse(O.none, 5), 5)
assert.deepStrictEqual(O.toValueOrElse(O.some(1), undefined), 1)
})
You don't need pipe for single function application.
getOrElse(() => "sensible default")(maybeValue)
If you don't like lazy and curried functions because of extra syntax, its not hard to write generic higher-order uncurry
and unlazy
functions which you can apply to getOrElse
or any other lazy curried function and get back what you want.
getOrElse(() => "sensible default")(maybeValue)
Thanks @Eoksni I think I'll go with that suggestion. :)
@spik3r Read https://fr.umio.us/why-ramda/ to learn why "actionable" value is the last argument. In that example, it's really the last argument, but because of fp-ts is typed, it has to implement currying "manually" via returned functions. Functional programming "partial application" is how we can create ad-hoc "classes". By "classes" I mean something with a state. In functional programming, the state is stored in the closure of some function.
🚀 Feature request
Hi there. It would be great to have a concise way to get the value from an Option see suggestion below.
Suggested Solution
Who does this impact? Who is this for?
I've added the above snippet to my project, but adding something similar to the framework could help new users of the framework get up and running easier.