Skip to content

Chaining With Map And Bind

ferdi kurnaz edited this page Aug 27, 2026 · 1 revision

Chaining with Map and Bind

One of the best parts of Resultron is that you can connect many steps into one clean "pipeline." If any step fails, the whole chain stops automatically, and the error flows to the end. You don't need if checks after every single line.

A simple chain

var result = Result.Success()
    .Bind(ValidateName)
    .Bind(ValidateEmail)
    .Bind(SaveUser)
    .Match(
        onSuccess: () => "User created successfully.",
        onFailure: error => $"Failed: {error.Description}"
    );

What happens here, step by step:

  1. Result.Success() starts the chain in a successful state.
  2. .Bind(ValidateName) runs ValidateName. If it fails, the chain stops here and skips the remaining steps.
  3. .Bind(ValidateEmail) only runs if step 2 succeeded.
  4. .Bind(SaveUser) only runs if step 3 succeeded.
  5. .Match(...) turns the final result (success or failure) into a plain string message.

This means you get one place (the Match at the end) to handle both success and failure, instead of writing if (result.IsFailure) return ... after every step.

A real example: creating a user

This example is adapted from the project's sample app. It validates a name and email, checks for duplicates, and then saves the user — all in one chain:

public Result<User> Create(string name, string email)
{
    return ValidateName(name)
        .Bind(() => ValidateEmail(email))
        .Bind(() => CheckEmailUniqueness(email))
        .Bind(() => SaveUser(name, email));
}

private static Result ValidateName(string name)
{
    return name.Length >= 2
        ? Result.Success()
        : Result.Failure(UserErrors.InvalidName);
}

private static Result ValidateEmail(string email)
{
    return email.Contains('@')
        ? Result.Success()
        : Result.Failure(UserErrors.InvalidEmail);
}

private Result CheckEmailUniqueness(string email)
{
    return _repository.EmailExists(email)
        ? Result.Failure(UserErrors.EmailAlreadyExists(email))
        : Result.Success();
}

private Result<User> SaveUser(string name, string email)
{
    var user = new User(Guid.NewGuid(), name, email, DateTime.UtcNow);
    return _repository.Add(user);
}

If name is too short, ValidateName fails, and ValidateEmail, CheckEmailUniqueness, and SaveUser are never called. The caller of Create simply gets back a failed Result<User> with a clear error, like "User.InvalidName".

Updating a value with Map and Bind together

Here is an example that mixes both Map and Bind to update a user's name:

public Result<User> UpdateName(Guid id, string newName)
{
    return ValidateName(newName)
        .Bind(() => _repository.GetById(id))     // Result -> Result<User>
        .Map(user => new User(user.Id, newName, user.Email, user.CreatedAt)) // Result<User> -> Result<User>
        .Bind(_repository.Update);               // Result<User> -> Result<User>
}
  • .Bind(() => _repository.GetById(id)): fetches the user. This step can fail (user not found), so it uses Bind.
  • .Map(user => ...): creates an updated copy of the user with the new name. This step cannot fail by itself, it's just a plain transformation, so it uses Map.
  • .Bind(_repository.Update): saves the updated user. Saving can fail, so it uses Bind again.

Why chaining is useful

Without chaining, the same code might look like this:

var nameCheck = ValidateName(newName);
if (nameCheck.IsFailure)
    return Result<User>.Failure(nameCheck.Error);

var userResult = _repository.GetById(id);
if (userResult.IsFailure)
    return Result<User>.Failure(userResult.Error);

var updatedUser = new User(userResult.Value.Id, newName, userResult.Value.Email, userResult.Value.CreatedAt);

var saveResult = _repository.Update(updatedUser);
if (saveResult.IsFailure)
    return Result<User>.Failure(saveResult.Error);

return saveResult;

The chained version does the exact same thing, but is much shorter and easier to read.

Next: see Handling Exceptions with Try to learn how Resultron wraps exceptions automatically.

Clone this wiki locally