Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/src/analyzer-rules/λ1005.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ static int Example(Option<int> option)
=> option.Match(none: () => 42, some: Identity);

static int Example(Result<int> result)
=> result.Match(ok: x => x, error: _ => 42);
=> result.Match(error: _ => 42, ok: x => x);
```

### Allowed
Expand Down
2 changes: 1 addition & 1 deletion Documentation/src/analyzer-rules/λ1006.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static Option<int> Example(Option<int> option, Option<int> fallback)
=> option.Match(none: () => fallback, some: x => Option.Some(x));

static Result<int> Example(Result<int> result, Result<int> fallback)
=> result.Match(ok: Result.Return, error: _ => fallback);
=> result.Match(error: _ => fallback, ok: Result.Return);
```

### Allowed
Expand Down
4 changes: 2 additions & 2 deletions Documentation/src/either.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Every operation from [`Option<T>`](./option.md) and `Result<T>` exists on `Eithe
|-----------------------|------------------------|
| `Ok(value)` | `Right(value)` |
| `Error(exception)` | `Left(value)` |
| `Match(ok, error)` | `Match(left, right)` |
| `Switch(ok, error)` | `Switch(left, right)` |
| `Match(error, ok)` | `Match(left, right)` |
| `Switch(error, ok)` | `Switch(left, right)` |
| `InspectError` | `InspectLeft` |

`Select`, `SelectMany`, `OrElse`, `GetOrElse` and `Inspect` work the same way, and so does query syntax. As with
Expand Down
4 changes: 2 additions & 2 deletions Documentation/src/fp/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ where the two tracks merge back into a plain value and the caller decides what t

```cs
PlaceOrder(request).Match(
ok: order => Results.Created($"/orders/{order.Id}", order),
error: exception => Results.BadRequest(exception.Message));
error: exception => Results.BadRequest(exception.Message),
ok: order => Results.Created($"/orders/{order.Id}", order));
```

The one discipline the style asks for is to stay on the tracks until the end. Unwrapping a `Result<T>` in the
Expand Down
8 changes: 4 additions & 4 deletions Documentation/src/result.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ difference is that the empty case is called `error` instead of `none`, and that
|--------------------|--------------------------|
| `Some(value)` | `Ok(value)` |
| `None` | `Error(exception)` |
| `Match(none, some)`| `Match(ok, error)` |
| `Switch(none, some)`| `Switch(ok, error)` |
| `Match(none, some)`| `Match(error, ok)` |
| `Switch(none, some)`| `Switch(error, ok)` |
| `InspectNone` | `InspectError` |
| `ToNullable` | `GetOrThrow` |

Expand Down Expand Up @@ -116,8 +116,8 @@ As with options, `Match` is the general tool and should be the last one you reac

```cs
IActionResult response = PlaceOrder(customerId, productId, quantity).Match(
ok: order => Created(order),
error: exception => BadRequest(exception.Message));
error: exception => BadRequest(exception.Message),
ok: order => Created(order));
```

`Switch` is the same for actions. Always name the arguments; [λ1003](./analyzer-rules/λ1003.md) enforces it, and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public readonly struct Result<TValidResult>

public Result<TResult> SelectMany<TResult>(System.Func<TValidResult, Result<TResult>> selector) where TResult : notnull => default;

public TMatchResult Match<TMatchResult>(System.Func<TValidResult, TMatchResult> ok, System.Func<System.Exception, TMatchResult> error) => default!;
public TMatchResult Match<TMatchResult>(System.Func<System.Exception, TMatchResult> error, System.Func<TValidResult, TMatchResult> ok) => default!;
}

public static class Result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public readonly struct Result<TValidResult>

public TValidResult GetOrElse(System.Func<System.Exception, TValidResult> fallback) => default!;

public TMatchResult Match<TMatchResult>(System.Func<TValidResult, TMatchResult> ok, System.Func<System.Exception, TMatchResult> error) => default!;
public TMatchResult Match<TMatchResult>(System.Func<System.Exception, TMatchResult> error, System.Func<TValidResult, TMatchResult> ok) => default!;
}
}
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,21 @@ private static Option<MatchInvocation> MatchMatchInvocation(IInvocationOperation
=> invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName } }
&& alternativeMonadTypes.Value.TryGetValue(receiverType.ConstructedFrom, out var alternativeMonadType)
&& invocation.GetArgumentsInParameterOrder() is { Length: 2 } arguments
? [new MatchInvocation(
receiverType,
alternativeMonadType,
arguments[alternativeMonadType.ErrorStateArgumentIndex],
arguments[alternativeMonadType.SuccessStateArgumentIndex]),
]
&& ErrorStateIndex(arguments, alternativeMonadType) is { } errorStateIndex
? [new MatchInvocation(receiverType, alternativeMonadType, arguments[errorStateIndex], arguments[1 - errorStateIndex])]
: [];

// The arguments are told apart by parameter name, so both the Funcky 3.x and the 4.0.0 order of Result.Match work.
private static int? ErrorStateIndex(ArgumentsInParameterOrder arguments, AlternativeMonadType alternativeMonadType)
{
if (arguments[0].Parameter?.Name == alternativeMonadType.ErrorStateParameterName)
{
return 0;
}

return arguments[1].Parameter?.Name == alternativeMonadType.ErrorStateParameterName ? 1 : null;
}

private static Diagnostic? AnalyzeMatchInvocation(
IInvocationOperation operation,
MatchInvocation match)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal sealed class AlternativeMonadType(
INamedTypeSymbol constructorsType,
string returnAlias,
string errorStateConstructorName,
bool matchHasSuccessStateFirst = false,
string errorStateParameterName,
INamedTypeSymbol? extensionsType = null)
{
private readonly Lazy<bool> _hasGetOrElse = HasMethodLazy(type, GetOrElseMethodName);
Expand All @@ -23,9 +23,10 @@ internal sealed class AlternativeMonadType(

public string ErrorStateConstructorName { get; } = errorStateConstructorName;

public int ErrorStateArgumentIndex => matchHasSuccessStateFirst ? 1 : 0;

public int SuccessStateArgumentIndex => matchHasSuccessStateFirst ? 0 : 1;
/// <summary>The name of the <c>Match</c> parameter that handles the error state (<c>none</c>, <c>left</c>, <c>error</c>). The
/// arguments are told apart by name rather than by position, so the analyzer works with Funcky 3.x, where <c>Result.Match</c>
/// took the success state first, and with Funcky 4.0.0, where the error state comes first everywhere.</summary>
public string ErrorStateParameterName { get; } = errorStateParameterName;

public bool HasGetOrElse => _hasGetOrElse.Value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private static void AddAlternativeMonadType(IDictionary<ISymbol, AlternativeMona
optionOfTType,
constructorsType: optionType,
errorStateConstructorName: OptionNonePropertyName,
errorStateParameterName: OptionMatchNoneParameterName,
returnAlias: OptionSomeMethodName,
extensionsType: compilation.GetOptionExtensionsType())
: null;
Expand All @@ -47,6 +48,7 @@ private static void AddAlternativeMonadType(IDictionary<ISymbol, AlternativeMona
eitherOfTType,
constructorsType: eitherType,
errorStateConstructorName: EitherLeftMethodName,
errorStateParameterName: EitherMatchLeftParameterName,
returnAlias: EitherRightMethodName)
: null;

Expand All @@ -57,7 +59,7 @@ private static void AddAlternativeMonadType(IDictionary<ISymbol, AlternativeMona
resultOfTType,
constructorsType: resultType,
errorStateConstructorName: ResultErrorMethodName,
matchHasSuccessStateFirst: true,
errorStateParameterName: ResultMatchErrorParameterName,
returnAlias: ResultOkMethodName)
: null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public static class FunckyWellKnownMemberNames

public const string OptionNonePropertyName = "None";

/// <summary>The <c>Match</c> parameters that handle the error state of each alternative monad.</summary>
public const string OptionMatchNoneParameterName = "none";

public const string EitherMatchLeftParameterName = "left";

public const string ResultMatchErrorParameterName = "error";

public const string EitherRightMethodName = "Right";

public const string EitherLeftMethodName = "Left";
Expand Down
14 changes: 7 additions & 7 deletions Funcky/Monads/Result/Result.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public static Result<TValidResult> Error(Exception exception)

[Pure]
[UseWithArgumentNames]
public TMatchResult Match<TMatchResult>(Func<TValidResult, TMatchResult> ok, Func<Exception, TMatchResult> error)
public TMatchResult Match<TMatchResult>(Func<Exception, TMatchResult> error, Func<TValidResult, TMatchResult> ok)
=> _state switch
{
State.Ok => ok(_result),
Expand All @@ -105,7 +105,7 @@ public TMatchResult Match<TMatchResult>(Func<TValidResult, TMatchResult> ok, Fun
};

[UseWithArgumentNames]
public void Switch(Action<TValidResult> ok, Action<Exception> error)
public void Switch(Action<Exception> error, Action<TValidResult> ok)
{
switch (_state)
{
Expand Down Expand Up @@ -135,20 +135,20 @@ public bool Equals(Result<TValidResult> other)
public override int GetHashCode()
=> Match(
uninitialized: static () => 0,
ok: static result => result.GetHashCode(),
error: static error => error.GetHashCode());
error: static error => error.GetHashCode(),
ok: static result => result.GetHashCode());

[Pure]
public override string ToString()
=> Match(
uninitialized: static () => "Uninitialized",
ok: static result => $"Ok({result})",
error: static exception => $"Error({exception.GetType().FullName}: {exception.Message})");
error: static exception => $"Error({exception.GetType().FullName}: {exception.Message})",
ok: static result => $"Ok({result})");

/// <summary>A total match that also handles the invalid state of a <c>default</c> value; for members that must never throw.</summary>
[Pure]
[UseWithArgumentNames]
internal TMatchResult Match<TMatchResult>(Func<TMatchResult> uninitialized, Func<TValidResult, TMatchResult> ok, Func<Exception, TMatchResult> error)
internal TMatchResult Match<TMatchResult>(Func<TMatchResult> uninitialized, Func<Exception, TMatchResult> error, Func<TValidResult, TMatchResult> ok)
=> _state switch
{
State.Ok => ok(_result),
Expand Down
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/net10.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwait<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, int, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/net11.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,7 @@ static Funcky.Extensions.StringExtensions.LastIndexOfOrNone(this string! haystac
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwait<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, int, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*static Funcky.Extensions.AsyncEnumerableExtensions.WhereSelectAwaitWithCancellation<TSource, TResult>(this System.Collections.Generic.IAsyncEnumerable<TSource>! source, System.Func<TSource, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<Funcky.Monads.Option<TResult>>>! selector) -> System.Collections.Generic.IAsyncEnumerable<TResult>!
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/net8.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
*REMOVED*static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option<Funcky.Unit>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, System.Func<TItem>! selector) -> Funcky.Monads.Option<TItem>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, TItem item) -> Funcky.Monads.Option<TItem>
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/net9.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
*REMOVED*static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option<Funcky.Unit>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, System.Func<TItem>! selector) -> Funcky.Monads.Option<TItem>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, TItem item) -> Funcky.Monads.Option<TItem>
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
*REMOVED*static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option<Funcky.Unit>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, System.Func<TItem>! selector) -> Funcky.Monads.Option<TItem>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, TItem item) -> Funcky.Monads.Option<TItem>
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 4 additions & 0 deletions Funcky/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
*REMOVED*static Funcky.Monads.Option.FromBoolean(bool boolean) -> Funcky.Monads.Option<Funcky.Unit>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, System.Func<TItem>! selector) -> Funcky.Monads.Option<TItem>
*REMOVED*static Funcky.Monads.Option.FromBoolean<TItem>(bool boolean, TItem item) -> Funcky.Monads.Option<TItem>
*REMOVED*Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<TValidResult, TMatchResult>! ok, System.Func<System.Exception!, TMatchResult>! error) -> TMatchResult
*REMOVED*Funcky.Monads.Result<TValidResult>.Switch(System.Action<TValidResult>! ok, System.Action<System.Exception!>! error) -> void
Funcky.Monads.Result<TValidResult>.Match<TMatchResult>(System.Func<System.Exception!, TMatchResult>! error, System.Func<TValidResult, TMatchResult>! ok) -> TMatchResult
Funcky.Monads.Result<TValidResult>.Switch(System.Action<System.Exception!>! error, System.Action<TValidResult>! ok) -> void
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ Result<Order> order = ParseOrder(json)
.SelectMany(Price);

return order.Match(
ok: o => Results.Ok(o),
error: e => Results.BadRequest(e.Message));
error: e => Results.BadRequest(e.Message),
ok: o => Results.Ok(o));
```

### IEnumerable extensions
Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Funcky adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
single-name overloads with a `(…, cancellationToken)` delegate. The code fix in Funcky.Analyzers 3.0.0 now also fires
on the compile error CS1061 that a removed name leaves behind, so a project can upgrade first and let Fix-All rewrite
every call afterwards; the fix for the obsoletion warning CS0618 on 3.8.0 stays as well.
* Breaking: `Result<T>.Match` and `Result<T>.Switch` take the error case first, `Match(error, ok)` and
`Switch(error, ok)`, consistent with `Option.Match(none, some)` and `Either.Match(left, right)`. Both methods carry
`[UseWithArgumentNames]`, so calls that follow λ1003 and name their arguments are unaffected; a positional call fails
to compile after the upgrade (the two delegates have different parameter types), swap its arguments. The `Match`
rules of Funcky.Analyzers (λ1005 to λ1007 and λ1011) now tell the two arguments apart by parameter name, so they work
with either order.
* Funcky and Funcky.Async add a `net11.0` asset and are built with the .NET 11 SDK (release candidate 3 while .NET 11
is in preview). The `net11.0` asset of Funcky.Async only forwards to Funcky, like the `net10.0` one. No target
framework is removed in this step; see `SupportPolicy.md` for the frameworks Funcky 4 drops.
Expand Down
Loading