Skip to content

Commit

Permalink
fix errors and add docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
th3oth3rjak3 committed May 7, 2024
1 parent 0fdbc6c commit 6528645
Show file tree
Hide file tree
Showing 5 changed files with 312 additions and 2 deletions.
111 changes: 111 additions & 0 deletions Functional.Test/Common/CommonExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;

using Functional.Common;
using Functional.Options;

using static Functional.Common.CommonExtensions;

Expand Down Expand Up @@ -244,4 +245,114 @@ public async Task ItShouldPipeAndIgnoreInput() =>
.TapAsync(value => value.ShouldBeOfType<Unit>())
.IgnoreAsync();

[TestMethod]
public void PipeShouldHandleActionsWithNoInput()
{
var list = new List<int>();

"ignored"
.Pipe(
() => list.Add(1),
() => list.Add(2))
.ShouldBeOfType<Unit>();

list.Count.ShouldBe(2);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
}

[TestMethod]
public void EffectShouldHandleActionsWithInput()
{
var effectResult = string.Empty;

"input"
.Effect(input => effectResult = input)
.ShouldBeOfType<Unit>();

effectResult.ShouldBe("input");
}

[TestMethod]
public void EffectShouldHandleActionsWithNoInput()
{
var effectResult = string.Empty;

"ignored"
.Effect(() => effectResult = "input")
.ShouldBeOfType<Unit>();

effectResult.ShouldBe("input");
}

[TestMethod]
public async Task EffectAsyncShouldHandleActionsWithInput()
{
var effectResult = string.Empty;

await "input"
.AsAsync()
.EffectAsync(input => effectResult = input)
.EffectAsync(unit => unit.ShouldBeOfType<Unit>());

effectResult.ShouldBe("input");
}

[TestMethod]
public async Task EffectAsyncShouldHandleActionsWithNoInput()
{
var list = new List<int>();

await "ignored"
.AsAsync()
.EffectAsync(
() => list.Add(1),
() => list.Add(2))
.EffectAsync(unit => unit.ShouldBeOfType<Unit>());

list.Count.ShouldBe(2);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
}

[TestMethod]
public async Task EffectShouldHandleActionsWithNoInputAndTaskOutput()
{
var list = new List<int>();

async Task addListItem(int input) => await input.AsAsync().EffectAsync(input => list.Add(input));

await "ignored input"
.AsAsync()
.EffectAsync(() => addListItem(1), () => addListItem(2));

list.Count.ShouldBe(2);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
}

[TestMethod]
public async Task EffectShouldHandleActionsWithInputAndTaskOutput()
{
var list = new List<int>();

async Task addListItem(int input) => await input.AsAsync().EffectAsync(input => list.Add(input));

await 1
.AsAsync()
.EffectAsync(value => addListItem(value), value => addListItem(value + 1));

list.Count.ShouldBe(2);
list[0].ShouldBe(1);
list[1].ShouldBe(2);
}

[TestMethod]
public void ItShouldHandleWeirdness()
{
Option.Some("value")
.Effect(value => Console.WriteLine(value), () => Console.WriteLine("None"))
.Effect(unit => Console.WriteLine(unit.ToString()));
}

}
2 changes: 1 addition & 1 deletion Functional.Test/Results/ResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ public void ItShouldMapOkResultsThatAreHaveExceptionErrorTypes() =>
[TestMethod]
public void ItShouldHandleEffectForOkOnly() =>
Result.Ok("It's ok")
.Effect(ok => ok.ShouldBe("It's ok"));
.EffectOk(ok => ok.ShouldBe("It's ok"));

[TestMethod]
public void ItShouldHandleEffectForErrorOnly() =>
Expand Down
83 changes: 83 additions & 0 deletions Functional/Common/CommonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ public static Unit Pipe<T>(this T input, params Action<T>[] actions) =>
.Tap(actions)
.Pipe(_ => Unit.Default);

/// <summary>
/// Perform a series of actions while ignoring the input.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The ignored input.</param>
/// <param name="actions">Actions to perform.</param>
/// <returns>Unit.</returns>
public static Unit Pipe<T>(this T input, params Action[] actions) =>
input.Tap(actions).Pipe(_ => Unit.Default);

/// <summary>
/// Perform a series of actions on the input and return unit.
/// </summary>
Expand All @@ -108,6 +118,65 @@ public static Unit Pipe<T>(this T input, params Action<T>[] actions) =>
public static TOutput Pipe<T, TOutput>(this T input, Func<TOutput> mapper) =>
input.Pipe(_ => mapper());

/// <summary>
/// Perform effects on any input type that returns unit.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The input value.</param>
/// <param name="actions">Actions to perform on the input value.</param>
/// <returns>Unit.</returns>
public static Unit Effect<T>(this T input, params Action<T>[] actions) =>
input.Pipe(actions);

/// <summary>
/// Perform effects ignoring the input value.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The ignored input.</param>
/// <param name="actions">Actions to perform.</param>
/// <returns>Unit.</returns>
public static Unit Effect<T>(this T input, params Action[] actions) =>
input.Pipe(actions);

/// <summary>
/// Perform effects on the input value.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The input value to perform effects on.</param>
/// <param name="actions">The actions to perform.</param>
/// <returns>Unit.</returns>
public static async Task<Unit> EffectAsync<T>(this Task<T> input, params Action<T>[] actions) =>
await input.PipeAsync(actions);

/// <summary>
/// Perform effects ignoring the input value.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The input value to ignore.</param>
/// <param name="actions">The actions to perform.</param>
/// <returns>Unit.</returns>
public static async Task<Unit> EffectAsync<T>(this Task<T> input, params Action[] actions) =>
await input.PipeAsync(actions);

/// <summary>
/// Perform effects on the input value.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The input value to perform effects on.</param>
/// <param name="actions">The actions to perform.</param>
/// <returns>Unit.</returns>
public static async Task<Unit> EffectAsync<T>(this Task<T> input, params Func<T, Task>[] actions) =>
await input.PipeAsync(actions);

/// <summary>
/// Perform effects ignoring the input value.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <param name="input">The ignored input value.</param>
/// <param name="actions">The actions to perform.</param>
/// <returns>Unit.</returns>
public static async Task<Unit> EffectAsync<T>(this Task<T> input, params Func<Task>[] actions) =>
await input.PipeAsync(actions);

/// <summary>
/// Wraps an object with a ValueTask to be used with Async functions.
Expand Down Expand Up @@ -232,6 +301,20 @@ public static async Task<Unit> PipeAsync<TInput>(this Task<TInput> input, params
.AsAsync()
.PipeAsync(_ => Unit.Default);

/// <summary>
/// Used to perform actions that return only a task.
/// </summary>
/// <typeparam name="TInput">The type of input that is ignored.</typeparam>
/// <param name="input">The ignored input.</param>
/// <param name="actions">Actions to be performed which result in a task.</param>
/// <returns>Unit.</returns>
public static async Task<Unit> PipeAsync<TInput>(this Task<TInput> input, params Func<Task>[] actions)
{
await input;
actions.ToList().ForEach(async action => await action());
return Unit.Default;
}

/// <summary>
/// Used to wrap an async input that performs actions.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Functional/Results/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Unit Effect(Action<Ok> doWhenOk, Action<Error> doWhenError) =>
/// Perform a side effect on a result type when the type is Ok.
/// </summary>
/// <param name="doWhenOk">An action to perform on an Ok result.</param>
public Unit Effect(Action<Ok> doWhenOk) =>
public Unit EffectOk(Action<Ok> doWhenOk) =>
Union
.Effect(
ok => doWhenOk(ok.Contents),
Expand Down
116 changes: 116 additions & 0 deletions docs/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,41 @@ public static class Program
todoItem => Console.WriteLine(todoItem.Title),
() => { /* We don't have to print anything actually... */ });
}
```
### Option EffectSome and EffectNone

If we only want to perform an effect when an `Option` is `Some` or when it's `None`, we can use the `EffectSome` and `EffectNone` methods.


```C# title="Program.cs" linenums="1" hl_lines="13 17 21 25"
namespace ScratchPad;

using Functional.Options;

using System;

public static class Program
{
public static void Main()
{
// This will print "value" to the console.
Option.Some("value")
.EffectSome(value => Console.WriteLine(value));

// This will do nothing since the input value was a None.
Option.None<string>()
.EffectSome(value => Console.WriteLine(value));

// This will do nothing since the input is a Some.
Option.Some("value")
.EffectNone(() => Console.WriteLine("won't print"));

// This will print "no value" since the input was None.
Option.None<string>()
.EffectNone(() => Console.WriteLine("no value"));
}
}

```

### Async Options
Expand Down Expand Up @@ -910,6 +945,41 @@ public static class Program
}
```

### Result EffectOk and EffectError

Perform an `Effect` when a `Result` is `Ok` or when it is an `Error`.

```C# title="Program.cs" linenums="1" hl_lines="13 17 21 25"
namespace ScratchPad;

using Functional.Results;

using System;

public static class Program
{
public static void Main()
{
// This will print "value" to the console.
Result.Ok("value")
.EffectOk(value => Console.WriteLine(value));

// This will do nothing since the input value was an Error.
Result.Error<string>(new Exception("Something bad happened"))
.EffectOk(value => Console.WriteLine(value));

// This will do nothing since the input value is Ok
Result.Ok("value")
.EffectError(exception => Console.WriteLine(exception.Message));

// This will print "Something bad happened" since it was an error.
Result.Error<string>(new Exception("Something bad happened"))
.EffectError(exception => Console.WriteLine(exception.Message));
}
}
```


### Async Results

Asynchronous support is also provided in this library for `Result`.
Expand Down Expand Up @@ -1039,6 +1109,27 @@ public static class Program
}
```

### Effect

`Effect` is like a `Pipe` that consumes the input, performs some series of actions, and returns `Unit`.

```C# title="Program.cs" linenums="1" hl_lines="12"
namespace ScratchPad;

using Functional.Common;

using System;

public static class Program
{
public static void Main()
{
"Some Random Value"
.Effect(input => Console.WriteLine(input));
}
}
```

### Cons

`Cons` generates an `ImmutableList` of any type that you put in it. In .NET 8 and C# 12, Collection Expressions and Collection Literals help reduce the need for this, but it can still be useful in older versions. See example below for usage.
Expand Down Expand Up @@ -1240,3 +1331,28 @@ public static class Program
}
}
```

### Unit

When performing an action, instead of returning void, we can return the type called `Unit` which represents no return value. This can be used to continue piping more functions after performing an action that would return void. Calling the second `Effect` would not have been possible without the `Unit` type.

```C# title="Program.cs" linenums="1" hl_lines="14 15"
namespace ScratchPad;

using Functional.Common;
using Functional.Options;

using System;

public static class Program
{
public static void Main()
{
// This should print "value" and then "()" on the following line.
Option.Some("value")
.Effect(value => Console.WriteLine(value), () => Console.WriteLine("No value"))
.Effect(unit => Console.WriteLine(unit.ToString()));
}
}

```

0 comments on commit 6528645

Please sign in to comment.