Reputation: 303
Say I have a Request
object:
{
user: { /* user data: username, email, etc. */ }
post: { /* post data: content, date, etc. */ }
}
Example of a Request
object:
{
user: {
id: '123'
username: 'kibe'
email: '[email protected]'
}
post: {
content: 'my new post!'
date: '20/02/2004'
}
}
Now, I have two functions: validateUser
and validatePost
. Both of them return a Maybe
monad, because they might fail.
How can I then do something like this?
function savePost (request) {
return request
|> validateUser // if validateUser returns either Success(user) or Failure(error), how would I pass down the post?
|> validatePost
|> savePostToDb
|> ok
}
Should I create a function validateRequest
which composes validateUser
and validatePost
? But then, how would I only give the post
object to savePostToDb
? What if savePostToDb
also requires the user ID?
function savePost (request) {
return request
|> validateRequest // returns an Either monad
|> savePostToDb // only requires user ID and the post, how would I pass these down?
|> ok
}
Hopefully these questions make sense. I am new to FP and although I am understanding its paradigms, I am failing to design a simple program
Thanks!
Upvotes: 0
Views: 581
Reputation: 135377
prop
We write a simple prop
function which safely looks up properties on your object. If they are present, we get a Just
-wrapped value, otherwise we get Nothing
-
const fromNullable = x =>
x == null
? Nothing
: Just(x)
const prop = k => t =>
fromNullable(t[k])
props
You have nested properties though, so let's make it so we can dig arbitrarily deep into your object using any sequence of props
-
const props = (k, ...ks) => t =>
ks.length
? prop(k)(t).bind(props(...ks))
: prop(k)(t)
validate
Now we can write a simple validate
coroutine -
function* validate (t)
{ const id = yield props("user", "id")(t)
const username = yield props("user", "username")(t)
const email = yield props("user", "email")(t)
const post = yield props("post", "content")(t)
const date = yield props("post", "date")(t)
return Just({ id, username, email, post, date }) // <- Just(whateverYouWant)
}
const doc =
{ user:
{ id: '123'
, username: 'kibe'
, email: '[email protected]'
}
, post:
{ content: 'my new post!'
, date: '20/02/2004'
}
}
coroutine(validate(doc)).bind(console.log)
With a valid doc
we see -
{
id: '123',
username: 'kibe',
email: '[email protected]',
post: 'my new post!',
date: '20/02/2004'
}
With an invalid document, otherdoc
-
const otherdoc =
{ user:
{ id: '123'
, username: 'kibe'
, // <- missing email
}
, post:
{ content: 'my new post!'
, // <- missing date
}
}
coroutine(validate(otherdoc)).bind(console.log)
// no effect because validate returns a Nothing!
coroutine
Implementation of coroutine
is simple and most importantly it is generic for any monad -
function coroutine (f)
{ function next (v)
{ let {done, value} = f.next(v)
return done ? value : value.bind(next)
}
return next()
}
Maybe
Lastly we supply implementation for Nothing
and Just
-
const Nothing =
({ bind: _ => Nothing })
const Just = v =>
({ bind: f => f(v) })
demo
Expand the snippet below to verify the results in your own browser -
const Nothing =
({ bind: _ => Nothing })
const Just = v =>
({ bind: f => f(v) })
const fromNullable = x =>
x == null
? Nothing
: Just(x)
const prop = k => t =>
fromNullable(t[k])
const props = (k, ...ks) => t =>
ks.length
? prop(k)(t).bind(props(...ks))
: prop(k)(t)
function coroutine (f)
{ function next (v)
{ let {done, value} = f.next(v)
return done ? value : value.bind(next)
}
return next()
}
function* validate (t)
{ const id = yield props("user", "id")(t)
const username = yield props("user", "username")(t)
const email = yield props("user", "email")(t)
const post = yield props("post", "content")(t)
const date = yield props("post", "date")(t)
return Just({ id, username, email, post, date })
}
const doc =
{user:{id:'123',username:'kibe',email:'[email protected]'},post:{content:'my new post!',date:'20/02/2004'}}
coroutine(validate(doc)).bind(console.log)
related reading
Upvotes: 1
Reputation: 61
Yes you should create validateRequest
and a savePostToDb
Methodes which give you a boolean back. Then you can simple create two Maybe-Functions with thos two new methodes and compose it togheter. Look at my example how I would do it simple:
// Here some simple Left/Right to Just and Nothing (Maybe)-Monade function to work with
const Left = x => f => _ => f(x);
const Right = x => _ => g => g(x);
const Nothing = Left();
const Just = Right;
// your maybe-validateRequest function
const maybeValidateRequest = request =>
validateRequest(request)
? Just(user)
: Nothing
// your maybe-postToDb function
const maybePostToDb = request =>
savePostToDb(request)
? Just(request)
: Nothing
// and finally compose those two maybes and do the postRequest finally
const savePost = request =>
maybeValidateRequest(request)
(() => console.error("Validation failed"))
(_ =>
maybePostToDb(request)
(() => console.error("Save to DB failed")))
(_ => postRequest(request)) // when it's everything passed,the postRequest can be safely made
Upvotes: 0