Changelog
All notable changes to this project will be documented in this file. Funcky adheres to Semantic Versioning.
Funcky 3.8.0 | Funcky.Async 1.6.0 | Funcky.Xunit 2.3.0 | Funcky.Xunit.v3 1.1.0 | Funcky.Analyzers 2.1.0
- Add
InRange<TFrom, TTo>(from, to)extension (.NET 7+) on all BCL numeric types to check whether a value lies within an interval. The boundary kinds are given as type argumentsIncluding/Excluding, e.g.5.InRange<Including, Excluding>(0, 10). Custom types implementingIComparisonOperators<T, T, bool>can use the staticNumberExtensions.InRange<TNumber, TFrom, TTo>. - The extension methods of Funcky, Funcky.Async and Funcky.Xunit are now C# 14 extension members, declared in
extension(...)blocks. The compiled methods keep their names andthisparameters, so the change is source and binary compatible. A few methods stay classic extension methods:Option.ToEitherand theComposeoverloads with a non-unary receiver (an extension member would reorder their type parameters, which breaks callers that name the type arguments),Lazy.Flatten(the compiler does not copy the trimming annotation of a block type parameter onto its marker type, andFlattenreturnsLazy<T>of that parameter, which the trimmer rejects with IL2091), and the source-generated…OrNoneandApplymethods. Consuming theparamsextension members (Interleave,And,Or,SplitLazy) with their arguments spread out needs the C# compiler of the .NET SDK 10.0.2xx or newer; the 10.0.1xx compiler reports a bogus CS8620 per argument forparamson extension members in referenced assemblies. - Every
Parse…OrNoneextension onstringis now also a static extension member on the type it parses, so the function is found where the BCLTryParselives:int.ParseOrNone(text),DateTime.ParseExactOrNone(text, format, provider, styles),MyEnum.ParseOrNone(text),Enum.ParseOrNone(typeof(MyEnum), text),MediaTypeHeaderValue.ParseOrNone(text), and on .NET 7+MyParsable.ParseOrNone(text)for everyIParsable<T>/ISpanParsable<T>/IUtf8SpanParsable<T>andMyNumber.ParseOrNone(text, style, provider)for everyINumberBase<T>. Every overload of thestringform is mirrored. The static members live in one class per parsed type (Int32ParseExtensions,DateTimeParseExtensions,EnumParseExtensions,ParsableParseExtensions,NumberParseExtensions, …), because the C# compiler does not allow static extension members of different receiver types with the same name and parameters in one class. Consumers on a compiler older than C# 14 see them as plain static methods of these classes (Int32ParseExtensions.ParseOrNone(text)). Thestringextensions stay on the 3.x line, but the static form is the recommended one: 4.0.0 decides the fate of the classic forms (#1003), and moving early keeps that migration small. Funcky.Analyzers 2.1.0 ships λ1201 with a code fix for it. - The
SequenceconstructorsReturn,FromNullable,Successors,Cycle,CycleRange,CycleMaterialized,RepeatRange,RepeatMaterializedandConcatare now also static extension members ofSystem.Linq.Enumerable, next toEnumerable.RangeandEnumerable.Repeat:Enumerable.Successors(1, n => n + 1).Enumerable.Concatwith exactly two sequences keeps binding to the BCL method, which does the same. The members are declared inEnumerableExtensions, which is what consumers on a compiler older than C# 14 see, and are the implementation; theSequenceconstructors forward to them. - Add
Result.Try(Func<T>)andResult.TryAsync(Func<Task<T>>), which run a function and capture its outcome asOk(value)orError(exception):Result.Try(() => int.Parse(text)). Exceptions that signal a corrupted process (OutOfMemoryException,StackOverflowException,AccessViolationException,ThreadAbortException) are never captured, and neither isOperationCanceledException, since a cancelled operation has no result. - Add
Thenonboolas the replacement ofOption.FromBoolean, modelled on Rust'sbool::then:(number % 2 == 0).Then(number)isSome(number)when the condition holds andNoneotherwise.Then(() => value)evaluates the value lazily, andThen(() => option)flattens, which replaces theOption.FromBoolean(condition, option).SelectMany(Identity)idiom:request.HasFormContentType.Then(() => request.Form.Files.FirstOrNone()). The members are declared inBooleanExtensions. - Deprecated:
Option.FromBoolean(all three overloads) is obsolete and will be removed in Funcky 4.0.0; useThenon the condition instead. Funcky.Analyzers 2.1.0 ships a code fix for the obsoletion warning (see below). - Funcky.Xunit and Funcky.Xunit.v3: the assertions of
FunctionalAssertare now also static extension members of xunit'sAssert, so they are found where the other assertions live:Assert.Some(option),Assert.None(option),Assert.Ok(result),Assert.Error(result),Assert.Left(either)andAssert.Right(either);Some,Ok,LeftandRightalso come with the overload that takes the expected value. TheAssertmembers are the implementation;FunctionalAssertforwards to them and stays the form for compilers older than C# 14 (the members are declared inAssertExtensions). - Funcky.Xunit.v3 now accepts xUnit.net v3 1.x through 4.x (
xunit.v3.assertandxunit.v3.extensibility.core[1.0.1, 5), previously[1.0.1, 2)). The package is still compiled against 1.0.1; the types it uses (Assert,XunitException,ArgumentFormatter,IXunitSerializer,SerializationHelper) are unchanged in 2.0, 3.0 and 4.0, and the test suite passes against 4.0.2. The Testing with xUnit chapter has a compatibility table with the test host and runner requirements of each xunit version. - The asynchronous overloads of
AdjacentGroupBy,Chunk,ExclusiveScan,InclusiveScan,InspectandWhereSelectnow follow the .NET 10 naming ofSystem.Linq.AsyncEnumerable: one name per operator, with the asynchronous delegate taking the cancellation token of the enumeration,xs.Inspect(async (item, cancellationToken) => …). This applies to the net10.0 asset of Funcky and to Funcky.Async alike. - The same for the terminal aggregations
AverageOrNoneAsync,FirstOrNoneAsync,LastOrNoneAsync,MaxOrNoneAsync,MinOrNoneAsync,NoneAsync,PartitionAsyncandSingleOrNoneAsync: they gain overloads whose asynchronous delegate receives the cancellation token,xs.FirstOrNoneAsync(async (item, cancellationToken) => …). - Deprecated: the
…Awaitand…AwaitWithCancellationoverloads of the six lazy operators (InspectAwait,InspectAwaitWithCancellation, …) and the…AwaitAsyncand…AwaitWithCancellationAsyncoverloads of the eight terminal aggregations (FirstOrNoneAwaitAsync,FirstOrNoneAwaitWithCancellationAsync, …) are obsolete and will be removed in Funcky 4.0.0.…AwaitWithCancellationis a rename;…Awaitadditionally gains the token parameter, which can be discarded:(item, _) => …. The obsolete overloads stay a warning for the whole 3.x line. Funcky.Analyzers 2.1.0 ships a code fix with fix-all support (see below). - Add
OrNoneextension properties (C# 14) for base class library members that returnnullas a regular outcome:Type.BaseTypeOrNone,MemberInfo.DeclaringTypeOrNone,Assembly.EntryPointOrNone,Exception.InnerExceptionOrNone,DirectoryInfo.ParentOrNoneandHttpResponseHeaders.LocationOrNone. They are the first extension properties in Funcky; see the Nullable Member Extensions chapter. - Announcement: Funcky.DiscriminatedUnion is retired with
Funcky 4.0.0, because C# gets native
uniontypes andclosedhierarchies with .NET 11, which Funcky 4.0.0 adopts. The generator keeps working with the 3.x line and receives no new features. Details in #900.
Analyzers (Funcky.Analyzers 2.1.0)
- New: a code fix for the obsoletion warning CS0618 on
Option.FromBoolean.Option.FromBoolean(condition, value)becomescondition.Then(value), the selector overload keeps its selector,Option.FromBoolean(condition)becomescondition.Then(Unit.Value), andOption.FromBoolean(condition, option).SelectMany(Identity)becomescondition.Then(() => option). The fix addsusing Funcky.Extensions;when needed and supports fix-all. A call whose selector returns anOptionand that is not flattened gets no fix, becauseThenwould flatten it. - The refactoring "Replace with Option.FromBoolean" on
Option.Some(value).Where(predicate)now producespredicate.Then(value)and is named "Replace with Then". - New: a code fix for the obsoletion warning CS0618 on the
…Awaitand…AwaitWithCancellationoperators ofAsyncEnumerableExtensions.xs.InspectAwaitWithCancellation((x, ct) => …)becomesxs.Inspect((x, ct) => …),xs.InspectAwait(x => …)becomesxs.Inspect((x, _) => …)with the ignored token parameter added to every delegate argument, and a method group or delegate value becomes a forwarding lambda,xs.Inspect((x, _) => Log(x)). The terminal aggregations map the same way,xs.FirstOrNoneAwaitAsync(x => …)becomesxs.FirstOrNoneAsync((x, _) => …). The fix supports fix-all across a document, project or solution, so a consumer migrates in one step before 4.0.0. - New: three opt-in rules with code fixes point at the C# 14 extension forms that 3.8.0 adds next to the classic API, so a solution adopts them in one fix-all. They only fire on C# 14 or newer and skip method groups, which have no static-member form of the same shape:
- Fix: λ1101 names the receiver as written in its message (
Assert.Some); for a static extension member it used to print the unnamed extension block (.Some). - Fix: the code fixes that add
using Funcky.Extensions;(λ1012, λ1201, λ1202, λ1203 and theOption.FromBooleanfix) share a Fix-All provider that rewrites every flagged call of a document in one pass and adds the directive once. The batch fixer treated three or more identical directive insertions as colliding edits and dropped those fixes, call rewrite included, so a second Fix-All pass was needed (#1009, #1020).
Funcky 3.7.0 | Funcky.Analyzers 2.0.0
The analyzer release: Funcky itself gains built-in correctness rules, Funcky.Analyzers gains eight style rules and a
code fix for a compiler warning, and the two packages settle which kind of rule lives where. The analyzer package becomes
2.0.0 because it drops a supported Roslyn version, which is a breaking change under semantic versioning.
Analyzers
The analyzer rules now follow one written rule for which package a rule lives in: a rule is built into the Funcky
package when the flagged code is outside the supported surface of the library (the code is wrong, not merely
unidiomatic), and it lives in the opt-in Funcky.Analyzers package when the code works but Funcky offers a more
idiomatic form. The built-in rules report errors and carry the category Funcky.Correctness, the opt-in rules report
warnings (an info for pure performance hints), come with a code fix and carry the category Funcky.Style. See the
analyzer rules page.
- Breaking (Funcky.Analyzers): the package no longer ships an asset for Roslyn 4.0; the analyzers require Roslyn 4.4 (Visual Studio 2022 17.4, .NET SDK 7.0.100) or newer. The Roslyn 4.0 asset was never tested and targeted a Visual Studio version that is out of support.
- Breaking:
λ1009"Do not usedefaultto instantiate this type" andλ1010"An option has either zero or one elements" moved fromFuncky.Analyzersinto the built-in analyzers, so every consumer ofFunckynow gets them without installing the analyzer package. The rule IDs stay the same, existing.editorconfigentries keep working. Funcky.Analyzers 2.0.0 no longer contains the two rules; an older Funcky.Analyzers next to Funcky 3.7.0 reportsλ1009twice until it is updated. - Breaking: the diagnostic category of every rule changed from
FunckytoFuncky.Correctness(built-in) orFuncky.Style(opt-in). Adotnet_analyzer_diagnostic.category-Funcky.severityentry in an.editorconfigneeds to name the new categories; per-ruledotnet_diagnostic.λ….severityentries are unaffected. The split lets you tone down all style rules with one line without touching the correctness rules. - New:
FunckyDisableBuiltinAnalyzersMSBuild property. Setting it totrueremoves the built-in analyzers from the build, the escape hatch for environments where they cannot run.ExcludeAssets="analyzers"on the package reference has the same effect. - Fix: the built-in analyzers are shipped once per supported Roslyn version (
roslyn3.7androslyn4.4) and the package'sbuildTransitivetargets keep only theroslyn3.7assets on SDKs without analyzer versioning, the same arrangement System.Text.Json uses.λ1010needs the Roslyn 4.4 list pattern API and is only active there, which matches the compilers that can use list patterns onOption<T>at all. - New: the attributes behind
λ1003([UseWithArgumentNames]) andλ1009([NonDefaultable]) can be used on your own members. The analyzers now match the attributes by name, so a copy in any assembly counts, andFuncky.Analyzersgenerates that copy when the project sets<FunckyCodeAnalysisAttributes>UseWithArgumentNames;NonDefaultable</FunckyCodeAnalysisAttributes>. - Fixed:
λ1003andλ1009went silent in a project referencing two assemblies that both define the attribute. - Fix: the built-in analyzer λ0001 applied its allowance rules inconsistently.
TryGetValueis now allowed anywhere inside acatchfilter clause, not only as its outermost expression, socatch when (option.TryGetValue(out var h) && h.CanHandle(e))compiles. Conversely, a call inside a lambda or local function that is merely used by a loop condition, a catch filter or anifcondition in an iterator, such aswhile (items.Any(i => i.Option.TryGetValue(out _))), is now reported; it used to slip through because the check walked all ancestors. The exemption for.cshtmlfiles finally applies: it recognizes Razor pages and views deriving fromRazorPageBase, where it used to require a BlazorComponentBase. The documentation of λ0001 now lists every allowed use case, includingifstatements that yield. - New: built-in rule
λ0004reports a baredefaultliteral in a branch of a conditional,switchor??expression that is implicitly converted toOption<T>,Result<T>orEither<L, R>. The literal takes the type of the sibling branch, socondition ? 42 : defaultisSome(0)and neverNone; for value types and tuples the compiler is silent about it. The code fix replaces the literal withOption<T>.None. - New: built-in rule
λ0005reports anullliteral that is converted toOption<T>,Result<T>orEither<L, R>or passed to one of their factory methods (Option.Some,Result.Ok,Either<L, R>.Left, …). The monads rejectnullwith anArgumentNullException, soOption<string> option = null;and evenoption == nullthrow at run time; with nullable reference types disabled the compiler is silent about it. The code fix replaces the literal withOption<T>.None. - New: rule
λ1011reports aMatchwhose failure branch returns the monad's own empty or error state and whose success branch returns a plain value, e.g.option.Match(none: Option<string>.None, some: x => x.ToString()): that isSelect. The code fix rewrites the call forOption<T>,Result<T>andEither<L, R>. - New: rule
λ1012reportsFirstOrDefault,LastOrDefault,SingleOrDefaultandElementAtOrDefaultwhose result is immediately tested fornull(?? fallback,?.,is null,is {} x,== null, …) and suggests the…OrNonevariant, which makes the missing element aNoneinstead of anull. The code fix rewritesxs.FirstOrDefault() ?? fallbacktoxs.FirstOrNone().GetOrElse(fallback), passes a fallback that does work as a function and adds theusing Funcky.Extensions;when needed. - New: rule
λ1013reportsSequence.Return(x)in a position with a target type ofIEnumerable<T>,IReadOnlyCollection<T>orIReadOnlyList<T>and suggests the collection expression[x], which the code fix produces. It needs C# 12 and only fires where the collection expression binds the same way and keeps the element type;var x = Sequence.Return(1)and the receiver of a LINQ chain are left alone. The rule reports an info. - New: rule
λ1014reports athrowexpression inside a fallback passed to the eager overload ofGetOrElse,OrElseorMatchonOption<T>,Result<T>andEither<L, R>, e.g.option.GetOrElse(fallback ?? throw …). The argument is evaluated before the call, so it throws even when there is a value. The code fix passes a function instead. - New: rule
λ1015reports a fallback passed to the eager overload ofGetOrElse,OrElseorMatchonOption<T>,Result<T>andEither<L, R>that does work (a method call, an object creation, an interpolated string, anawait), e.g.option.GetOrElse(LoadDefault()): the work happens even when there is a value. The code fix passes a function instead. As a performance hint rather than a correctness issue the rule reports an info, the first rule in the package to do so. - New: rule
λ1016reports a lambda passed to the lazy overload ofGetOrElse,OrElseorMatchonOption<T>,Result<T>andEither<L, R>that only returns a literal, a constant, a variable or a member, e.g.option.GetOrElse(() => 0f): there is no work to defer, so the eager overload taking the value says the same with less. The code fix replaces the lambda with its body. - New: rule
λ1017reports a lambda that spells out one of the named functions inFuncky.Functional:x => xisIdentity,_ => trueisTrue,_ => falseisFalseand() => { }isNoOperation. It only reports where the method group binds the same way as the lambda, so an ambiguousWhere(True)is left alone. The code fix replaces the lambda and addsusing static Funcky.Functional;when needed. Before C# 11 the rule reports an info, because the compiler only caches method group delegates from C# 11 on. - New: rule
λ1018reportsSelectMany(x => x)andSelectMany(Identity)on a nestedOption,Result,EitherorIEnumerableand suggestsFlatten(), which says what happens without a selector to read. The code fix replaces the call. - New: a code fix for the compiler warning CS8714 ("Nullability of type argument doesn't match 'notnull' constraint") on
Option<T>.Select. A selector that can returnnullis rewritten toSelectManywithOption.FromNullable, sooption.Select(user => user.MiddleName)becomesoption.SelectMany(user => Option.FromNullable(user.MiddleName)). The fix handles expression-bodied and block-bodied lambdas and supports fix-all;EitherandResultget no fix because they need a failure value.
Funcky 3.6.1 | Funcky.Async 1.5.0 | Funcky.Xunit 2.2.0 | Funcky.Xunit.v3 1.0.0 | Funcky.Analyzers 1.4.2
- New:
AggregateOrNoneandAggregateOrNoneAsync, the…OrNonevariant of the seedlessAggregate, which throws for an empty sequence. The async variant also takes an accumulator receiving the cancellation token. - New:
And,OrandNotextension methods onFunc<T, bool>inFuncky.Extensions, the extension form ofAll,AnyandNot. The type argument is inferred from the receiver, soisActive.And(isAdmin)needs no<User>. - New:
ThenonFunc<…>, the forward form ofCompose:f.Then(g)isx => g(f(x)), so a pipeline reads in the order it runs. Overloads mirrorCompose, including a nullary function and anActionas the last step. - New:
FunctionalAssert.Error<TException, TValidResult>(result)asserts that aResultis an error of exactly the given exception type and returns it typed, the analogue ofAssert.Throws. - Fix: the xUnit v3
Optionserializer no longer throws anInvalidOperationException("TODO")when asked whether anullvalue is serializable; it answersfalseand reports a failure reason like the interface asks for. - Deprecation: the obsolete
ImmutableListExtensions(superseded byListExtensionsin 3.0) now announces its removal in Funcky 4.0.0 in the obsolete message. - Documentation:
SplitLinesstates that a trailing new line adds no empty line, that an empty text yields nothing and that only CR, LF and CRLF are recognized. - New:
UnitoverridesToString(printsUnitinstead of the type name) and implements the non-genericIComparable. - Fix:
Flatten(Option, Either, Lazy, IEnumerable),Option.ToEither,Option.ToNullable,Either.SelectLeft,Option.ToAsyncEnumerableand the stringChunkandSlidingWindoware marked[Pure]like their siblings. - New:
GetNonEnumeratedCountOrNoneis available on every target framework; it was limited to .NET 6 and later although the polyfill for the underlying check was already shipped. On older frameworks only collections with aCountare recognized. - Fix:
Option<T>.WhereandOption.FromBoolean(bool, T)no longer allocate a closure per call. - Fix:
SlidingWindowandSlidingWindowAsynckept the window in anImmutableQueueand built anImmutableListfor every window. They now use a ring buffer and copy each window into a read-only array, which is O(width) per window with a single allocation instead of a tree per window. - Fix:
WhereSelectand its async variants no longer allocate an enumerable per element; they are plain iterators now and validate their arguments when called like the other iterator-based extensions. - Fix:
AnyOrElsewith a fallback function invoked the function whenAnyOrElsewas called if the source was an empty collection, but when the result was enumerated otherwise. The function is now always invoked on enumeration. - Fix: every analyzer rule now carries a help link to its documentation page (
https://polyadic.github.io/funcky/analyzer-rules/<id>.html); previously only λ0001 had one. - Fix: the
FunctionalAssertcode fix (λ1101) was exported under the name of the argument-name code fix, which broke fix-all grouping in the IDE. - Fix: the
NonDefaultableandSyntaxSupportOnlymarker attributes are removed by the trimmer likeUseWithArgumentNamesalready was; all three are compile-time-only. - Packaging: the Funcky.Analyzers package declared its version in a property the SDK does not read, so every package
built since February 2025 outside a release was versioned 1.0.0. It uses
VersionPrefixlike the other packages. - Packaging: the Funcky and Funcky.Analyzers packages no longer ship the
tools/install.ps1anduninstall.ps1hooks, whichPackageReferencenever runs. - Fix:
AverageOrNoneandAverageOrNoneAsynconlong(andOption<long>) summed indoubleand lost precision for large values; they now sum exactly in a checkedlonglikeEnumerable.Average, so the result matches the BCL and a sum that overflowslongthrowsOverflowExceptioninstead of returning an inexact average. Thefloatoverloads sum indoublelike the BCL. The element count is along, so sequences with more than two billion elements are averaged correctly. Internally the three per-type aggregator classes became one allocation-free generic struct. - Fix:
InterleaveandMerge(sync and async) acquired the enumerators of all sources before entering thetrythat disposes them, so if theGetEnumeratorof one source threw, the enumerators of the sources before it were never disposed. They are now acquired inside thetryand the ones acquired so far are disposed. - Fix:
Sequence.RepeatRangeandAsyncSequence.RepeatRangereject a negativecountwith anArgumentOutOfRangeExceptionwhen they are called, likeRepeatMaterializedandEnumerable.Repeat. Previously a negative count silently yielded the source once. The XML docs of all three methods described the result as infinite; it contains the source exactlycounttimes. - Documentation: the Funcky.Xunit readme claimed that
AggressiveInliningremoves theFunctionalAssertframe from the stack trace of a failed assertion on frameworks without[StackTraceHidden]. The JIT never inlines a method with an exception handler, so the attribute was dead; it is removed and the readme now states that on .NET 6 and later the trace starts at the test method, and on older frameworks with a singleFunctionalAssertframe above it. Both behaviours are now covered by tests, the latter on .NET Framework 4.8. - Build: the public API of every package is now tracked per target framework. The
PublicAPI.Shipped.txtandPublicAPI.Unshipped.txtfiles moved toPublicAPI/<target framework>/and the public API analyzer runs for every target framework instead of only the newest one. The old single file listed the async APIs for frameworks that do not have them and did not know about APIs that only exist on older frameworks, so a breaking change on those was not detected. - Fix: the
OrNoneFromTryPatternsource generator handles try methods it previously generated broken code for or crashed on:in,refandref readonlyparameters are forwarded with their modifiers instead of being dropped from the signature, non-null default values (numbers, strings, chars, booleans, enums anddefaultstructs) are copied instead of throwing at generation time, overloads that do not follow the try pattern (no or severaloutparameters, non-boolreturn, instance methods) are skipped instead of crashing, and two attributed source files with the same file name no longer collide on the generated hint name. Generated methods now carry a summary, so theParse…OrNonefamily shows documentation in IntelliSense. - New: the generic
ParseOrNone<T>(string,ReadOnlySpan<char>and UTF-8ReadOnlySpan<byte>) andParseNumberOrNone<T>(string andReadOnlySpan<char>) gain overloads without theIFormatProvider, which use the current culture like the generatedParse…OrNonemethods, andParseNumberOrNone<T>gains overloads without theNumberStyles, which use the number type's default style. - Fix:
ParseNumberOrNone<T>on strings accepts anullcandidate and returnsNone, likeParseOrNone<T>and the generated parse methods. All generic parse methods are now marked[Pure]and documented. - Fix:
Sequence.Return(params T[])returned the array itself typed asIReadOnlyList<T>, so the result could be cast back to the array and modified. It now returns a read-only view over the array (Array.AsReadOnly), which still does not copy. The XML docs explain that an array passed in is viewed, not copied, and that passing an array binds to theparamsoverload rather than producing a one-element sequence. - Breaking: the generic
ParseEnumOrNone<TEnum>overloads are constrained tostruct, Enuminstead ofstruct, so"1".ParseEnumOrNone<int>()is a compile error instead of anArgumentExceptionat runtime. - Fix: the
stringoverloads ofParseEnumOrNoneaccept anullcandidate and returnNone, like every otherParse…OrNonemethod on strings. - Fix: on frameworks without
ExceptionDispatchInfo.SetCurrentStackTrace(.NET Framework, .NET Standard, .NET Core 3.1),Result<T>.Errorcaptures the stack trace by reflecting on private runtime members in a static initializer. On a runtime that names them differently or cannot compile expression trees, the firstErrorcall failed with aTypeInitializationException. The lookup is now defensive: if it fails, the stack trace is simply not captured andErrorworks as usual. - Fix:
OptionEqualityComparer<T>.DefaultandOptionComparer<T>.Defaultcreated a new comparer instance on every read, and everyEquals,GetHashCode,==andCompareToon anOption<T>went through them and through several delegate allocations. Both defaults are now cached per item type likeEqualityComparer<T>.Default, and equality, hashing and comparison of options no longer allocate. - Fix:
GetHashCodeandToStringofEither<L, R>andGetHashCodeofEitherOrBoth<L, R>threwNotSupportedExceptionfor a value constructed withdefault, so such a value brokeHashSet,Dictionary,Distinct, string interpolation and logging. Both members are now total likeEqualsalready was: a defaulted value hashes to 0 and prints asUninitialized.Match,Switchand everything built on them still throw. - New:
EitherOrBoth<L, R>overridesToString(Left(…),Right(…),Both(…, …)). - Fix: a
Result<T>constructed withdefaultbehaved likeOkwith anullvalue, bypassing the null check ofResult.Ok.Match,Switchand every operation built on them now throwNotSupportedExceptionfor such a value, likeEither<L, R>already did.Equals,GetHashCodeandToStringstay total. The analyzer λ1009 still reportsdefault(Result<T>)at compile time; see polyadic/funcky#934 for making it a built-in analyzer. - Fix:
ShuffleandShuffleAsyncwithout aRandomargument usednew Random()per call, which on .NET Framework is seeded from the clock, so two shuffles within the same tick returned the same order. They now draw fromRandom.Sharedon .NET 6 and later and from a shared, per-thread generator on older frameworks. - Fix: on frameworks before .NET 8,
Shufflebuilt its result by appending to anImmutableList, which is O(n log n) and allocates per element. It now shuffles an array in place like the .NET 8 implementation, so the result is O(n) and identical for a given seed on every target framework. Both overloads also drop the[Pure]attribute, since they advance the state of the random number generator. - Documentation: the XML docs of
ShuffleandShuffleAsyncclaimed deferred execution; both are eager. - Fix:
MemoizeandCycleRange/RepeatRangeforIEnumerableno longer acquire the source's enumerator when the buffer is created, but when the first element is requested, so a source that opens a resource or throws fromGetEnumeratordoes so lazily and a buffer that is disposed unused never touches the source. The source enumerator is disposed as soon as it is exhausted instead of when the buffer is disposed, andMemoizecan now be enumerated by several threads concurrently. TheIAsyncEnumerablecycle buffer no longer callsMoveNextAsyncon an exhausted source, which rewound a .NET 10 compiler-generated iterator and repeated the source instead of the buffer. - Fix: the read-only view that
Materializereturns for anIList<T>orICollection<T>source can no longer be used to modify the source through a cast to the mutable interface:IsReadOnlyistrueandAdd,Clear,Remove,Insert,RemoveAtand the indexer setter throwNotSupportedException, likeReadOnlyCollection<T>. The view still reflects later changes to the source;Materializeevaluates a lazy sequence once, it does not take a snapshot. - Fix: the iterator-based
IAsyncEnumerableextensions (AdjacentGroupByand itsAwaitvariants,AnyOrElse,Chunk,Inspect,InspectEmpty,Interleave,Memoize,Merge,Pairwise,PowerSet,InclusiveScan,ExclusiveScanand theirAwaitvariants,SlidingWindow,Split,Transpose,WithFirst,WithLast,WithPrevious,ZipLongest) andAsyncSequence.Successorsnow validate their arguments when they are called, like LINQ: anullsource, selector, comparer or fallback throws anArgumentNullExceptionnaming the parameter immediately, instead of aNullReferenceExceptionwhen the result is first enumerated. - Fix:
WithFirst,WithLastandZipLongestforIAsyncEnumerablenow pass the cancellation token given toGetAsyncEnumerator(e.g. viaWithCancellation) on to the source sequence. - Fix: the iterator-based
IEnumerableextensions (AdjacentGroupBy,AnyOrElse,Chunk,Inspect,InspectEmpty,Interleave,Memoize,Merge,Pairwise,PowerSet,InclusiveScan,ExclusiveScan,SlidingWindow,Split,Transpose,WithFirst,WithLast,WithPrevious,ZipLongest) andSequence.Successorsnow validate their arguments when they are called, like LINQ: anullsource, selector, comparer or fallback throws anArgumentNullExceptionnaming the parameter immediately, instead of aNullReferenceExceptionwhen the result is first enumerated. - Breaking:
SplitonIEnumerable<T>andIAsyncEnumerable<T>now behaves exactly likestring.Split: a separator at the end of the source produces a trailing empty part, and an empty source produces a single empty part. Previously both cases swallowed the empty part, so[1, 0].Split(0)gave[[1]]while[0, 1].Split(0)gave[[], [1]], and the source could not be reconstructed from the parts. The result now always has exactly one part more than there are separators. - Fix:
MinByOrNoneandMaxByOrNonenow treatnullkeys likeEnumerable.MinBy/Enumerable.MaxBy: elements whose key isnullare never chosen as the minimum or maximum, and if all keys arenullthe first element is returned. PreviouslyMinByOrNonereturned the first element with anullkey, because the default comparer ordersnullbefore everything else. Both methods also evaluate the key selector only once per element instead of twice. - Fix: the retry policies (
ConstantDelayPolicy,NoDelayRetryPolicy,LinearBackOffRetryPolicy,ExponentialBackOffRetryPolicy) now reject a negativemaxRetrieswith anArgumentOutOfRangeExceptionwhen they are constructed, instead of failing later insideRetry. - Fix:
ConstantDelayPolicy,LinearBackOffRetryPolicyandExponentialBackOffRetryPolicynow reject a negative delay with anArgumentOutOfRangeExceptionwhen they are constructed, instead of failing later fromThread.SleeporTask.Delay. - Fix: the NuGet packages produced by CI (nightly feed and release artifacts) were built in the Debug configuration. They are now built in Release.
- Fix: cancellation gaps in the async APIs (
Funcky.Async, andFunckyitself on .NET 10):AsyncSequence.Successorsnow honours the cancellation token passed toGetAsyncEnumerator(e.g. viaWithCancellation) and stops before computing the next element.MemoizeandCycleRange/RepeatRangeforIAsyncEnumerableno longer acquire the source's enumerator when the buffer is created, but when the first element is requested, and they honour each consumer's cancellation token. The memoized buffer can now be enumerated by several consumers concurrently.AnyOrElseforIAsyncEnumerablenow awaits withConfigureAwait(false)like the other operators.- The exception-based
RetryAsync/RetryAwaitAsyncno longer retry once the cancellation token is cancelled, even ifshouldRetrymatches the exception, and check the token before the first attempt.
- New:
RetryAsyncandRetryAwaitAsyncoverloads whose producer receives theCancellationToken, so an attempt in progress can be aborted. - Fix: the
RetryandRetryAsyncoverloads without a retry policy were implemented recursively, so a producer that returnedNoneoften enough crashed the process with aStackOverflowException. They are now plain loops. - Fix: the
Option-basedRetryandRetryAsyncoverloads numbered the retries from 0 while the exception-based overloads numbered them from 1, so the sameIRetryPolicyproduced different delays depending on the overload. With aLinearBackOffRetryPolicytheOption-based overloads retried immediately the first time.IRetryPolicy.Delay(retryCount)is now documented as one-based (Delay(1)is the wait before the first retry) and all overloads follow that contract. - Fix:
ExponentialBackOffRetryPolicynow waitsfirstDelaybefore the first retry, likeLinearBackOffRetryPolicy. Previously the first delay wasfirstDelay * 1.5with the exception-basedRetryoverloads. - Fix:
Transposeenumerated the outer sequence three times, two of them eagerly when the method was called. It now enumerates the outer sequence exactly once, lazily, when the first column is requested. (#895) - Breaking:
Transposenow throws anInvalidOperationExceptionwhen the rows do not all have the same length, instead of silently producing a wrongly shaped result. The behaviour for jagged input was documented as undefined. (#895) - Fix: calling
Split(separator)on an array in C# 14 (the default for .NET 10) bound toMemoryExtensions.Split(ReadOnlySpan<T>, T)because of first-class span conversions, so the result was aSpanSplitEnumerator<T>instead of anIEnumerable<IReadOnlyList<T>>and the following LINQ call failed to compile. An array overload ofSplitnow keeps arrays bound to Funcky. (#893) - Fix:
MergeonIEnumerable<T>andIAsyncEnumerable<T>was not stable: when elements from different sources compared equal, the element from the later source was yielded first. Equal elements now keep the order of the sources, like a stable sort. (#891) - Fix:
WithIndex,WithFirst,WithLastandWithPreviouson anIList<T>source returned a collection whoseCopyToskipped the firstarrayIndexelements of the source, so copying it into an array at a non-zero offset (e.g. viaList<T>.AddRangeorInsertRangeon a non-empty list) dropped elements and left default values behind. (#889) - Fix: awaiting an empty
Option<Task>,Option<Task<T>>,Option<ValueTask>orOption<ValueTask<T>>via the awaiter'sOnCompletedmethod never invoked the continuation. Theawaitkeyword was not affected, because the compiler checksIsCompletedfirst, but custom schedulers and other awaiter-based code could hang forever. - The
Optionawaiters now implementICriticalNotifyCompletion, soawaitno longer needs to flow theExecutionContextwhen the underlying task awaiter does not either. - Add a
net10.0target toFuncky.Asyncthat only forwards all its types toFuncky. Projects that reference bothFunckyandFuncky.Asyncno longer get ambiguous type errors (CS0433) when they move to .NET 10. On .NET 10 you can simply remove theFuncky.Asyncreference. - Fix:
InterleaveandMergeforIAsyncEnumerablenow pass the cancellation token to the inner enumerators (same fix as in Funcky 3.6.0).
Funcky 3.6.0
This update is mainly to update to .NET 10.
- Funcky now targets
net10.0. - On .NET 10 and newer, the
IAsyncEnumerableextensions,AsyncSequence,AsyncFunctionaland the asyncOption/Result/Eitherextensions are part of theFunckypackage itself, becauseSystem.Linq.AsyncEnumerableis now part of the base class library. Funcky does not depend onSystem.Linq.Asyncon this framework. Funcky.Asyncstays the home of these APIs on .NET 9 and older frameworks, where it continues to depend onSystem.Linq.Async.- The
Chunk(int)overload forIAsyncEnumerableis not provided on .NET 10; the identically named method in the base class library is used instead and returns arrays rather thanIReadOnlyList<T>. TheChunkoverloads with aresultSelectorare unchanged. AdjacentGroupByforIAsyncEnumerablereturnsIGrouping<TKey, TElement>on .NET 10 instead of theIAsyncGrouping<TKey, TElement>type fromSystem.Linq.Async.
Funcky 3.5.1 | Funcky.Async 1.4.1 | Funcky.Xunit 2.1.1 | Funcky.Analyzers 1.4.1
This is a patch release which fixes vulnerability warnings of (direct and transitive) dependencies.
Funcky 3.5.0 | Funcky.Async 1.4.0 | Funcky.Xunit 2.1.0 | Funcky.Analyzers 1.4.0
This update is mainly to update to .NET 9 but also has several smaller improvements.
.NET 9
Thanks to the new [OverloadResolutionPriority] attribute, we've been able finally fix
the ambiguous call errors when using DictionaryExtensions.GetValueOrNone:
Dictionary<string, string> favouriteFoods = new();
Option<int> tausFavouriteFood = favouriteFoods.GetValueOrNone("Tau");
// ^^^^^^^^^^^^^^
// This used to produce a `error CS0121: The call is ambiguous between ...` error
// and now simply works 🎉
In good old fashion, we've also added new parse extensions:
ParseExtensions.ParseAssemblyNameInfoOrNoneParseExtensions.ParseTypeNameOrNone
Policy for EOL Target Frameworks
We've explicitly written down our policy for how we deal with older / EOL target frameworks.
In short: EOL target frameworks are supported by Funcky until the next major version. However, we may no longer test Funcky against those target frameworks, so we can't guarantee that everything works smoothly.
New APIs
In Funcky 3.0 we've changed CycleRange and RepeatRange to return an IBuffer
to enable us to memoize the collection, preventing unnecessary enumeration. This however
is unpractical for uses where you want to cycle an already materialized collection.
This release adds two new methods to accommodate that use case:
CycleMaterialized and RepeatMaterialized.
For convenient downcasting of Option<T>, Result<T> and Either<TLeft, T> values, we've added a new
DownCast class:
Option<object> option = Option.Some("hello world");
Option<string> downcasted = DownCast<string>.From(option);
Funcky.XUnit
Funcky.XUnit has been updated to the latest version (2.9.3) of xUnit.net.
Due to changes in xUnit we've had to change the exception type thrown by our FunctionalAssert methods
from AssertActualExpectedException to XunitException.
Funcky.Analyzers
The new analyzer rule λ1009 prevents you from accidentally default-instantiating
Either, Result and EitherOrBoth.
Funcky.Async
The PowerSet and WithPrevious extensions now correctly pass along the cancellation token.
Funcky 3.4.0 | Funcky.Async 1.3.0 | Funcky.XUnit 2.0.2
This update is mainly to update to .NET 8 but also has several smaller improvements.
Native AOT
Both Funcky and Funcky.Async have been annotated to be compatible with Native AOT.
The only exception is OptionJsonSerializer which is not compatible with Native AOT.
.NET 8
We use the new C#12 and .NET features in the code, and expose new features through our API.
- .NET 8 added new overloads to their
TryParseAPIs. These changes are reflected in Funcky'sParseOrNoneAPIs.ParseByteOrNoneoverloads withReadOnlySpan<byte>andstring?ParseSByteOrNoneoverloads withReadOnlySpan<byte>ParseSingleOrNoneoverloads withReadOnlySpan<byte>ParseDoubleOrNoneoverloads withReadOnlySpan<byte>ParseDecimalOrNoneoverloads withReadOnlySpan<byte>ParseInt16OrNoneoverloads withReadOnlySpan<byte>ParseInt32OrNoneoverloads withReadOnlySpan<byte>ParseInt64OrNoneoverloads withReadOnlySpan<byte>ParseUInt16OrNoneoverloads withReadOnlySpan<byte>ParseUInt32OrNoneoverloads withReadOnlySpan<byte>ParseUInt64OrNoneoverloads withReadOnlySpan<byte>ParseNumberOrNone<TNumber>overloadsParseOrNone<TParsable>overloads
String Extensions
We implemented a few of the IEnumerable extensions which are very useful on strings.
Chunkonstring.SlidingWindowonstring.
Monads
- Implemented
UpCastfor the mondsOption,Either,ResultandSystem.Lazy. - Implemented
InspectEmptyonIEnumerableandIAsyncEnumerable - Implemented
ToAsyncEnumerableextension onOption
IEnumerator
MoveNextOrNoneextension onIEnumerator<T>
Consistency
FindIndexOrNoneandFindLastIndexOrNoneextensions onList
Funcky 3.3.0 | Funcky.Analyzers 1.3.0 | Funcky.Xunit 2.0.1
This is a relatively minor release focuses on convenience for our monads Option, Either and Result.
GetOrElse and OrElse for all
We've added GetOrElse and OrElse to Either and Result bringing them on par with Option.
The corresponding analyzer now also correctly suggests using these methods instead of Match for Result and Either.
Inspect for the error case
All three alternative monads Option, Either and Result now support inspecting the «error» case:
Option.InspectNone- executes a side effect only when the option isNone.Either.InspectLeft- executes a side effect only when the either is on theLeftside.Result.InspectError- executes a side effect only when the result is anError.
These methods are particularly useful for logging warnings/errors.
Funcky.XUnit
- Funcky.XUnit is only compatible with XUnit 2.4, this is now correctly declared.
Funcky 3.2.0 | Funcky.Async 1.2.0
List Pattern for Option
We've added support for C# 11's List Patterns to Option<T>.
This means that you can use regular switch expressions / statements to match on options:
var greeting = person switch
{
{ FirstName: var firstName, LastName: [var lastName] } => $"Hello {firstName} {lastName}",
{ FirstName: var firstName } => $"Hi {firstName}",
};
record Person(string FirstName, Option<string> LastName);
Discard
The new Discard.__ field provides a short-hand for Unit.Value to be used with switch expressions.
using static Funcky.Discard;
return __ switch
{
_ when user.IsFrenchAdmin() => "le sécret",
_ when user.IsAdmin() => "secret",
_ => "(redacted)",
};
Retry with Exception
We've added overloads to the Retry and RetryAsync functions that allow retrying a function
as long as an exception is thrown.
Example from IoRetrier:
// Retries an action until the file is no longer in use.
Task RetryWhileFileIsInUseAsync(IRetryPolicy policy, Action action)
=> RetryAsync(ActionToUnit(action), exception => exception is IOException && exception.HResult == FileInUseHResult, policy);
New Extensions
EnumerableExtensions.MinByOrNoneEnumerableExtensions.MaxByOrNoneImmutableListExtensions.IndexOfOrNoneImmutableListExtensions.LastIndexOfOrNoneListExtensions.IndexOfOrNone
Funcky 3.1.0 | Funcky.Async 1.1.0 | Funcky.Analyzers 1.2.0
New APIs
- ✨
OptionExtensions.ToNullable✨ StreamExtensions.ReadByteOrNone- New overloads for
ElementAtOrNonethat take anIndex. - New overload for
JoinToStringthat takes anIEnumerable<string>.
.NET 7
- .NET 7 added new overloads to their
TryParseAPIs. These changes are reflected in Funcky'sParseOrNoneAPIs. - The
ParseOrNonemethods include the new[StringSyntax]attribute from .NET 7.
Analyzers
The new Option.Match analyzer suggests simpler alternatives over custom Matches including
the all-new ToNullable extension.
Funcky 3.0.0 | Funcky.Async 1.0.0 | Funcky.XUnit 2.0.0
There's a handy Migration Guide available.
New APIs
Result.GetOrThrowEnumerableExtensions.GetNonEnumeratedCountOrNone
PriorityQueue
PriorityQueueExtensions.DequeueOrNonePeekOrNone
Traversable
The new Traverse and Sequence extension methods allow you to
«swap» the inner and outer monad (e.g. Result<Option<T>> -> Option<Result<T>>)
Memoize
The new Memoize extension function returns an IBuffer / IAsyncBuffer.
This new type represents ownership over the underlying enumerator (and is therefore IDisposable).
CycleRange and RepeatRange have also been changed to return an IBuffer.
ParseExtensions
The parse extensions have been improved with the goal of aligning more with the BCL. Many of the changes are breaking.
- The functions now use BCL type names instead of C# type names
(e.g.
ParseIntOrNonehas been renamed toParse) - The parameter names and nullability have been changed to align with the BCL.
- Added
HttpHeadersNonValidatedExtensions
IReadOnlyList / IReadOnlyCollection
Funcky now communicates materialization in the IEnumerable<T> extensions by returning
IReadOnlyList or IReadOnlyCollection. This reduces «multiple enumeration» warnings.
MaterializeChunkPartitionShuffleSlidingWindowSplitTransposeSequence.Return
Disallowing null Values
Our Option<T> type has always disallowed null values.
This has been extended to our other monads: Result<T>, Either<L, R> and Reader<E, R>.
Breaking Changes
Option.None()has been changed to a property. There's an automatic fix available for this.- Our
Matchfunctions now differentiate betweenFuncandAction. TheActionoverloads have been renamed toSwitch. - The return type of
EnumerableExtensions.ForEachhas been changed toUnit. - Many parameter names and generic type names have been renamed in an attempt to unify naming across Funcky.
- All
Actionextensions have been moved to a new classActionExtensions. EitherOrBothhas been moved to theFunckynamespace.- The retry policies have been moved to the
Funcky.RetryPoliciesnamespace. Partitionreturns a customPartitionsstruct instead of a tuple.
Obsoleted APIs Removed
APIs that have been obsoleted during 2.x have been removed:
ObjectExtensions.ToEnumerableFuncky.GenericConstraints.RequireClassandRequireStruct- All
Try*APIs (TryGetValue,TryParse*, etc.). These APIs use theOrNonesuffix instead. Sequence.Generatehas been superseded bySequence.SuccessorsCartesianProduct
JSON Converter
We have removed the implicit System.Text.Json converter for Option<T>.
This means that you'll have to register the OptionJsonConverter yourself.
⚠️ Test this change carefully as you won't get an error during compilation, but rather at runtime.
Moved to Funcky.Async
All APIs related to IAsyncEnumerable and Task have been moved to the new Funcky.Async package:
AsyncEnumerableExtensionsFunctional.RetryAsync->AsyncFunctional.RetryAsyncOption<Task>andOption<ValueTask>awaiters
Funcky.Async
AsyncSequence
This class exposes all of the same factory functions as Sequence, but for IAsyncEnumerable:
ReturnSuccessorsConcatCycleCycleRangeFromNullableRepeatRange
New IAsyncEnumerable extensions
We've worked hard towards the goal of parity between our extensions for IEnumerable and IAsyncEnumerable:
AdjacentGroupByAnyOrElseAverageOrNoneAsync/MaxOrNoneAsync/MinOrNoneAsyncChunkConcatToStringAsyncExclusiveScanInclusiveScanInspectInterleaveIntersperseJoinToStringAsyncMaterializeAsyncMemoizeMergeNoneAsyncPartitionAsyncPowerSetSequence/SequenceAsync/Traverse/TraverseAsyncShuffleAsyncSlidingWindowSplitTransposeWhereNotNullWithIndex/WithLast/WithPrevious/WithFirstZipLongest
Funcky.Xunit
- Breaking: The
Isprefix has been dropped from assertion methods for consistency with XUnit's naming scheme for assertion methods.
Funcky 2.7.1
Deprecations
Option.None<T>(): We originally introduced theOption.None<T>method as a future proof replacement toOption<T>.Nonefor use in method groups, because Funcky 3 changesOption<T>.Noneto a property. This turned out to be confusing to users especially because both method are always suggested in autocomplete.
Funcky 2.7.0 | Funcky.XUnit 1.0.0 | Funcky.Analyzers 1.1.0
This release is the last non-breaking release for Funcky before 3.0.
Deprecations
EnumerableExtensions.CartesianProductwill be removed in Funcky 3.- To align our naming with that of the BCL, the
ParseOrNonemethods that return a type that has a keyword in C#int,long, etc. use the name of the BCL type instead.
Example:ParseIntOrNonebecomesParseInt32OrNone.
The old methods will be removed in Funcky 3. - In preparation for Funcky 3 we deprecated
Option<T>.Nonewhen used as method group. UseOption.None<T>instead.
New ParseOrNone extensions
With the help of a source generator we have added a lot of new ParseOrNone methods for various types from the BCL:
- Unsigned integer types
DateOnly,TimeOnlyVersion- Support for
ReadOnlySpan<T>as input - ... and more
Convenience for Either and Result
- Added implicit conversions for
EitherandResult. - Implement
InspectforEitherandResult. - Added
PartitionforIEnumerable<Either>andIEnumerable<Result>. - Added
ToStringonEitherandResult. - Implement
ToEitheronOption.
IEnumerable<T> extensions
AnyOrElse- Prefix sum:
InclusiveScanandExclusiveScan
Analyzers
This release adds two new analyzer rules:
- λ1003: Warning when certain methods, such as
Matchare used without argument labels - λ1004: Warning that suggests
.ConcatToString()over.JoinToString("")
Both of these warnings come with corresponding code fixes.
Funcky.Xunit
- Breaking: Funcky.Xunit now uses the
Funckynamespace, instead ofFuncky.Xunit. - Add assertion methods for testing
Either:FunctionalAssert.IsLeftandFunctionalAssert.IsRight.
Funcky 2.6.0 | Funcky.Analyzers 1.0.0
Analyzers
This release comes with a new package Funcky.Analyzers, which we'll use
to guide users of Funcky
New extensions
- Add extensions
DequeueOrNoneandPeekOrNoneonQueueandConcurrentQueue. - Add extension
ConcatToStringas an alias forstring.Concat. - Add overload to
WhereSelectwith no parameter. - Add methods to convert from
EithertoOption: #439LeftOrNone: Returns the left value orNoneif the either value was right.RightOrNone: Returns the right value orNoneif the either value was left.
- Extension functions for
System.Rangeto allow the generations ofIEnumerable<T>s from Range-Syntax:foreach(var i in 1..5) { } // negative numbers are not supported from x in 5..2 from y in 1..3 select (x, y)
Improvements to Sequence
Sequence.Returnnow accepts multiple parameters:Sequence.Return(1, 2, 3)- ⚠️
Sequence.Generatehas been deprecated in favour of the newly addedSequence.Successorsfunction which includes the first element (seed) in the generated sequence.
Improvements to Option
- Add
Option.FromBooleanto create anOption<T>from a boolean.
Improvements to Result
The behaviour of the Result.Error constructor has been changed regarding exceptions
with an already set stack trace. The original stack trace is now preserved.
Previously this resulted in the stacktrace being replaced (.NET < 5.0) or an error (.NET ≥ 5.0).
Improvements to Either
- Add
Either.Flipto swaps left with right.
Tooling
- Funcky automatically adds global usings for the most important namespaces of funcky
when the
FunckyImplicitUsingsproperty is set. This requires .NET SDK ≥ 6.0 and C# ≥ 10.0. - Funcky now supports trimming for self-contained deployments.
Option<T>now works with the new System.Text.Json source generation.- The
Funckypackage now supports Source Link and deterministic builds. - The symbols package is now finally working again.
Funcky 2.5.0
Reader Monad
This release includes the Reader monad including a bunch of factory methods
and convenience extensions.
public static Reader<Environment, IEnumerable<string>> DefaultLayout(IEnumerable<DateTime> month)
=> from colorizedMonthName in ColorizedMonthName(month)
from weekDayLine in WeekDayLine()
from weeksInMonth in month
.GroupBy(GetWeekOfYear)
.Select(FormatWeek)
.Sequence()
select BuildDefaultLayout(colorizedMonthName, weekDayLine, weeksInMonth);
Improved Action Extensions
Funcky now supports Curry, Uncurry and Flip for Actions too.
This release also adds the inversion of ActionToUnit: UnitToAction
More Extensions for IEnumerable<T>
Intersperse: Adds a given item in between all items of an enumerable.JoinToString: Alias forstring.Join.WithPrevious: Similar toWithFirst/Last/Indexbut with the predecessor of each item.ForEach: Add an overload toForEachthat accepts aUnit-returningFunc.
Additional Factory Methods
EitherOrBoth.FromOptionscreates anEitherOrBothfrom two options.Lazy.FromFunccreates aLazy<T>from aFunc.
This is sugar over theLazy<T>constructor, with the additional benefit of supporting type inference.Lazy.Returncreates aLazy<T>from a value.
This is sugar over theLazy<T>constructor, with the additional benefit of supporting type inference.
Documentation Improvements
This release comes with a few small documentation improvements.
Funcky users will now also see the [Pure] attributes which were previously not emitted.
Funcky 2.4.1
- Remove upper bounds on all Microsoft.Bcl.* dependencies. Between the 2.3.0 and 2.4.0 release an overly restrictive upper bound was accidentally introduced for Microsoft.Bcl.AsyncInterfaces.
Funcky 2.4.0
Try* → *OrNone
We've renamed all Try* methods, such as TryParse, TryGet value to *OrNone.
The old methods are still available, but marked as obsolete and will be removed in 3.0.0.
Factory methods for IEnumerable<T>
This release adds some new factory methods for creating IEnumerable<T>
to the Sequence class:
Sequence.RepeatRange: Generates a sequence that contains the same sequence of elements the given number of timesSequence.Cycle: Cycles the same element over and over again as an endless generator.Sequence.CycleRange: Generates a sequence that contains the same sequence of elements over and over again as an endless generatorSequence.Concat
More Extension Methods
for IEnumerable<T>
Materialize: Materializes all the items of a lazy enumerable.PowerSet: Returns a sequence with the set of all subsetsShuffle: Returns the given sequence in random Order in O(n).Split: Splits the source sequence a separator.ZipLongest: Zips two sequences with different lengths.
for string
SplitLazy: Splits a string by separator lazily.SplitLines: Splits a string by newline lazily.
for Func
CurryUncurryFlipCompose
EitherOrBoth
EitherOrBoth is a new data type that can represent Left, Right and Both. It is used in ZipLongest.
Monad.Return
This release adds a Return method for all monad types in Funcky:
Option.ReturnEither<TLeft>.ReturnResult.Return
OptionEqualityComparer
To support more advanced comparison scenarios, OptionEqualityComparer has been added similar to the already existing OptionComparer.
Smaller Improvements
- Added a missing
Matchoverload toEitherthat takesActions - Added additional overloads for
Functional.TrueandFunctional.Falsefor up to four parameters.
Funcky 2.3.0
net5.0has been added to Funcky's target frameworks.
Improvements to Option<T>
Option<T>is now implicitly convertible fromT.public static Option<int> Answer => 42;Optionadds support forSystem.Text.Json:
The customJsonConverteris picked up automatically when serializing/deserializing.Noneis serialized asnullandSome(value)is serialized to whatevervalueserializes to.
Factory methods for IEnumerable<T>
This release adds factory methods for creating IEnumerable<T>
with the static class Sequence:
Sequence.Return: Creates anIEnumerable<T>with exactly one item.Sequence.FromNullable: Creates anIEnumerable<T>with zero or one items.Sequence.Generate: Creates anIEnumerable<T>using a generation function and a seed.
More Extension Methods for IEnumerable<T>
This release adds a bunch of new extension methods on IEnumerable<T>:
AdjacentGroupByAverageOrNoneCartesianProductChunkElementAtOrNoneInterleaveMaxOrNoneMergeMinOrNonePairwisePartitionSlidingWindowTakeEveryTransposeWithFirstWithIndexWithLast
IAsyncEnumerable<T> Support
This release adds a couple of extension methods that provide interoperability
with Option<T> to IAsyncEnumerable<T>:
WhereSelectFirstOrNoneAsyncLastOrNoneAsyncSingleOrNoneAsyncElementAtOrNoneAsync
A couple of the new extension methods on IEnumerable<T> have async counterparts:
PairwiseTakeEvery
The naming of the extension methods and their overloads follows that of System.Linq.Async.
Improved IQueryable Support
This release adds specialized extension methods for IQueryable<T> that are better
suited especially for use with EF Core:
FirstOrNoneLastOrNoneSingleOrNone
Dependencies
To support .NET Standard, Funcky conditionally pulls in dependencies that provide the missing functionality:
Microsoft.Bcl.AsyncInterfacesfor .NET Standard 2.0System.Collections.ImmutableandSystem.Text.Jsonfor .NET Standard 2.0 and 2.1- The version constraints for all these packages have been relaxed to allow 5.x.
Improvements
ConfigureAwait(false)is now used everywhereawaitis used.- The
IRetryPolicyimplementations now use correctTimespanwithdoublemultiplication when targeting .NET Standard 2.0.
Deprecations
ObjectExtensions.ToEnumerablehas been deprecated in favor ofSequence.FromNullable.RequireClassandRequireStructhave been obsoleted with no replacement.
Funcky 2.2.0 | Funcky.xUnit 0.1.3
- Added overload to
Functional.Retrywith aIRetryPolicy. - Added
Noneoverload that takes no predicate.
Funcky 2.1.1 | Funcky.xUnit 0.1.2
- Re-release of previous release with correct assemblies.
Funcky 2.1.0 | Funcky.xUnit 0.1.1
- Add
Inspectmethod toOptionakin toIEnumerable.Inspect. - Add
ToTheoryDataextension forIEnumerable<T>for xUnit. - Add
Unit.Valueas a way to a get aUnitvalue. - Add
Functional.Retrywhich retries a producer untilOption.Someis returned.
Funcky 2.0.0
Breaking Changes
- Remove
Readermonad based onawait. - Remove
IToString. - Remove overload for
Option.Fromthat flattens passedOptions. - Move
ToEnumerableextension method to its own class. This is only a breaking change if you've used the extension method as normal method. In that case you need to changeEnumerableExtensions.ToEnumerabletoObjectExtensions.ToEnumerable. - Rename
Option.FromtoOption.FromNullableand remove overload that takes non-nullable value types. - Unify
Option<T>.ToEnumerableandYieldtoToEnumerable - Rename
OrElseoverloads that return the item toGetOrElsewhich improves overload resolution. - The
Eachextension method onIEnumerable<T>has been renamed toForEach. - Move the
Okconstructor ofResult<T>to a non-generic class. This allows for the compiler to infer the generic type. Old:Result<int>.Ok(10). New:Result.Ok(10). - Use
Func<T, bool>instead ofPredicate<T>in predicate composition functions (Functional.All,Functional.Any,Functional.Not), because most APIs inSystemuseFunc. Functional.Anynow returnsfalsewhen the given list of predicates is empty.
Fixes
- Fix incorrect
Equalsimplementation onOption.Equalspreviously returnedtruewhen comparing aNonevalue with aSomevalue containing the default value of the type. Exceptioncreated byResultmonad contains valid stack trace- Fix incorrect implementation on
Result.SelectManywhich called theselectedResultSelectoreven when the result was an error. As a result (pun intended) of the fix,ResultCombinationExceptionis no longer needed and also removed.
Additions
- Add
IndexOfOrNone,LastIndexOfOrNone,IndexOfAnyOrNoneandLastIndexOfAnyOrNoneextension methods tostring. - Added
Curry,UncurryandFlipto theFunctionalClass - Add extension method for
HttpHeaders.TryGetValues, which returns anOption. - Add extension methods for getting
Streamproperties that are not always available, asOption:GetLengthOrNone,GetPositionOrNone,GetReadTimeoutOrNone,GetWriteTimeoutOrNone. - Add
Noneextension method toIEnumerable. Option<Task<T>>,Option<Task>and theirValueTaskequivalents are now awaitable:var answer = await Option.Some(Task.FromResult(42));
Improvements
- Full nullable support introduced with C# 8.
- Mark our functions as
[Pure]. - Implement
IEquatableonOption,ResultandEither.
Funcky 2.0.0-rc.2
- Move the
Okconstructor ofResult<T>to a non-generic class. This allows for the compiler to infer the generic type. Old:Result<int>.Ok(10). New:Result.Ok(10). - Add
IndexOfOrNone,LastIndexOfOrNone,IndexOfAnyOrNoneandLastIndexOfAnyOrNoneextension methods tostring. - Rename
OrElseoverloads that return the item toGetOrElsewhich improves overload resolution. - Added
Curry,UncurryandFlipto theFunctionalClass - Remove
IToString. - Mark our functions as
[Pure]. - Fix incorrect implementation on
Result.SelectManywhich called theselectedResultSelectoreven when the result was an error. As a result (pun intended) of the fix,ResultCombinationExceptionis no longer needed and also removed.
Funcky 2.0.0-rc.1
- Full nullable support introduced with C# 8
- Rename
Option.From->Option.FromNullableand remove overload that takes non-nullable value types. - Use
Func<T, bool>instead ofPredicate<T>in predicate composition functions (Functional.All,Functional.Any,Functional.Not), because most APIs inSystemuseFunc. Functional.Anynow returnsfalsewhen the given list of predicates is empty.- The
Eachextension method onIEnumerable<T>has been renamed toForEach. - Unify
Option<T>.ToEnumerableandYieldtoToEnumerable - Remove
Readermonad based onawait. Exceptioncreated byResultmonad contains valid stack trace
Funcky 1.8.0
- Added overload for
AndThenwhich flattens theOption - Add
Wheremethod toOption<T>, which allows filtering theOptionby a predicate. - Add overload for
Option<T>.SelectManythat takes only a selector. - Add
WhereNotNullextension method forIEnumerable<T>.
Funcky 1.7.0
- Add nullability annotations to everything except for
Monads.Reader. - Add a function for creating an
Option<T>from a nullable value:Option.From. Either.Matchnow throws when called on anEithervalue created usingdefault(Either<L, R>).- Add
TrueandFalsefunctions to public API - Match of
ResultMonad accepts actions - Add
FirstOrNone,LastOrNoneandSingleOrNoneextension functions
Funcky 1.6.0
- Add ToEnumerable function to
Option<T>. - Add
WhereSelectextension function forIEnumerable<T>. - Add missing overload for nullary actions to
ActionToUnit.