Skip to content

feat: Add 'Ensure' and 'Check' method similar to C#FunctionalExtensions #281

@PI-Gorbo

Description

@PI-Gorbo

Hi! Loving the library. Using this library has felt very natural to me, as I use CSharpFunctionalExtensions all the time in C#.
I think the library identifies some helpful methods that could be useful in this library too, and I have highlighted them below.
If there are already more 'FSharp' style ways of approaching this, I am very open. I don't mind being wrong :)

Ensure

From the source code:

public static Result<T, E> Ensure<T, E>(this Result<T, E> result, Func<T, bool> predicate, Func<T, E> errorPredicate)
{
    if (result.IsFailure)
        return result;

    if (!predicate(result.Value))
        return Result.Failure<T, E>(errorPredicate(result.Value));

    return result;
}

Ensure allows you to evaluate a predicate. If the predicate is false, then return an error.

Example usage of ensure

Before:

let mapIdentityResult (res: Task<IdentityResult>) : Task<Result<unit, string>> =
    res
    |> TaskResult.ofTask
    >>= fun res ->
            if res.Succeeded then
                TaskResult.ok ()
            else
                res.Errors
                    |> Seq.map (fun x -> x.Description)
                    |> Seq.fold (fun item agg -> agg + ", " + item) ""
                    |> TaskResult.error

After:

let mapIdentityResult (res: Task<IdentityResult>) : Task<Result<unit, string>> =
    res
    |> TaskResult.ofTask
    |> TaskResult.ensure (fun res -> res.Succeeded) (fun res ->
                    res.Errors
                    |> Seq.map (fun x -> x.Description)
                    |> String.concat ", ")
    |> TaskResult.ignore

Check

From the source code:

public static Result<T, E> Check<T, K, E>(this Result<T, E> result, Func<T, Result<K, E>> func)
{
    return result.Bind(func).Map(_ => result.Value);
}

This method allows you to keep the calling success condition of the result.

Example usage of Check

Here is a code snippet that could utilize check

...
  |> TaskResult.bind (fun user ->
        if not user.EmailConfirmed then
            userManager.ConfirmEmailAsync(
                user,
                Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(request.ConfirmEmailToken))
            )
            |> mapIdentityResult
            |> TaskResult.mapError FailedToConfirm
            |> TaskResult.map (fun _  -> user)
        else
            TaskResult.ok user)
 |> ...

Here, using check would look like so:

...
  |> TaskResult.check(fun user ->
        if not user.EmailConfirmed then
            userManager.ConfirmEmailAsync(
                user,
                Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(request.ConfirmEmailToken))
            )
            |> mapIdentityResult
            |> TaskResult.mapError FailedToConfirm
        else
            TaskResult.ok)
 |> ...

I can appreciate here the code difference is small, but I think the semantic difference is helpful.

Again, I am new to f#, so if this is not the idiomatic way of doing things, then let me know - But if this is liked im happy to submit a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions