├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── docs.yml │ ├── publish-nightly-package.yml │ └── spelling.yml ├── .gitignore ├── .mailmap ├── Analyzers.props ├── Artwork ├── logo-green-rounded.png ├── logo-green.png ├── logo-red-rounded.png ├── logo-red.png └── logo.svg ├── Directory.Build.props ├── Directory.Packages.props ├── Documentation ├── Marble Graphics.txt ├── book.toml ├── custom.css ├── package.json ├── scripts │ └── build-marble-graphics.mjs ├── src │ ├── SUMMARY.md │ ├── analyzer-rules │ │ └── λ0001.md │ ├── case-studies │ │ ├── calendar-code.md │ │ ├── calendar.md │ │ ├── if-null-to-option.md │ │ └── transpose.png │ ├── changelog.md │ ├── enumerable-extensions │ │ ├── adjacent-group-by.md │ │ ├── adjacent-group-by.swirly │ │ ├── average-or-none.md │ │ ├── cartesian-product.md │ │ ├── cartesian-product.swirly │ │ ├── chunk.md │ │ ├── chunk.swirly │ │ ├── enumerable-extensions.md │ │ ├── first-or-none.md │ │ ├── for-each.md │ │ ├── inspect.md │ │ ├── interleave.md │ │ ├── interleave.swirly │ │ ├── intersperse.md │ │ ├── intersperse.swirly │ │ ├── last-or-none.md │ │ ├── materialize.md │ │ ├── merge.md │ │ ├── merge.swirly │ │ ├── none.md │ │ ├── pairwise.md │ │ ├── pairwise.swirly │ │ ├── partition.md │ │ ├── partition.swirly │ │ ├── power-set.md │ │ ├── power-set.swirly │ │ ├── shuffle.md │ │ ├── shuffle.swirly │ │ ├── sliding-window.md │ │ ├── sliding-window.swirly │ │ ├── split.md │ │ ├── split.swirly │ │ ├── take-every.md │ │ ├── take-every.swirly │ │ ├── transpose.md │ │ ├── transpose.swirly │ │ ├── where-not-null.md │ │ ├── where-not-null.swirly │ │ ├── where-select.md │ │ ├── where-select.swirly │ │ ├── with-first.md │ │ ├── with-first.swirly │ │ ├── with-index.md │ │ ├── with-index.swirly │ │ ├── with-last.md │ │ ├── with-last.swirly │ │ ├── with-previous.md │ │ ├── with-previous.swirly │ │ ├── zip-longest.md │ │ └── zip-longest.swirly │ ├── fp │ │ └── overview.md │ ├── functional-helpers │ │ ├── action-to-unit.md │ │ ├── functional-helpers.md │ │ ├── identity.md │ │ ├── no-operation.md │ │ └── unit-type.md │ ├── introduction.md │ ├── migration-guide.md │ ├── option.md │ ├── string-extensions.md │ └── try-pattern.md └── yarn.lock ├── FrameworkFeatureConstants.props ├── Funcky.Analyzers ├── Funcky.Analyzers.CodeFixes │ ├── AddArgumentNameCodeFix.cs │ ├── AlternativeMonad │ │ ├── MatchToNullableCodeFix.cs │ │ └── MatchToOrElseCodeFix.cs │ ├── CodeAnalysisExtensions │ │ ├── DiagnosticExtensions.cs │ │ ├── SyntaxGeneratorExtensions.cs │ │ ├── SyntaxNodeExtensions.ParameterReference.cs │ │ └── SyntaxNodeExtensions.cs │ ├── CodeFixResources.Designer.cs │ ├── CodeFixResources.resx │ ├── EnumerableRepeatNeverCodeFix.cs │ ├── EnumerableRepeatOnceCodeFix.cs │ ├── Funcky.Analyzers.CodeFixes.Roslyn4.0.csproj │ ├── Funcky.Analyzers.CodeFixes.csproj │ ├── Funcky.Analyzers.CodeFixes.targets │ ├── FunctionalAssertFix.cs │ ├── JoinToStringEmptyCodeFix.cs │ └── OptionSomeWhereToFromBooleanRefactoring.cs ├── Funcky.Analyzers.Package │ ├── Funcky.Analyzers.Package.csproj │ ├── Packing.targets │ ├── buildTransitive │ │ └── Funcky.Analyzers.targets │ ├── package.md │ └── tools │ │ ├── install.ps1 │ │ └── uninstall.ps1 ├── Funcky.Analyzers.Test │ ├── AlternativeMonad │ │ ├── AlternativeMonadAnalyzerTest.Stubs.cs │ │ ├── AlternativeMonadAnalyzerTest.ToNullable.cs │ │ └── AlternativeMonadAnalyzerTest.cs │ ├── EnumerableRepeatNeverTest.cs │ ├── EnumerableRepeatOnceTest.cs │ ├── Funcky.Analyzers.Test.csproj │ ├── FunctionalAssertAnalyzerTest.Setup.cs │ ├── FunctionalAssertAnalyzerTest.cs │ ├── JoinToStringEmptyTest.cs │ ├── NonDefaultableTest.cs │ ├── OptionListPatternTest.cs │ ├── OptionNoneInvocationCodeFixTest.cs │ ├── OptionSomeWhereToFromBooleanRefactoringTest.Setup.cs │ ├── OptionSomeWhereToFromBooleanRefactoringTest.cs │ ├── SyntaxSupportOnlyTest.cs │ ├── TestCode │ │ ├── EnumerableRepeatWithAnyNumber.input │ │ ├── JoinToStringEmpty.expected │ │ ├── JoinToStringEmpty.input │ │ ├── JoinToStringEmptyConstant.expected │ │ ├── JoinToStringEmptyConstant.input │ │ ├── RepeatNever.expected │ │ ├── RepeatNever.input │ │ ├── RepeatNeverFlipped.expected │ │ ├── RepeatNeverFlipped.input │ │ ├── RepeatNeverQualification.expected │ │ ├── RepeatNeverQualification.input │ │ ├── RepeatNeverWithConstant.expected │ │ ├── RepeatNeverWithConstant.input │ │ ├── RepeatNeverWithInt.expected │ │ ├── RepeatNeverWithInt.input │ │ ├── RepeatOnce.expected │ │ ├── RepeatOnce.input │ │ ├── RepeatOnceFlipped.expected │ │ ├── RepeatOnceFlipped.input │ │ ├── RepeatOnceMissingSequenceType.input │ │ ├── RepeatOnceQualification.expected │ │ ├── RepeatOnceQualification.input │ │ ├── RepeatOnceWithConstant.expected │ │ ├── RepeatOnceWithConstant.input │ │ └── ValidUseJoinToString.input │ ├── TryGetValueTest.cs │ ├── UseWithArgumentNamesTest.cs │ ├── VerifierTests.cs │ ├── Verifiers │ │ ├── CSharpAnalyzerVerifier`1+Test.cs │ │ ├── CSharpAnalyzerVerifier`1.cs │ │ ├── CSharpCodeFixVerifier`2+Test.cs │ │ ├── CSharpCodeFixVerifier`2.cs │ │ ├── CSharpCodeRefactoringVerifier`1+Test.cs │ │ ├── CSharpCodeRefactoringVerifier`1.cs │ │ └── CSharpVerifierHelper.cs │ └── VerifyWithSourceExample.cs ├── Funcky.Analyzers.Vsix │ ├── Funcky.Analyzers.Vsix.csproj │ └── source.extension.vsixmanifest ├── Funcky.Analyzers │ ├── AlternativeMonad │ │ ├── AlternativeMonadAnalyzer.GetOrElse.cs │ │ ├── AlternativeMonadAnalyzer.OrElse.cs │ │ ├── AlternativeMonadAnalyzer.SelectMany.cs │ │ ├── AlternativeMonadAnalyzer.ToNullable.cs │ │ ├── AlternativeMonadAnalyzer.cs │ │ ├── AlternativeMonadErrorStateConstructorMatching.cs │ │ ├── AlternativeMonadType.cs │ │ ├── AlternativeMonadTypeCollection.cs │ │ └── MonadReturnMatching.cs │ ├── AnalyzerReleases.Shipped.md │ ├── AnalyzerReleases.Unshipped.md │ ├── Argument.cs │ ├── CodeAnalysisExtensions │ │ ├── ArgumentOperationExtensions.cs │ │ ├── SemanticModelExtensions.cs │ │ ├── SymbolEqualityFunctions.cs │ │ ├── SymbolExtensions.cs │ │ ├── SyntaxNodeExtensions.ExpressionTree.cs │ │ └── SyntaxNodeExtensions.cs │ ├── DiagnosticName.cs │ ├── EnumerableRepeatNeverAnalyzer.cs │ ├── EnumerableRepeatOnceAnalyzer.cs │ ├── Funcky.Analyzers.Roslyn4.0.csproj │ ├── Funcky.Analyzers.csproj │ ├── Funcky.Analyzers.targets │ ├── FunckyWellKnownMemberNames.cs │ ├── FunckyWellKnownTypeNames.cs │ ├── FunctionalAssert │ │ ├── FunctionalAssertAnalyzer.cs │ │ ├── FunctionalAssertMatching.cs │ │ └── XunitAssertMatching.cs │ ├── Functions │ │ ├── AnonymousFunctionMatching.cs │ │ ├── ConstantFunctionMatching.cs │ │ └── IdentityFunctionMatching.cs │ ├── JoinToStringEmptyAnalyzer.cs │ ├── LocalizedResourceLoader.cs │ ├── NonDefaultable │ │ ├── NonDefaultableAnalyzer.cs │ │ └── NonDefaultableStyleCopSuppressor.cs │ ├── OperationMatching.cs │ ├── Option │ │ ├── Option.ListPattern.cs │ │ └── Option.cs │ ├── OptionListPatternAnalyzer.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── UseWithArgumentNamesAnalyzer.cs ├── Funcky.BuiltinAnalyzers.CodeFixes │ ├── Funcky.BuiltinAnalyzers.CodeFixes.csproj │ └── OptionNoneInvocationCodeFix.cs ├── Funcky.BuiltinAnalyzers │ ├── AnalyzerReleases.Shipped.md │ ├── AnalyzerReleases.Unshipped.md │ ├── CompilationExtensions.cs │ ├── Funcky.BuiltinAnalyzers.csproj │ ├── MethodSymbolExtensions.cs │ ├── SyntaxSupportOnlyAnalyzer.cs │ ├── TryGetValueAnalyzer.cs │ └── WellKnownMemberNames.cs └── readme.md ├── Funcky.Async.Test ├── AsyncGenerator.cs ├── AsyncSequence │ ├── ConcatTest.cs │ ├── CycleRangeTest.cs │ ├── CycleTest.cs │ ├── RepeatRangeTest.cs │ ├── ReturnTest.cs │ └── SuccessorsTest.cs ├── Extensions │ └── AsyncEnumerableExtensions │ │ ├── AdjacentGroupByTest.cs │ │ ├── AnyOrElseTest.cs │ │ ├── AverageOrNoneTest.cs │ │ ├── ChunkTest.cs │ │ ├── ConcatToStringTest.cs │ │ ├── ElementAtOrNoneTest.cs │ │ ├── FirstOrNoneTest.cs │ │ ├── InspectEmptyTest.cs │ │ ├── InspectTest.cs │ │ ├── InterleaveTest.cs │ │ ├── IntersperseTest.cs │ │ ├── JoinToStringTest.cs │ │ ├── LastOrNoneTest.cs │ │ ├── MaterializeTest.cs │ │ ├── MaxOrNoneTest.cs │ │ ├── MemoizeTest.cs │ │ ├── MergeTest.cs │ │ ├── MinOrNoneTest.cs │ │ ├── PairwiseTest.cs │ │ ├── PartitionEitherTest.cs │ │ ├── PartitionResultTest.cs │ │ ├── PartitionTest.cs │ │ ├── PowerSetTest.cs │ │ ├── ScanTest.cs │ │ ├── ShuffleTest.cs │ │ ├── SingleOrNoneTest.cs │ │ ├── SlidingWindowTest.cs │ │ ├── SplitTest.cs │ │ ├── TakeEveryTest.cs │ │ ├── TestData.cs │ │ ├── TransposeTest.cs │ │ ├── WhereNotNullTest.cs │ │ ├── WhereSelectTest.cs │ │ ├── WithFirstTest.cs │ │ ├── WithIndexTest.cs │ │ ├── WithLastTest.cs │ │ ├── WithPreviousTest.cs │ │ └── ZipLongestTest.cs ├── Funcky.Async.Test.csproj ├── FunckyAsyncPropertyAttribute.cs ├── FunctionalClass │ ├── RetryAsyncTest.cs │ └── RetryWithExceptionAsyncTest.cs ├── Monads │ ├── OptionAsyncExtensions │ │ └── ToAsyncEnumerableTest.cs │ └── OptionAwaiterTest.cs └── TestUtilities │ ├── AssertIsCancellationRequestedAsyncSequence.cs │ ├── AsyncAssert.cs │ ├── AsyncEnumerateOnce.cs │ ├── CountCreation.cs │ ├── FailOnCall.cs │ ├── FailOnEnumerateAsyncSequence.cs │ ├── OptionProducer.cs │ └── RepeatingSequence.cs ├── Funcky.Async ├── AsyncSequence │ ├── AsyncSequence.Concat.cs │ ├── AsyncSequence.Cycle.cs │ ├── AsyncSequence.CycleRange.cs │ ├── AsyncSequence.FromNullable.cs │ ├── AsyncSequence.RepeatRange.cs │ ├── AsyncSequence.Return.cs │ └── AsyncSequence.Successors.cs ├── Extensions │ └── AsyncEnumerableExtensions │ │ ├── AdjacentGroupBy.cs │ │ ├── AdjacentGroupByAwait.cs │ │ ├── AdjacentGroupByAwaitWithCancellation.cs │ │ ├── AnyOrElse.cs │ │ ├── AsyncGrouping.cs │ │ ├── AverageOrNone.cs │ │ ├── AverageOrNoneAwait.cs │ │ ├── AverageOrNoneAwaitWithCancellation.cs │ │ ├── Chunk.cs │ │ ├── ConcatToString.cs │ │ ├── ElementAtOrNone.cs │ │ ├── FirstOrNone.cs │ │ ├── Inspect.cs │ │ ├── InspectEmpty.cs │ │ ├── Interleave.cs │ │ ├── Intersperse.cs │ │ ├── JoinToString.cs │ │ ├── LastOrNone.cs │ │ ├── Materialize.cs │ │ ├── MaxOrNone.cs │ │ ├── Memoize.cs │ │ ├── Merge.cs │ │ ├── MinOrNone.cs │ │ ├── None.cs │ │ ├── Pairwise.cs │ │ ├── Partition.cs │ │ ├── PartitionEither.cs │ │ ├── PartitionResult.cs │ │ ├── PowerSet.cs │ │ ├── Scan.cs │ │ ├── Sequence.cs │ │ ├── Shuffle.cs │ │ ├── SingleOrNone.cs │ │ ├── SlidingWindow.cs │ │ ├── Split.cs │ │ ├── TakeEvery.cs │ │ ├── Transpose.cs │ │ ├── Traverse.cs │ │ ├── WhereNotNull.cs │ │ ├── WhereSelect.cs │ │ ├── WithFirst.cs │ │ ├── WithIndex.cs │ │ ├── WithLast.cs │ │ ├── WithPrevious.cs │ │ └── ZipLongest.cs ├── Funcky.Async.csproj ├── Functional │ ├── RetryAsync.cs │ └── RetryWithExceptionAsync.cs ├── IAsyncBuffer.cs ├── Monads │ ├── Either │ │ └── EitherAsyncExtensions.Traversable.cs │ ├── Option │ │ ├── ConfiguredOptionTaskAwaitable.cs │ │ ├── ConfiguredOptionValueTaskAwaitable.cs │ │ ├── OptionAsyncExtensions.GetAwaiter.cs │ │ ├── OptionAsyncExtensions.Traversable.cs │ │ ├── OptionExtensions.ToAsyncEnumerable.cs │ │ ├── OptionTaskAwaiter.cs │ │ └── OptionValueTaskAwaiter.cs │ └── Result │ │ └── ResultAsyncExtensions.Traversable.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── ValueTaskFactory.cs └── package.md ├── Funcky.FsCheck ├── Funcky.FsCheck.fsproj └── FunckyGenerators.fs ├── Funcky.SourceGenerator.Test ├── Funcky.SourceGenerator.Test.csproj ├── ModuleInitializer.cs ├── OrNoneGeneratorSnapshotTests.CopiesStringSyntaxAttributeFromOriginalDefinition.00.verified.cs ├── OrNoneGeneratorSnapshotTests.CopiesStringSyntaxAttributeFromOriginalDefinition.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateMultipleMethodsInASingleClass.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateMultipleMethodsInASingleClass.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithMultipleArgumentsToForward.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithMultipleArgumentsToForward.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithTheSingleArgumentCandidate.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithTheSingleArgumentCandidate.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GeneratesAllOverloadsForAGivenMethod.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GeneratesAllOverloadsForAGivenMethod.01.verified.cs ├── OrNoneGeneratorSnapshotTests.GeneratesMethodWhenTargetIsNotNullableAnnotated.00.verified.cs ├── OrNoneGeneratorSnapshotTests.GeneratesMethodWhenTargetIsNotNullableAnnotated.01.verified.cs ├── OrNoneGeneratorSnapshotTests.cs └── TestHelper.cs ├── Funcky.SourceGenerator ├── ApplyGenerator.cs ├── CodeSnippets.cs ├── Funcky.SourceGenerator.csproj ├── MethodPartial.cs ├── OrNoneFromTryPatternGenerator.cs └── OrNoneFromTryPatternPartial.cs ├── Funcky.Test.Internal ├── Data │ └── Person.cs ├── DescendingIntComparer.cs ├── Funcky.Test.Internal.csproj └── SelectorTransformation.cs ├── Funcky.Test ├── DownCastTest.cs ├── Extensions │ ├── DictionaryExtensionTest.cs │ ├── DisposableCulture.cs │ ├── EnumerableExtensions │ │ ├── AdjacentGroupByTest.cs │ │ ├── AnyOrElseTest.cs │ │ ├── AverageOrNoneTest.cs │ │ ├── ChunkTest.cs │ │ ├── ConcatToStringTest.cs │ │ ├── ElementAtOrNoneTest.cs │ │ ├── FailOnEnumerationList.cs │ │ ├── FirstSingleLastOrNoneTest.cs │ │ ├── ForEachTest.cs │ │ ├── GetNonEnumeratedCountOrNoneTest.cs │ │ ├── InspectEmptyTest.cs │ │ ├── InspectTest.cs │ │ ├── InterleaveTest.cs │ │ ├── IntersperseTest.cs │ │ ├── JoinToStringTest.cs │ │ ├── MaterializeTest.cs │ │ ├── MaxByOrNoneTest.cs │ │ ├── MaxOrNoneTest.cs │ │ ├── MemoizeTest.cs │ │ ├── MergeTest.cs │ │ ├── MinByOrNoneTest.cs │ │ ├── MinOrNoneTest.cs │ │ ├── PairwiseTest.cs │ │ ├── PartitionEitherTest.cs │ │ ├── PartitionResultTest.cs │ │ ├── PartitionTest.cs │ │ ├── PowerSetTest.cs │ │ ├── ScanTest.cs │ │ ├── SequenceTest.cs │ │ ├── ShuffleTest.cs │ │ ├── SlidingWindowTest.cs │ │ ├── SplitTest.cs │ │ ├── TakeEveryTest.cs │ │ ├── TransposeTest.cs │ │ ├── WhereNotNullTest.cs │ │ ├── WhereSelectTest.cs │ │ ├── WithFirstTest.cs │ │ ├── WithIndexTest.cs │ │ ├── WithLastTest.cs │ │ ├── WithPreviousTest.cs │ │ └── ZipLongestTest.cs │ ├── FunctionCompositionTest.cs │ ├── HttpHeadersNonValidatedExtensionsTest.cs │ ├── ImmutableListExtensions │ │ ├── IndexOfOrNoneTest.cs │ │ └── LastIndexOfOrNoneTest.cs │ ├── ListExtensionsTest.cs │ ├── NoStream.cs │ ├── ParseExtensions │ │ ├── ParseExtensionsTest.CacheControlHeaderValue.cs │ │ ├── ParseExtensionsTest.Char.cs │ │ ├── ParseExtensionsTest.Generic.cs │ │ └── ParseExtensionsTest.Version.cs │ ├── ParseExtensionsTest.cs │ ├── PriorityQueueExtensionsTest.cs │ ├── QueryableExtensions │ │ ├── ElementAtOrNoneTest.cs │ │ ├── FirstOrNoneTest.cs │ │ ├── LastOrNoneTest.cs │ │ ├── PreventAccidentalUseAsEnumerableTest.cs │ │ └── SingleOrNoneTest.cs │ ├── RangeExtensionTest.cs │ ├── SequenceTest.cs │ ├── SideEffect.cs │ ├── SkipOnMonoFact.cs │ ├── StreamExtensionsTest.cs │ └── StringExtensions │ │ ├── ChunkOnStringTest.cs │ │ ├── IndexOfTest.cs │ │ ├── LazySplitTest.cs │ │ ├── SlidingWindowOnStringTest.cs │ │ └── SplitLinesTest.cs ├── Funcky.Test.csproj ├── FunctionalClass │ ├── ActionToUnitTest.cs │ ├── ApplyTest.cs │ ├── CurryTest.cs │ ├── FlipTest.cs │ ├── FnTest.cs │ ├── IdentityTest.cs │ ├── NoOperationTest.cs │ ├── PredicateCompositionTest.cs │ ├── RetryTest.cs │ ├── RetryWithExceptionTest.cs │ └── UncurryTest.cs ├── Monads │ ├── EitherTest.Convenience.cs │ ├── EitherTest.Monad.cs │ ├── EitherTest.Sequence.cs │ ├── EitherTest.ToOption.cs │ ├── EitherTest.cs │ ├── LazyTest.Monad.cs │ ├── LazyTest.cs │ ├── Option.FromBoolean.cs │ ├── Option.ListPatternTest.cs │ ├── OptionEqualityComparerTest.cs │ ├── OptionExtensionsTest.cs │ ├── OptionImplicitConversionTest.cs │ ├── OptionJsonConverterTest.cs │ ├── OptionTest.Comparable.cs │ ├── OptionTest.Convenience.cs │ ├── OptionTest.Monad.cs │ ├── OptionTest.Sequence.cs │ ├── OptionTest.cs │ ├── ReaderTest.Monad.cs │ ├── ReaderTest.cs │ ├── ResultTest.Convenience.cs │ ├── ResultTest.Monad.cs │ ├── ResultTest.Sequence.cs │ └── ResultTest.cs ├── Sequence │ ├── ConcatTest.cs │ ├── CycleMaterializedTest.cs │ ├── CycleRangeTest.cs │ ├── CycleTest.cs │ ├── RepeatMaterializedTest.cs │ ├── RepeatRangeTest.cs │ ├── ReturnTest.cs │ └── SuccessorsTest.cs ├── TestUtils │ ├── CheckAssert.cs │ ├── CountCreation.cs │ ├── EnumerableExtensions.cs │ ├── EnumerateOnce.cs │ ├── EverythingIsEqual.cs │ ├── FailOnCall.cs │ ├── FailOnEnumerateCollection.cs │ ├── FailOnEnumerateCollectionWrapper.cs │ ├── FailOnEnumerateList.cs │ ├── FailOnEnumerateListWrapper.cs │ ├── FailOnEnumerateReadOnlyCollection.cs │ ├── FailOnEnumerationSequence.cs │ ├── IMyInterface.cs │ ├── MyClass.cs │ ├── OptionProducer.cs │ ├── QueryableExtensions.cs │ ├── RepeatingSequence.cs │ └── SideEffect.cs ├── UnitTest.cs └── UpCastTest.cs ├── Funcky.TrimmingTest ├── Funcky.TrimmingTest.csproj └── Program.cs ├── Funcky.Xunit.Test ├── Extensions │ └── ToTheoryDataExtensionTest.cs ├── Funcky.Xunit.Test.csproj └── FunctionalAssertClass │ ├── ErrorTest.cs │ ├── LeftTest.cs │ ├── NoneTest.cs │ ├── OkTest.cs │ ├── RightTest.cs │ └── SomeTest.cs ├── Funcky.Xunit.v3.Test ├── Funcky.Xunit.v3.Test.csproj └── Serializers │ ├── EitherSerializerTest.cs │ ├── OptionSerializerTest.cs │ └── UnitSerializerTest.cs ├── Funcky.Xunit.v3 ├── Funcky.Xunit.v3.csproj ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── RegisterEitherSerializerAttribute.cs ├── RegisterOptionSerializerAttribute.cs ├── RegisterUnitSerializerAttribute.cs ├── Serializers │ ├── EitherSerializer.cs │ ├── OptionSerializer.cs │ ├── StringExtensions.cs │ └── UnitSerializer.cs ├── build │ └── Funcky.Xunit.v3.targets └── package.md ├── Funcky.Xunit ├── CodeAnalysis │ └── AssertMethodHasOverloadWithExpectedValueAttribute.cs ├── Extensions │ └── ToTheoryDataExtension.cs ├── Funcky.Xunit.csproj ├── FunctionalAssert │ ├── EqualOrThrow.cs │ ├── Error.cs │ ├── Left.cs │ ├── None.cs │ ├── Ok.cs │ ├── Right.cs │ └── Some.cs ├── FunctionalAssertException.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── package.md └── readme.md ├── Funcky.sln.DotSettings ├── Funcky.slnx ├── Funcky ├── Buffers │ ├── CollectionBuffer.cs │ └── ListBuffer.cs ├── CodeAnalysis │ ├── NonDefaultableAttribute.cs │ ├── SyntaxSupportOnlyAttribute.cs │ └── UseWithArgumentNamesAttribute.cs ├── Compatibility │ ├── EnumerableCompatibility.cs │ ├── ExceptionUtilities.cs │ ├── StringExtensions.cs │ └── TimeSpanExtensions.cs ├── CompatibilitySuppressions.xml ├── Discard.cs ├── DownCast.cs ├── EitherOrBoth.cs ├── Extensions │ ├── ActionExtensions │ │ ├── Compose.cs │ │ ├── Curry.cs │ │ ├── Flip.cs │ │ └── Uncurry.cs │ ├── DictionaryExtensions.cs │ ├── EnumerableExtensions │ │ ├── AdjacentGroupBy.cs │ │ ├── AnyOrElse.cs │ │ ├── AverageOrNone.cs │ │ ├── Chunk.cs │ │ ├── ConcatToString.cs │ │ ├── EitherPartitions.cs │ │ ├── ElementAtOrNone.cs │ │ ├── FirstOrNone.cs │ │ ├── ForEach.cs │ │ ├── GetNonEnumeratedCountOrNone.cs │ │ ├── Grouping.cs │ │ ├── Inspect.cs │ │ ├── InspectEmpty.cs │ │ ├── Interleave.cs │ │ ├── Intersperse.cs │ │ ├── JoinToString.cs │ │ ├── LastOrNone.cs │ │ ├── Materialize.cs │ │ ├── MaxByOrNone.cs │ │ ├── MaxOrNone.cs │ │ ├── Memoize.cs │ │ ├── Merge.cs │ │ ├── MinByOrNone.cs │ │ ├── MinOrNone.cs │ │ ├── None.cs │ │ ├── Pairwise.cs │ │ ├── Partition.cs │ │ ├── PartitionEither.cs │ │ ├── PartitionResult.cs │ │ ├── Partitions.cs │ │ ├── PowerSet.cs │ │ ├── ResultPartitions.cs │ │ ├── Scan.cs │ │ ├── Sequence.cs │ │ ├── Shuffle.cs │ │ ├── SingleOrNone.cs │ │ ├── SlidingWindow.cs │ │ ├── Split.cs │ │ ├── TakeEvery.cs │ │ ├── Transpose.cs │ │ ├── Traverse.cs │ │ ├── ValueWithFirst.cs │ │ ├── ValueWithIndex.cs │ │ ├── ValueWithLast.cs │ │ ├── ValueWithPrevious.cs │ │ ├── WhereNotNull.cs │ │ ├── WhereSelect.cs │ │ ├── WithFirst.cs │ │ ├── WithIndex.cs │ │ ├── WithLast.cs │ │ ├── WithPrevious.cs │ │ └── ZipLongest.cs │ ├── EnumeratorExtensions.cs │ ├── FuncExtensions │ │ ├── Compose.cs │ │ ├── Curry.cs │ │ ├── Flip.cs │ │ └── Uncurry.cs │ ├── HttpHeadersExtensions.cs │ ├── HttpHeadersNonValidatedExtensions.cs │ ├── JsonSerializerOptionsExtensions.cs │ ├── ListExtensions │ │ ├── IImmutableList.IndexOfOrNone.cs │ │ ├── IImmutableList.LastIndexOfOrNone.cs │ │ ├── IList.cs │ │ └── List.cs │ ├── OrderedDictionaryExtensions.cs │ ├── ParseExtensions │ │ ├── ParseExtensions.AssemblyNameInfo.cs │ │ ├── ParseExtensions.BigInteger.cs │ │ ├── ParseExtensions.Boolean.cs │ │ ├── ParseExtensions.Char.cs │ │ ├── ParseExtensions.DateOnly.cs │ │ ├── ParseExtensions.DateTime.cs │ │ ├── ParseExtensions.DateTimeOffset.cs │ │ ├── ParseExtensions.Enum.cs │ │ ├── ParseExtensions.GenericNumber.cs │ │ ├── ParseExtensions.GenericParseable.cs │ │ ├── ParseExtensions.Guid.cs │ │ ├── ParseExtensions.IpPrimitives.cs │ │ ├── ParseExtensions.Numbers.cs │ │ ├── ParseExtensions.System.Net.Http.cs │ │ ├── ParseExtensions.TimeOnly.cs │ │ ├── ParseExtensions.TimeSpan.cs │ │ ├── ParseExtensions.TypeName.cs │ │ └── ParseExtensions.Version.cs │ ├── PriorityQueueExtensions.cs │ ├── QueryableExtensions │ │ ├── ElementAtOrNone.cs │ │ ├── FirstOrNone.cs │ │ ├── LastOrNone.cs │ │ └── SingleOrNone.cs │ ├── QueueExtensions.cs │ ├── RangeExtensions.cs │ ├── StreamExtensions.cs │ └── StringExtensions │ │ ├── Chunk.cs │ │ ├── IndexOfAnyOrNone.cs │ │ ├── IndexOfOrNone.cs │ │ ├── LastIndexOfAnyOrNone.cs │ │ ├── LastIndexOfOrNone.cs │ │ ├── SlidingWindow.cs │ │ ├── SplitLazy.cs │ │ └── SplitLines.cs ├── Funcky.csproj ├── Functional │ ├── ActionToUnit.cs │ ├── BoolConstant.cs │ ├── Curry.cs │ ├── Flip.cs │ ├── Fn.cs │ ├── Identity.cs │ ├── NoOperation.cs │ ├── PredicateComposition.cs │ ├── Retry.cs │ ├── RetryWithException.cs │ ├── Uncurry.cs │ └── UnitToAction.cs ├── IBuffer.cs ├── ILLink.LinkAttributes.xml ├── Internal │ ├── Aggregators │ │ ├── DecimalAverageAggregator.cs │ │ ├── DoubleAverageAggregator.cs │ │ ├── FloatAverageAggregator.cs │ │ ├── MaxAggregator.cs │ │ └── MinAggregator.cs │ ├── ComparisonResult.cs │ ├── FailToOption.cs │ ├── ListWithSelector.cs │ ├── Mixer.cs │ ├── OptionTupleExtensions.cs │ ├── PartitionBuilder.cs │ ├── RangeEnumerable.cs │ ├── SlidingWindowQueue.cs │ ├── UnsafeEither.cs │ ├── Validators │ │ ├── ChunkSizeValidator.cs │ │ └── WindowWidthValidator.cs │ └── ValueMapper.cs ├── Monads │ ├── Either │ │ ├── Either.Convenience.cs │ │ ├── Either.Core.cs │ │ ├── Either.Debugger.cs │ │ ├── Either.Monad.cs │ │ ├── EitherExtensions.Traversable.cs │ │ ├── EitherExtensions.cs │ │ └── IEither.cs │ ├── Lazy │ │ ├── Lazy.Core.cs │ │ └── Lazy.Monad.cs │ ├── Option │ │ ├── IOption.cs │ │ ├── Option.Comparable.cs │ │ ├── Option.Convenience.cs │ │ ├── Option.Core.cs │ │ ├── Option.Debugger.cs │ │ ├── Option.Equality.cs │ │ ├── Option.FromBoolean.cs │ │ ├── Option.FromNullable.cs │ │ ├── Option.ListPattern.cs │ │ ├── Option.Monad.cs │ │ ├── OptionComparer.cs │ │ ├── OptionEqualityComparer.cs │ │ ├── OptionExtensions.Traversable.cs │ │ ├── OptionExtensions.cs │ │ └── OptionJsonConverter.cs │ ├── Reader │ │ ├── Reader.Core.cs │ │ └── Reader.Monad.cs │ └── Result │ │ ├── Result.Convenience.cs │ │ ├── Result.Core.cs │ │ ├── Result.Debugger.cs │ │ ├── Result.Monad.cs │ │ └── ResultExtensions.Traversable.cs ├── Obsolete │ └── ImmutableListExtensions │ │ └── IndexOfOrNone.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── RequireClass.cs ├── RequireStruct.cs ├── RetryPolicies │ ├── ConstantDelayPolicy.cs │ ├── DoNotRetryPolicy.cs │ ├── ExponentialBackOffRetryPolicy.cs │ ├── IRetryPolicy.cs │ ├── LinearBackOffRetryPolicy.cs │ └── NoDelayRetryPolicy.cs ├── Sequence │ ├── Sequence.Concat.cs │ ├── Sequence.Cycle.cs │ ├── Sequence.CycleMaterialized.cs │ ├── Sequence.CycleRange.cs │ ├── Sequence.FromNullable.cs │ ├── Sequence.RepeatMaterialized.cs │ ├── Sequence.RepeatRange.cs │ ├── Sequence.Return.cs │ └── Sequence.Successors.cs ├── Unit.cs ├── UpCast.cs └── build │ └── Funcky.targets ├── GlobalUsings.Test.props ├── GlobalUsings.props ├── LICENSE-Apache ├── LICENSE-MIT ├── NuGet.config ├── PublicApiAnalyzers.targets ├── README.md ├── SupportPolicy.md ├── Website ├── build.fsx ├── fonts.css ├── fonts │ ├── open-sans │ │ ├── OpenSans-Bold.woff2 │ │ ├── OpenSans-Regular.woff2 │ │ └── license.txt │ ├── source-code-pro │ │ ├── SourceCodePro-Regular.ttf.woff2 │ │ └── license.txt │ └── vollkorn │ │ ├── Vollkorn-Bold.woff2 │ │ └── license.txt ├── icons │ ├── LICENSE │ ├── diamond.svg │ ├── package.svg │ └── puzzle.svg ├── index.html ├── redirect.html ├── style.css └── tabs.js ├── changelog.md ├── global.json └── typos.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.{props,slnx,targets}] 12 | indent_style = tab 13 | 14 | [*.{slnx}] 15 | end_of_line = crlf 16 | 17 | [*.{yml,yaml}] 18 | indent_size = 2 19 | 20 | [*.verified.cs] 21 | trim_trailing_whitespace = false 22 | insert_final_newline = false 23 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | d713a38ae4dd7fa9837f8c98dc925a49d4063d0d 2 | 8ddd51c74401701d02ed57b2989a9071790d5721 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | # Visual Studio always saves the solution 3 | # file with CRLF even if we manually convert it to 4 | # LF beforehand. 5 | *.slnx text eol=crlf 6 | *.cs diff=csharp 7 | -------------------------------------------------------------------------------- /.github/workflows/publish-nightly-package.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Publish Nightly Packages 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-dotnet@v4 14 | name: Install Current .NET SDK 15 | - name: Pack Packages 16 | run: dotnet pack --output nupkg --version-suffix "nightly.$(git rev-parse --short "${{github.sha}}")" 17 | - name: Push Packages 18 | run: dotnet nuget push --source https://nuget.pkg.github.com/polyadic/index.json --api-key ${{secrets.GITHUB_TOKEN}} nupkg/Funcky.*.nupkg 19 | -------------------------------------------------------------------------------- /.github/workflows/spelling.yml: -------------------------------------------------------------------------------- 1 | name: Spelling 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [pull_request] 7 | 8 | env: 9 | CLICOLOR: 1 10 | 11 | jobs: 12 | spelling: 13 | name: Spell Check with Typos 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Actions Repository 17 | uses: actions/checkout@v4 18 | - name: Spell Check Repo 19 | uses: crate-ci/typos@v1.29.4 20 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jan Hohenheim 2 | Mathias Fischler 3 | Mathias Fischler 4 | Tau Gärtli <4602612+bash@users.noreply.github.com> 5 | Tau Gärtli 6 | Tau Gärtli 7 | Tau Gärtli 8 | Thomas Bruderer 9 | Thomas Bruderer 10 | -------------------------------------------------------------------------------- /Analyzers.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Artwork/logo-green-rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Artwork/logo-green-rounded.png -------------------------------------------------------------------------------- /Artwork/logo-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Artwork/logo-green.png -------------------------------------------------------------------------------- /Artwork/logo-red-rounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Artwork/logo-red-rounded.png -------------------------------------------------------------------------------- /Artwork/logo-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Artwork/logo-red.png -------------------------------------------------------------------------------- /Documentation/Marble Graphics.txt: -------------------------------------------------------------------------------- 1 | To create the marble graphics we used swirly: 2 | 3 | Swirly - A marble diagram generator 4 | 5 | Source: 6 | https://github.com/timdp/swirly 7 | 8 | Interactive Webservice 9 | https://swirly.dev/ -------------------------------------------------------------------------------- /Documentation/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Funcky Contributors"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Funcky User Guide" 7 | 8 | [output.html] 9 | site-url = "/funcky/book" 10 | additional-css = ["custom.css"] 11 | git-repository-url = "https://github.com/polyadic/funcky" 12 | git-repository-icon = "fa-github" 13 | -------------------------------------------------------------------------------- /Documentation/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --caption-font-size: 14px; 3 | --content-max-width: 800px; 4 | } 5 | 6 | /* Emphasized text after a code block is treated as its caption */ 7 | pre + p > em { 8 | font-size: var(--caption-font-size); 9 | margin-top: -12px; 10 | margin-bottom: 20px; 11 | display: block; 12 | } 13 | -------------------------------------------------------------------------------- /Documentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@polyadic/funcky-documentation", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build-marble-graphics": "node scripts/build-marble-graphics.mjs" 6 | }, 7 | "devDependencies": { 8 | "@swirly/parser": "^0.21.0", 9 | "@swirly/renderer-node": "^0.21.0", 10 | "@swirly/theme-default-light": "^0.21.0", 11 | "@swirly/theme-default-dark": "^0.21.0", 12 | "glob": "^7.1.7" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Documentation/src/case-studies/transpose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Documentation/src/case-studies/transpose.png -------------------------------------------------------------------------------- /Documentation/src/changelog.md: -------------------------------------------------------------------------------- 1 | {{#include ../../changelog.md}} 2 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/adjacent-group-by.md: -------------------------------------------------------------------------------- 1 | ## AdjacentGroupBy 2 | 3 | 4 | 5 | 6 | A marble diagram showing the AdjacentGroupBy operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/adjacent-group-by.swirly: -------------------------------------------------------------------------------- 1 | % AdjacentGroupBy 2 | 3 | ---a-a-a-b-b-c-c-a-a- 4 | 5 | > AdjacentGroupBy 6 | 7 | w = ----a-a-a--- 8 | 9 | x = ----b-b--- 10 | 11 | y = ----c-c--- 12 | 13 | z = ----a-a--- 14 | 15 | --w----x----y----z------ 16 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/average-or-none.md: -------------------------------------------------------------------------------- 1 | ## AverageOrNone 2 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/cartesian-product.swirly: -------------------------------------------------------------------------------- 1 | % CartesianProduct 2 | 3 | ------a------b------c------| 4 | 5 | ------1------2------3------4------| 6 | 7 | > CartesianProduct 8 | 9 | 10 | -(a1)-(a2)-(a3)-(a4)-(b1)-(b2)-(b3)-(b4)-(c1)-(c2)-(c3)-(c4)-| 11 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/chunk.swirly: -------------------------------------------------------------------------------- 1 | % Chunk 2 | 3 | ------1---2---3---4---5---6---7------| 4 | 5 | > Chunk(3) 6 | 7 | 8 | a=---1---2---3---| 9 | 10 | b=---4---5---6---| 11 | 12 | c=---7---| 13 | 14 | 15 | ---a------b------c---------| 16 | 17 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/first-or-none.md: -------------------------------------------------------------------------------- 1 | ## FirstOrNone 2 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/for-each.md: -------------------------------------------------------------------------------- 1 | ## ForEach 2 | With the `.ForEach` extension method, you can invoke an action for each item in an enumerable, just like a `foreach` statement would allow you to do. 3 | 4 | This method is already available in .NET, but just on `List`s, and it makes sense for it to be available on every enumerable. 5 | 6 | Keep in mind that `.ForEach` is imperative and only expects an `Action`. It should not be used to change state of anything outside of the `.ForEach`. 7 | If you want to combine the enumerable into a result, consider using `.Aggregate()`, as that is designed for such use-cases. 8 | 9 | ### Example 10 | 11 | ```csharp 12 | // Original 13 | foreach (var item in Items) 14 | { 15 | DoSomething(item); 16 | } 17 | 18 | // Using `.ForEach` 19 | Items.ForEach(DoSomething); // equivalent to Items.ForEach(item => DoSomething(item)); 20 | ``` 21 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/interleave.md: -------------------------------------------------------------------------------- 1 | ## Interleave 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Interleave operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/interleave.swirly: -------------------------------------------------------------------------------- 1 | % Interleave 2 | 3 | ------1---2---3---4---5---6---7------| 4 | 5 | ------a---b---c---d------| 6 | 7 | ------A---B---C---D---E------| 8 | 9 | > Interleave 10 | 11 | ---1-a-A-2-b-B-3-c-C-4-d-D-5-E-6-7---| 12 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/intersperse.md: -------------------------------------------------------------------------------- 1 | ## Intersperse 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Interleave operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/intersperse.swirly: -------------------------------------------------------------------------------- 1 | % Intersperse 2 | 3 | ---9-----7-----3-----4-----6---| 4 | 5 | > Intersperse(U) 6 | 7 | 8 | ---9--U--7--U--3--U--4--U--6---| 9 | 10 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/last-or-none.md: -------------------------------------------------------------------------------- 1 | ## LastOrNone 2 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/materialize.md: -------------------------------------------------------------------------------- 1 | ## Materialize -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/merge.md: -------------------------------------------------------------------------------- 1 | ## Merge 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Merge operation 7 | 8 | 9 | 10 | ### Examples 11 | 12 | Given two sequences which are already ordered the same way: 13 | 14 | ``` 15 | sequence1 = [1, 2, 7, 9, 14] 16 | sequence2 = [3, 6, 8, 11, 12, 16] 17 | ``` 18 | 19 | By merging we get one single sequence with the all elements of the given sequences with the same order. 20 | 21 | ``` 22 | sequence1.Merge(sequence2) => 23 | [1, 2, 7, 9, 14 ] 24 | [ 3, 6, 8, 11, 12, 16] 25 | ------------------------------------- 26 | [1, 2, 3, 6, 7, 8, 9, 11, 12, 14, 16] 27 | ``` 28 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/merge.swirly: -------------------------------------------------------------------------------- 1 | % Merge 2 | 3 | ------c---d---g---h------| 4 | 5 | ------a---b---f---i---j---| 6 | 7 | > Merge 8 | 9 | 10 | ------a---b---c---d---f---g---h---i---j---| 11 | 12 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/none.md: -------------------------------------------------------------------------------- 1 | ## None 2 | With the `.None` extension method, you can make `!enumerable.Any()` calls easier. 3 | 4 | That's all there is. You can replace: 5 | ```csharp 6 | if (!enumerable.Any()) { ... } 7 | ``` 8 | 9 | with the easier to read 10 | 11 | ```csharp 12 | if (enumerable.None()) { ... } 13 | ``` 14 | 15 | Just like with `.Any()`, you can additionally pass a predicate as a parameter: 16 | 17 | ```csharp 18 | if (enumerable.None(item => item.SomeNumericProperty == 2) { ... } 19 | ``` -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/pairwise.md: -------------------------------------------------------------------------------- 1 | ## PairWise 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Pairwise operation 7 | 8 | 9 | 10 | ### Example 11 | 12 | ``` 13 | animals = [ 🐵, 🐶, 🐺, 🐱, 🦄, 🐷, 🦁] 14 | 15 | animals.PairWise() => 16 | [[🐵, 🐶], 17 | [🐶, 🐺], 18 | [🐺, 🐱], 19 | [🐱, 🦄], 20 | [🦄, 🐷], 21 | [🐷, 🦁]] 22 | ``` 23 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/pairwise.swirly: -------------------------------------------------------------------------------- 1 | % Pairwise 2 | 3 | ------1----2----3----4----5----6----7---| 4 | 5 | > Pairwise 6 | 7 | ------(12)-(23)-(34)-(45)-(56)-(67)-----| 8 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/partition.md: -------------------------------------------------------------------------------- 1 | ## Partition 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Partition operation 7 | 8 | 9 | 10 | ### Example 11 | 12 | ``` 13 | plantBasedFood = [🍉, 🍩 , 🎂, 🍌, 🍫, 🍓, 🍒, 🥕, 🌽, 🥧 ] 14 | 15 | plantBasedFood.Partition(IsProcessedFood?) 16 | => [[🍩 , 🎂, 🍫, 🥧], 17 | [🍉, 🍌, 🍓, 🍒, 🥕, 🌽]] 18 | ``` 19 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/partition.swirly: -------------------------------------------------------------------------------- 1 | % Partition 2 | 3 | ------1---a---b---3---4---c---d---7------| 4 | 5 | > Partition(IsDigit) 6 | 7 | x=------1---3---4---7------| 8 | 9 | y=------a---b---c---d------| 10 | 11 | ------x------y------| 12 | 13 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/power-set.md: -------------------------------------------------------------------------------- 1 | ## PowerSet 2 | 3 | 4 | 5 | 6 | A marble diagram showing the PowerSet operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/power-set.swirly: -------------------------------------------------------------------------------- 1 | % PowerSet 2 | 3 | [styles] 4 | higher_order_angle = 30 5 | 6 | ---1-----b-----C---| 7 | 8 | 9 | > PowerSet 10 | 11 | G=---| 12 | 13 | H=---1---| 14 | 15 | I=---b---| 16 | 17 | J=---C---| 18 | 19 | K=---1---b---| 20 | 21 | L=---1---C---| 22 | 23 | M=---b---C---| 24 | 25 | N=---1---b---C---| 26 | 27 | 28 | ---G---H---I---J---K---L---M---N---| 29 | 30 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/shuffle.md: -------------------------------------------------------------------------------- 1 | ## Shuffle 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Shuffle operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/shuffle.swirly: -------------------------------------------------------------------------------- 1 | 2 | % Shuffle 3 | 4 | 5 | ---A---B---C---D---E---F---| 6 | 7 | > Shuffle 8 | 9 | ---C---B---F---D---E---A---| 10 | 11 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/sliding-window.md: -------------------------------------------------------------------------------- 1 | ## SlidingWindow 2 | 3 | 4 | 5 | 6 | A marble diagram showing the SlidingWindow operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/sliding-window.swirly: -------------------------------------------------------------------------------- 1 | % SlidingWindow 2 | 3 | ------2----g----5----s----4----l----x----8----k----| 4 | 5 | > SlidingWindow(4) 6 | 7 | 8 | A=----2----g----5----s----| 9 | 10 | B=----g----5----s----4----| 11 | 12 | C=----5----s----4----l----| 13 | 14 | D=----s----4----l----x----| 15 | 16 | E=----4----l----x----8----| 17 | 18 | F=----l----x----8----k----| 19 | 20 | ------A----B----C----D----E----F-----| 21 | 22 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/split.md: -------------------------------------------------------------------------------- 1 | ## Split 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Split operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/split.swirly: -------------------------------------------------------------------------------- 1 | % Split 2 | 3 | ------s-e-d-X-X-2-f-a-s-o-i-X-5-X-w-1-s-d-e---| 4 | 5 | > Split(X) 6 | 7 | 8 | A=---s---e---d---| 9 | 10 | B=------| 11 | 12 | C=---2---f---a---s---o---i---| 13 | 14 | D=---5---| 15 | 16 | E=---w---1---s---d---e---| 17 | 18 | ------A----B----C----D----E--------| 19 | 20 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/take-every.md: -------------------------------------------------------------------------------- 1 | ## TakeEvery 2 | 3 | 4 | 5 | 6 | A marble diagram showing the TakeEvery operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/take-every.swirly: -------------------------------------------------------------------------------- 1 | % TakeEvery 2 | 3 | ------c----2----g----A----p----X----3---2---| 4 | 5 | > TakeEvery(3) 6 | 7 | ------c--------------A--------------3---| 8 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/transpose.md: -------------------------------------------------------------------------------- 1 | ## Transpose 2 | 3 | 4 | 5 | 6 | A marble diagram showing the Transpose operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/transpose.swirly: -------------------------------------------------------------------------------- 1 | % Transpose 2 | 3 | [styles] 4 | higher_order_angle = 30 5 | 6 | 7 | A=---2---g---5---s---| 8 | 9 | B=---4---l---x---8---| 10 | 11 | C=---x---3---q---p---| 12 | 13 | ------A------B------C------| 14 | 15 | > Transpose 16 | 17 | 18 | A=----2----4----x----| 19 | 20 | B=----g----l----3----| 21 | 22 | C=----5----x----p----| 23 | 24 | D=----s----8----q----| 25 | 26 | ---A---B---C---D---| 27 | 28 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/where-not-null.md: -------------------------------------------------------------------------------- 1 | ## WhereNotNull 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WhereNotNull operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/where-not-null.swirly: -------------------------------------------------------------------------------- 1 | % WhereNotNull 2 | 3 | ------N----2----4----N----N----1----7---| 4 | N:=Ø 5 | 6 | > WhereNotNull 7 | 8 | ------2----4----1----7---| 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/where-select.md: -------------------------------------------------------------------------------- 1 | ## WhereSelect 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WhereSelect operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/where-select.swirly: -------------------------------------------------------------------------------- 1 | % WhereSelect 2 | 3 | ------A----2----4----N----X----1----P---| 4 | 5 | > WhereSelect(IsDigit ? Ø : ToLower) 6 | 7 | ------a--------------n----x---------p---| 8 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-first.md: -------------------------------------------------------------------------------- 1 | ## WithFirst 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WithFirst operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-first.swirly: -------------------------------------------------------------------------------- 1 | % WithFirst 2 | 3 | ------A----l----3----j----g---| 4 | 5 | > WithFirst 6 | 7 | ------(At)-(lf)-(3f)-(jf)-(gf)---| 8 | t := true 9 | f := false 10 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-index.md: -------------------------------------------------------------------------------- 1 | ## WithIndex 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WithIndex operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-index.swirly: -------------------------------------------------------------------------------- 1 | % WithIndex 2 | 3 | ------c----d----g----j----g---| 4 | 5 | > WithIndex 6 | 7 | 8 | ------(c0)-(d1)-(g2)-(j3)-(g4)---| 9 | 10 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-last.md: -------------------------------------------------------------------------------- 1 | ## WithLast 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WithLast operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-last.swirly: -------------------------------------------------------------------------------- 1 | % WithLast 2 | 3 | ------X----h----b----2----A------| 4 | 5 | > WithLast 6 | 7 | ------(Xf)-(hf)-(bf)-(2f)-(At)---| 8 | t := true 9 | f := false 10 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-previous.md: -------------------------------------------------------------------------------- 1 | ## WithPrevious 2 | 3 | 4 | 5 | 6 | A marble diagram showing the WithPrevious operation 7 | 8 | 9 | 10 | ### Example 11 | 12 | ``` 13 | animals = [ 🦄, 🐺, 🐷, 🦁, 🐵, 🐶 ] 14 | 15 | animals.WithPrevious() => 16 | [[∅, 🦄], 17 | [🦄, 🐺], 18 | [🐺, 🐷], 19 | [🐷, 🦁], 20 | [🦁, 🐵], 21 | [🐵, 🐶]] 22 | ``` 23 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/with-previous.swirly: -------------------------------------------------------------------------------- 1 | % WithPrevious 2 | 3 | ------1----2----3----4----5----6----7---| 4 | 5 | > WithPrevious 6 | 7 | ------(A1)-(12)-(23)-(34)-(45)-(56)-(67)-----| 8 | A:=Ø 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/zip-longest.md: -------------------------------------------------------------------------------- 1 | ## ZipLongest 2 | 3 | 4 | 5 | 6 | A marble diagram showing the ZipLongest operation 7 | 8 | 9 | -------------------------------------------------------------------------------- /Documentation/src/enumerable-extensions/zip-longest.swirly: -------------------------------------------------------------------------------- 1 | % ZipLongest 2 | 3 | 4 | ---A---D---C-----F---H---| 5 | 6 | 7 | ---1---3---4---| 8 | 9 | > ZipLongest 10 | 11 | ---(A1)(D3)-(C4)-F---H---| 12 | 13 | -------------------------------------------------------------------------------- /Documentation/src/fp/overview.md: -------------------------------------------------------------------------------- 1 | # Functional Programming 2 | 3 | * No side effects 4 | * Pure functions 5 | * Referential transparency 6 | * Follow the types 7 | * Higher order functions 8 | * Composition 9 | -------------------------------------------------------------------------------- /Documentation/src/functional-helpers/functional-helpers.md: -------------------------------------------------------------------------------- 1 | The static class `Funcky.Functional` is designed to be used with a static import (`using static Funcky.Functional;`). All examples will be as if `using static` was used. 2 | All the methods in `Funcky.Functional` are named to be easily understood without the functional prefix. They are general purpose, and their goal is to unify typical boilerplate code or be more expressive than typical C# ways of doing their job. -------------------------------------------------------------------------------- /Documentation/src/functional-helpers/identity.md: -------------------------------------------------------------------------------- 1 | The `Identity` function is designed to replace parameter-returning lambdas, like sometimes used in LINQ. 2 | 3 | Example 1: 4 | ```csharp 5 | // Method: 6 | public void FunctionExpectingSelector(Func selector) 7 | { 8 | // ... 9 | } 10 | 11 | // Usually: 12 | FunctionExpectingSelector(x => x); 13 | // Or: 14 | FunctionExpectingSelector(item => item); 15 | 16 | // With Identity: 17 | FunctionExpectingSelector(Identity); 18 | ``` 19 | 20 | Example 2 (typical `SelectMany` selector): 21 | ```csharp 22 | // Usually result of a query: 23 | IEnumerable> itemGroups = new[] { new[] { 1, 2, 3 }, new[] { 5, 6, 7 } }; 24 | 25 | // Goal: Get all items flattened. 26 | // Common approach: 27 | itemGroups.SelectMany(x => x); 28 | // Or: 29 | itemGroups.SelectMany(items => items); 30 | 31 | // With Identity: 32 | itemGroups.SelectMany(Identity); 33 | ``` -------------------------------------------------------------------------------- /Documentation/src/introduction.md: -------------------------------------------------------------------------------- 1 | Functional programming is the oldest of the three major programming paradigms, none the less it is the last which gets wide spread usage. Even in languages like C++, Java or C# we want to use a functional style of programming. 2 | 3 | Linq is the first Monad which got wide spread use in C#, and most C# programmers were not even aware of it being a monad, which probably helped. 4 | 5 | [Mark Seemann] points out that "Unfortunately, Maybe implementations often come with an API that enables you to ask a Maybe object if it's populated or empty, and a way to extract the value from the Maybe container. This misleads many programmers \[...]" 6 | 7 | This library is based on his example code, and should grow slowly to a library which helps to use and understand the Functional programming paradigm. Functional programming is side-effect free and the strong type system can be used to make illegal state impossible. 8 | 9 | Use functional programming as an additional asset to write correct code. 10 | 11 | [Mark Seemann]: https://blog.ploeh.dk/2019/02/04/how-to-get-the-value-out-of-the-monad/ 12 | -------------------------------------------------------------------------------- /Documentation/src/migration-guide.md: -------------------------------------------------------------------------------- 1 | # Migrating from 2.x to 3.0 2 | 3 | 1. Update to the latest 2.x version of Funcky (2.7.1) and fix all warnings. 4 | 2. Update to Funcky 3.0. 5 | 3. Run `dotnet format analyzers`. This will migrate `Option.None()` to `Option.None` for you. \ 6 | This command sometimes fails while loading your project(s). `--no-restore` might help with that. 7 | 4. Build and fix other compilation failures. 8 | 5. You might need to re-run `dotnet format analyzers` after fixing other errors. 9 | 6. **Important:** Check if you're using `System.Text.Json` to serialize `Option`s. 10 | This will no longer work automatically. You need to add the `OptionJsonConverter` yourself. 11 | -------------------------------------------------------------------------------- /Documentation/src/string-extensions.md: -------------------------------------------------------------------------------- 1 | # String Extensions 2 | 3 | ## IndexOf 4 | 5 | The classical `IndexOf` methods provide a special form of error handling by returning `-1` when nothing is found. 6 | This is very cumbersome and a potential footgun, since you're not forced to check the return value. 7 | 8 | Funcky offers extension methods on `string` for each overload of `IndexOf`, `IndexOfAny`, `LastIndexOf`, and `LastIndexOfAny`. 9 | The extension methods follow the simple convention of being suffixed with `OrNone`. 10 | 11 | 12 | ```csharp 13 | Option ParseKey(string input) 14 | => input.IndexOfOrNone('[') 15 | .Select(startIndex => ParseKeyWithMultipleParts(input, startIndex)) 16 | .GetOrElse(() => ParseRegularKey(input)); 17 | ``` 18 | *Example usage of `IndexOfOrNone`* -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/DiagnosticExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 5 | 6 | internal static class DiagnosticExtensions 7 | { 8 | // No static interfaces or IParsable in .NET Standard 2.0... 9 | private delegate bool TryParseDelegate(string? s, [NotNullWhen(true)] out T? value); 10 | 11 | public static bool TryGetIntProperty(this Diagnostic diagnostic, string propertyName, out int value) 12 | => TryGetProperty(diagnostic, propertyName, int.TryParse, out value); 13 | 14 | private static bool TryGetProperty(this Diagnostic diagnostic, string propertyName, TryParseDelegate parser, [NotNullWhen(true)] out T? value) 15 | { 16 | value = default; 17 | return diagnostic.Properties.TryGetValue(propertyName, out var stringValue) && parser(stringValue, out value); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp.Syntax; 3 | using Microsoft.CodeAnalysis.Text; 4 | 5 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 6 | 7 | internal static partial class SyntaxNodeExtensions 8 | { 9 | // getInnermostNodeForTie: true is important, because otherwise we might 10 | // get the ArgumentExpressionSyntax, which has the same span. 11 | public static InvocationExpressionSyntax? FindInvocationExpression(this SyntaxNode node, TextSpan span) 12 | => node.FindNode(span, getInnermostNodeForTie: true).FirstAncestorOrSelf(); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.Roslyn4.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.1 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.4.0 4 | $(DefineConstants);ROSLYN_4_4_0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Package/buildTransitive/Funcky.Analyzers.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Package/package.md: -------------------------------------------------------------------------------- 1 | Analyzers and code fixes that guide you towards idiomatic usage of [Funcky]. 2 | 3 | ```xml 4 | 5 | ``` 6 | 7 | [Funcky]: https://www.nuget.org/packages/Funcky 8 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/EnumerableRepeatWithAnyNumber.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Repeat(1337, 2); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/JoinToStringEmpty.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using Funcky.Extensions; 6 | 7 | namespace Funcky.Extensions 8 | { 9 | public static partial class EnumerableExtensions 10 | { 11 | [Pure] 12 | public static string ConcatToString(this IEnumerable source) 13 | => string.Concat(source); 14 | 15 | [Pure] 16 | public static string JoinToString(this IEnumerable source, string separator) 17 | => string.Join(separator, source); } 18 | } 19 | 20 | namespace ConsoleApplication1 21 | { 22 | class Program 23 | { 24 | private void Syntax() 25 | { 26 | var strings = new []{ "A", "B", "B", "A"}; 27 | 28 | var joined = strings.ConcatToString(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/JoinToStringEmpty.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using Funcky.Extensions; 6 | 7 | namespace Funcky.Extensions 8 | { 9 | public static partial class EnumerableExtensions 10 | { 11 | [Pure] 12 | public static string ConcatToString(this IEnumerable source) 13 | => string.Concat(source); 14 | 15 | [Pure] 16 | public static string JoinToString(this IEnumerable source, string separator) 17 | => string.Join(separator, source); } 18 | } 19 | 20 | namespace ConsoleApplication1 21 | { 22 | class Program 23 | { 24 | private void Syntax() 25 | { 26 | var strings = new []{ "A", "B", "B", "A"}; 27 | 28 | var joined = strings.JoinToString(string.Empty); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/JoinToStringEmptyConstant.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using Funcky.Extensions; 6 | 7 | namespace Funcky.Extensions 8 | { 9 | public static partial class EnumerableExtensions 10 | { 11 | [Pure] 12 | public static string ConcatToString(this IEnumerable source) 13 | => string.Concat(source); 14 | 15 | [Pure] 16 | public static string JoinToString(this IEnumerable source, string separator) 17 | => string.Join(separator, source); } 18 | } 19 | 20 | namespace ConsoleApplication1 21 | { 22 | class Program 23 | { 24 | const string Empty = ""; 25 | 26 | private void Syntax() 27 | { 28 | var strings = new []{ "A", "B", "B", "A"}; 29 | 30 | var joined = strings.ConcatToString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/JoinToStringEmptyConstant.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using Funcky.Extensions; 6 | 7 | namespace Funcky.Extensions 8 | { 9 | public static partial class EnumerableExtensions 10 | { 11 | [Pure] 12 | public static string ConcatToString(this IEnumerable source) 13 | => string.Concat(source); 14 | 15 | [Pure] 16 | public static string JoinToString(this IEnumerable source, string separator) 17 | => string.Join(separator, source); } 18 | } 19 | 20 | namespace ConsoleApplication1 21 | { 22 | class Program 23 | { 24 | const string Empty = ""; 25 | 26 | private void Syntax() 27 | { 28 | var strings = new []{ "A", "B", "B", "A"}; 29 | 30 | var joined = strings.JoinToString(Empty); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNever.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Empty(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNever.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Repeat("Hello world!", 0); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverFlipped.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Empty(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverFlipped.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Repeat(count: 0, element: "Hello world!"); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverQualification.expected: -------------------------------------------------------------------------------- 1 | namespace WithUsingStatic 2 | { 3 | using static System.Linq.Enumerable; 4 | 5 | public class C 6 | { 7 | public void M() 8 | { 9 | _ = Empty(); 10 | } 11 | } 12 | } 13 | 14 | namespace WithFullyQualifiedName 15 | { 16 | public class C 17 | { 18 | public void M() 19 | { 20 | _ = System.Linq.Enumerable.Empty(); 21 | } 22 | } 23 | } 24 | 25 | namespace WithUsingAlias 26 | { 27 | using Seq = System.Linq.Enumerable; 28 | 29 | public class C 30 | { 31 | public void M() 32 | { 33 | _ = Seq.Empty(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverQualification.input: -------------------------------------------------------------------------------- 1 | namespace WithUsingStatic 2 | { 3 | using static System.Linq.Enumerable; 4 | 5 | public class C 6 | { 7 | public void M() 8 | { 9 | _ = Repeat("value", 0); 10 | } 11 | } 12 | } 13 | 14 | namespace WithFullyQualifiedName 15 | { 16 | public class C 17 | { 18 | public void M() 19 | { 20 | _ = System.Linq.Enumerable.Repeat("value", 0); 21 | } 22 | } 23 | } 24 | 25 | namespace WithUsingAlias 26 | { 27 | using Seq = System.Linq.Enumerable; 28 | 29 | public class C 30 | { 31 | public void M() 32 | { 33 | _ = Seq.Repeat("value", 0); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverWithConstant.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | const int never = 0; 11 | var single = Enumerable.Empty(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverWithConstant.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | const int never = 0; 11 | var single = Enumerable.Repeat("Hello world!", never); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverWithInt.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Empty(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatNeverWithInt.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Repeat(1337, 0); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnce.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky { 6 | class Sequence { 7 | public static string Return(string value) => value; 8 | } 9 | } 10 | 11 | namespace ConsoleApplication1 12 | { 13 | class Program 14 | { 15 | private void Syntax() 16 | { 17 | var single = Sequence.Return("Hello world!"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnce.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky { 6 | class Sequence { 7 | public static string Return(string value) => value; 8 | } 9 | } 10 | 11 | namespace ConsoleApplication1 12 | { 13 | class Program 14 | { 15 | private void Syntax() 16 | { 17 | var single = Enumerable.Repeat("Hello world!", 1); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceFlipped.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky 6 | { 7 | class Sequence 8 | { 9 | public static string Return(string value) => value; 10 | } 11 | } 12 | 13 | namespace ConsoleApplication1 14 | { 15 | class Program 16 | { 17 | private void Syntax() 18 | { 19 | var single = Sequence.Return("Hello world!"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceFlipped.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky 6 | { 7 | class Sequence 8 | { 9 | public static string Return(string value) => value; 10 | } 11 | } 12 | 13 | namespace ConsoleApplication1 14 | { 15 | class Program 16 | { 17 | private void Syntax() 18 | { 19 | var single = Enumerable.Repeat(count: 1, element: "Hello world!"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceMissingSequenceType.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace ConsoleApplication1 5 | { 6 | class Program 7 | { 8 | private void Syntax() 9 | { 10 | var single = Enumerable.Repeat("Hello world!", 1); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceQualification.expected: -------------------------------------------------------------------------------- 1 | namespace Funcky 2 | { 3 | class Sequence 4 | { 5 | public static System.Collections.Generic.IEnumerable Return(T value) => null!; 6 | } 7 | } 8 | 9 | namespace WithUsingStatic 10 | { 11 | using static Funcky.Sequence; 12 | 13 | public class C 14 | { 15 | public void M() 16 | { 17 | _ = Return(10); 18 | } 19 | } 20 | } 21 | 22 | namespace WithFullyQualifiedName 23 | { 24 | public class C 25 | { 26 | public void M() 27 | { 28 | _ = Funcky.Sequence.Return(10); 29 | } 30 | } 31 | } 32 | 33 | namespace WithUsingAlias 34 | { 35 | using Seq = Funcky.Sequence; 36 | 37 | public class C 38 | { 39 | public void M() 40 | { 41 | _ = Seq.Return(10); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceQualification.input: -------------------------------------------------------------------------------- 1 | namespace Funcky 2 | { 3 | class Sequence 4 | { 5 | public static System.Collections.Generic.IEnumerable Return(T value) => null!; 6 | } 7 | } 8 | 9 | namespace WithUsingStatic 10 | { 11 | using static Funcky.Sequence; 12 | 13 | public class C 14 | { 15 | public void M() 16 | { 17 | _ = System.Linq.Enumerable.Repeat(10, 1); 18 | } 19 | } 20 | } 21 | 22 | namespace WithFullyQualifiedName 23 | { 24 | public class C 25 | { 26 | public void M() 27 | { 28 | _ = System.Linq.Enumerable.Repeat(10, 1); 29 | } 30 | } 31 | } 32 | 33 | namespace WithUsingAlias 34 | { 35 | using Seq = Funcky.Sequence; 36 | 37 | public class C 38 | { 39 | public void M() 40 | { 41 | _ = System.Linq.Enumerable.Repeat(10, 1); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceWithConstant.expected: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky { 6 | class Sequence { 7 | public static string Return(string value) => value; 8 | } 9 | } 10 | 11 | namespace ConsoleApplication1 12 | { 13 | class Program 14 | { 15 | private void Syntax() 16 | { 17 | const int once = 1; 18 | var single = Sequence.Return("Hello world!"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/RepeatOnceWithConstant.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Funcky; 4 | 5 | namespace Funcky { 6 | class Sequence { 7 | public static string Return(string value) => value; 8 | } 9 | } 10 | 11 | namespace ConsoleApplication1 12 | { 13 | class Program 14 | { 15 | private void Syntax() 16 | { 17 | const int once = 1; 18 | var single = Enumerable.Repeat("Hello world!", once); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/TestCode/ValidUseJoinToString.input: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using Funcky.Extensions; 6 | 7 | namespace Funcky.Extensions 8 | { 9 | public static partial class EnumerableExtensions 10 | { 11 | [Pure] 12 | public static string ConcatToString(this IEnumerable source) 13 | => string.Concat(source); 14 | 15 | [Pure] 16 | public static string JoinToString(this IEnumerable source, string separator) 17 | => string.Join(separator, source); } 18 | } 19 | 20 | namespace ConsoleApplication1 21 | { 22 | class Program 23 | { 24 | private void Syntax() 25 | { 26 | var strings = new []{ "A", "B", "B", "A"}; 27 | 28 | var concatenated = strings.ConcatToString(); 29 | var joined = strings.JoinToString("-"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers.Test/VerifierTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Xunit; 3 | 4 | namespace Funcky.Analyzers.Test; 5 | 6 | public sealed class VerifierTests 7 | { 8 | [Fact] 9 | public void CS8632AndCS8669AreNullableWarnings() 10 | { 11 | Assert.Equal(ReportDiagnostic.Error, CSharpVerifierHelper.NullableWarnings["CS8632"]); 12 | Assert.Equal(ReportDiagnostic.Error, CSharpVerifierHelper.NullableWarnings["CS8669"]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/AnalyzerReleases.Unshipped.md: -------------------------------------------------------------------------------- 1 | ; Unshipped analyzer release 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | ### New Rules 5 | Rule ID | Category | Severity | Notes 6 | --------|----------|----------|------- 7 | λ1010 | Funcky | Error | OptionListPatternAnalyzer 8 | λ1101 | Funcky | Warning | FunctionalAssertAnalyzer 9 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/Argument.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Analyzers; 2 | 3 | public static class Argument 4 | { 5 | public const int First = 0; 6 | public const int Second = 1; 7 | } 8 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SemanticModelExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 4 | 5 | internal static class SemanticModelExtensions 6 | { 7 | public static INamedTypeSymbol NullableOfT(this SemanticModel semanticModel, ITypeSymbol itemType) 8 | => semanticModel.Compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(itemType); 9 | } 10 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 4 | 5 | internal static class SymbolEqualityFunctions 6 | { 7 | public static bool SymbolEquals(ISymbol? x, ISymbol? y) 8 | => SymbolEqualityComparer.Default.Equals(x, y); 9 | 10 | public static bool SymbolEqualsIncludeNullability(ISymbol? x, ISymbol? y) 11 | => SymbolEqualityComparer.IncludeNullability.Equals(x, y); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 4 | 5 | internal static class SymbolExtensions 6 | { 7 | public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeClass) 8 | => symbol.GetAttributes().Any(attribute => SymbolEquals(attribute.AttributeClass, attributeClass)); 9 | } 10 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Diagnostics.CodeAnalysis; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | 6 | namespace Funcky.Analyzers.CodeAnalysisExtensions; 7 | 8 | internal static partial class SyntaxNodeExtensions 9 | { 10 | private static ImmutableArray GetAllSymbols(SymbolInfo info) 11 | => info.Symbol == null 12 | ? info.CandidateSymbols 13 | : ImmutableArray.Create(info.Symbol); 14 | 15 | // Copied from Roslyn's source code as this API is not public: 16 | // https://github.com/dotnet/roslyn/blob/232f7afa4966411958759c880de3a1765bdb28a0/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L925 17 | private static bool IsAnyLambda([NotNullWhen(returnValue: true)] this SyntaxNode? node) 18 | => node?.Kind() is SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression; 19 | } 20 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/DiagnosticName.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Analyzers; 2 | 3 | internal static class DiagnosticName 4 | { 5 | public const string Prefix = "λ"; 6 | 7 | public const string Usage = "10"; 8 | 9 | public const string FunctionalAssertUsage = "11"; 10 | } 11 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.Roslyn4.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.1 4 | true 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.4.0 4 | $(DefineConstants);ROSLYN_4_4_0 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Analyzers.CodeAnalysisExtensions; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Operations; 4 | 5 | namespace Funcky.Analyzers.FunctionalAssert; 6 | 7 | public sealed class FunctionalAssertMatching 8 | { 9 | public static bool IsAssertMethodWithAccompanyingEqualOverload( 10 | IInvocationOperation invocation, 11 | AssertMethodHasOverloadWithExpectedValueAttributeType attributeType) 12 | => invocation.TargetMethod.HasAttribute(attributeType.Value) && invocation.Arguments.Length == 1; 13 | } 14 | 15 | public sealed record AssertMethodHasOverloadWithExpectedValueAttributeType(INamedTypeSymbol Value); 16 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Operations; 2 | 3 | namespace Funcky.Analyzers.Functions; 4 | 5 | internal static class AnonymousFunctionMatching 6 | { 7 | /// Matches an anonymous function of the shape (x) => y. 8 | public static Option MatchAnonymousUnaryFunctionWithSingleReturn( 9 | IAnonymousFunctionOperation anonymousFunction) 10 | => MatchAnonymousFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] 11 | && anonymousFunction.Symbol.Parameters is [_] 12 | ? [returnOperation] : []; 13 | 14 | /// Matches an anonymous function of the shape (...) => y. 15 | public static Option MatchAnonymousFunctionWithSingleReturn(IAnonymousFunctionOperation anonymousFunction) 16 | => anonymousFunction.Body.Operations is [IReturnOperation returnOperation] 17 | ? [returnOperation] : []; 18 | } 19 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/LocalizedResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Funcky.Analyzers; 4 | 5 | internal static class LocalizedResourceLoader 6 | { 7 | internal static LocalizableString LoadFromResource(string resourceName) 8 | => new LocalizableResourceString(resourceName, Resources.ResourceManager, typeof(Resources)); 9 | } 10 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.Analyzers/Option/Option.ListPattern.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics; 3 | 4 | namespace Funcky.Analyzers; 5 | 6 | internal readonly partial struct Option 7 | { 8 | private const int NoneLength = 0; 9 | private const int SomeLength = 1; 10 | 11 | [EditorBrowsable(EditorBrowsableState.Never)] 12 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 13 | public int Count 14 | => _hasItem 15 | ? SomeLength 16 | : NoneLength; 17 | 18 | [EditorBrowsable(EditorBrowsableState.Never)] 19 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 20 | public TItem this[int index] 21 | => _hasItem && index is 0 22 | ? _item 23 | : throw new IndexOutOfRangeException("Index was out of range."); 24 | } 25 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers.CodeFixes/Funcky.BuiltinAnalyzers.CodeFixes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | false 5 | preview 6 | enable 7 | true 8 | Funcky.BuiltinAnalyzers 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | ; Shipped analyzer releases 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | ## Release 2.7 5 | ### New Rules 6 | Rule ID | Category | Severity | Notes 7 | --------|----------|----------|------- 8 | λ0001 | Funcky | Error | TryGetValueAnalyzer 9 | λ0002 | Funcky.Deprecation | Warning | OptionNoneMethodGroupAnalyzer 10 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers/AnalyzerReleases.Unshipped.md: -------------------------------------------------------------------------------- 1 | ; Unshipped analyzer release 2 | ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md 3 | 4 | ### New Rules 5 | 6 | Rule ID | Category | Severity | Notes 7 | --------|----------|----------|------- 8 | λ0003 | Funcky | Error | SyntaxSupportOnlyAnalyzer 9 | 10 | ### Removed Rules 11 | Rule ID | Category | Severity | Notes 12 | --------|----------|----------|------- 13 | λ0002 | Funcky.Deprecation | Warning | OptionNoneMethodGroupAnalyzer 14 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers/CompilationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Funcky.BuiltinAnalyzers; 4 | 5 | internal static class CompilationExtensions 6 | { 7 | public static INamedTypeSymbol? GetOptionOfTType(this Compilation compilation) => compilation.GetTypeByMetadataName("Funcky.Monads.Option`1"); 8 | 9 | public static INamedTypeSymbol? GetOptionType(this Compilation compilation) => compilation.GetTypeByMetadataName("Funcky.Monads.Option"); 10 | } 11 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers/Funcky.BuiltinAnalyzers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | false 5 | preview 6 | enable 7 | true 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Funcky.Analyzers/Funcky.BuiltinAnalyzers/WellKnownMemberNames.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.BuiltinAnalyzers; 2 | 3 | internal static class WellKnownMemberNames 4 | { 5 | public static class OptionOfT 6 | { 7 | public const string TryGetValue = "TryGetValue"; 8 | public const string None = "None"; 9 | } 10 | 11 | public static class Option 12 | { 13 | public const string None = "None"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.Async.Test/AsyncSequence/CycleTest.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | using FsCheck.Xunit; 4 | using Funcky.Test.TestUtils; 5 | 6 | namespace Funcky.Async.Test; 7 | 8 | public sealed class CycleTest 9 | { 10 | [Property] 11 | public Property CycleCanProduceArbitraryManyItems(int value, PositiveInt arbitraryElements) 12 | => (AsyncSequence.Cycle(value).Take(arbitraryElements.Get).CountAsync().Result == arbitraryElements.Get) 13 | .ToProperty(); 14 | 15 | [Property] 16 | public Property CycleRepeatsTheElementArbitraryManyTimes(int value, PositiveInt arbitraryElements) 17 | => AsyncSequence 18 | .Cycle(value) 19 | .IsSequenceRepeating(AsyncSequence.Return(value)) 20 | .NTimes(arbitraryElements.Get) 21 | .Result 22 | .ToProperty(); 23 | } 24 | -------------------------------------------------------------------------------- /Funcky.Async.Test/AsyncSequence/ReturnTest.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | using FsCheck.Xunit; 4 | using Funcky.Async.Test.TestUtilities; 5 | 6 | namespace Funcky.Async.Test; 7 | 8 | public sealed class ReturnTest 9 | { 10 | [Property] 11 | public Property ReturnOfASingleItemElevatesThatItemIntoASingleItemedEnumerable(int item) 12 | { 13 | var sequence = AsyncSequence.Return(item); 14 | 15 | return (sequence.SingleOrNoneAsync().Result == item).ToProperty(); 16 | } 17 | 18 | [Fact] 19 | public async Task SequenceReturnCreatesAnEnumerableFromAnArbitraryNumberOfParameters() 20 | { 21 | const string one = "Alpha"; 22 | const string two = "Beta"; 23 | const string three = "Gamma"; 24 | 25 | var sequence = AsyncSequence.Return(one, two, three); 26 | 27 | await AsyncAssert.Collection( 28 | sequence, 29 | element1 => Assert.Equal(one, element1), 30 | element2 => Assert.Equal(two, element2), 31 | element3 => Assert.Equal(three, element3)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/PartitionResultTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; 2 | 3 | public sealed class PartitionResultTest 4 | { 5 | [Fact] 6 | public async Task ReturnsTwoEmptyListsWhenSourceIsEmpty() 7 | { 8 | var (error, ok) = await AsyncEnumerable.Empty>().PartitionAsync(); 9 | Assert.Empty(error); 10 | Assert.Empty(ok); 11 | } 12 | 13 | [Fact] 14 | public async Task PartitionsItemsIntoOkAndError() 15 | { 16 | var values = Sequence.Return(10, 20); 17 | var exceptions = Sequence.Return(new Exception("foo"), new InvalidOperationException("bar")); 18 | var input = values.Select(Result.Ok).ToAsyncEnumerable().Interleave(exceptions.Select(Result.Error).ToAsyncEnumerable()); 19 | 20 | var (error, ok) = await input.PartitionAsync(); 21 | 22 | Assert.Equal(values, ok); 23 | Assert.Equal(exceptions, error); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Async.Test/Extensions/AsyncEnumerableExtensions/TestData.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Async.Test.Extensions.AsyncEnumerableExtensions; 2 | 3 | internal static class TestData 4 | { 5 | public const string FirstItem = "first"; 6 | 7 | public const string LastItem = "last"; 8 | 9 | public const string MiddleItem = "middle"; 10 | 11 | public static readonly IAsyncEnumerable EmptyEnumerable 12 | = AsyncEnumerable.Empty(); 13 | 14 | public static readonly IAsyncEnumerable EnumerableWithOneItem 15 | = AsyncSequence.Return(FirstItem); 16 | 17 | public static readonly IAsyncEnumerable EnumerableTwoItems 18 | = AsyncSequence.Return(42, 1337); 19 | 20 | public static readonly IAsyncEnumerable EnumerableWithMoreThanOneItem 21 | = AsyncSequence.Return(FirstItem, MiddleItem, LastItem); 22 | 23 | public static readonly IAsyncEnumerable OneToFive 24 | = AsyncSequence.Return(1, 2, 3, 4, 5); 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Async.Test/FunckyAsyncPropertyAttribute.cs: -------------------------------------------------------------------------------- 1 | using FsCheck.Xunit; 2 | 3 | namespace Funcky.Async.Test; 4 | 5 | internal sealed class FunckyAsyncPropertyAttribute : PropertyAttribute 6 | { 7 | public FunckyAsyncPropertyAttribute() 8 | { 9 | Arbitrary = [typeof(AsyncGenerator)]; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Funcky.Async.Test/Monads/OptionAsyncExtensions/ToAsyncEnumerableTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Async.Test.Monads.OptionAsyncExtensions; 2 | 3 | public sealed class ToAsyncEnumerableTest 4 | { 5 | [Fact] 6 | public async Task ReturnsEmptyEnumerableWhenOptionIsEmpty() 7 | { 8 | var option = Option.None; 9 | Assert.True(await option.ToAsyncEnumerable().NoneAsync()); 10 | } 11 | 12 | [Fact] 13 | public async Task ReturnsEnumerableWithOneElementWhenOptionHasValue() 14 | { 15 | const int value = 42; 16 | var option = Option.Some(value); 17 | Assert.Equal(value, await option.ToAsyncEnumerable().SingleAsync()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Funcky.Async.Test/TestUtilities/AssertIsCancellationRequestedAsyncSequence.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 2 | 3 | namespace Funcky.Async.Test.TestUtilities; 4 | 5 | internal sealed class AssertIsCancellationRequestedAsyncSequence : IAsyncEnumerable 6 | { 7 | public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) 8 | { 9 | Assert.True(cancellationToken.IsCancellationRequested, "cancellationToken.IsCancellationRequested"); 10 | yield break; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.Async.Test/TestUtilities/CountCreation.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Async.Test.TestUtilities; 2 | 3 | internal sealed class CountCreation 4 | { 5 | public CountCreation() 6 | { 7 | Count += 1; 8 | } 9 | 10 | public static int Count { get; private set; } 11 | } 12 | -------------------------------------------------------------------------------- /Funcky.Async.Test/TestUtilities/FailOnCall.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Async.Test.TestUtilities; 4 | 5 | internal static class FailOnCall 6 | { 7 | internal static TResult Function() 8 | => throw new XunitException("Function was unexpectedly called."); 9 | 10 | internal static TResult Function(T1 p1) 11 | => throw new XunitException("Function was unexpectedly called."); 12 | 13 | internal static TResult Function(T1 p1, T2 p2) 14 | => throw new XunitException("Function was unexpectedly called."); 15 | 16 | internal static TResult Function(T1 p1, T2 p2, T3 p3) 17 | => throw new XunitException("Function was unexpectedly called."); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky.Async.Test/TestUtilities/FailOnEnumerateAsyncSequence.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Async.Test.TestUtilities; 4 | 5 | internal sealed class FailOnEnumerateAsyncSequence : IAsyncEnumerable 6 | { 7 | #pragma warning disable 1998, 162 8 | public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) 9 | { 10 | throw new XunitException("Sequence was unexpectedly enumerated."); 11 | yield break; 12 | } 13 | #pragma warning restore 1998, 162 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async.Test/TestUtilities/OptionProducer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Async.Test.TestUtilities; 2 | 3 | internal sealed class OptionProducer(int retriesNeeded, T result) 4 | where T : notnull 5 | { 6 | public int Called { get; private set; } 7 | 8 | public ValueTask> ProduceAsync() 9 | { 10 | Called += 1; 11 | 12 | return ValueTask.FromResult(Option.FromBoolean(retriesNeeded == (Called - 1), result)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async/AsyncSequence/AsyncSequence.Cycle.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Async.ValueTaskFactory; 2 | 3 | namespace Funcky; 4 | 5 | public static partial class AsyncSequence 6 | { 7 | /// 8 | /// Cycles the same element over and over again as an endless generator. 9 | /// 10 | /// Type of the element to be cycled. 11 | /// The element to be cycled. 12 | /// Returns an infinite IEnumerable cycling through the same elements. 13 | [Pure] 14 | public static IAsyncEnumerable Cycle(TResult element) 15 | => Successors(element, ValueTaskFromResult); 16 | } 17 | -------------------------------------------------------------------------------- /Funcky.Async/AsyncSequence/AsyncSequence.FromNullable.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class AsyncSequence 4 | { 5 | /// An consisting of a single item or zero items. 6 | [Pure] 7 | public static IAsyncEnumerable FromNullable(TResult? element) 8 | where TResult : class 9 | => element is null 10 | ? AsyncEnumerable.Empty() 11 | : Return(element); 12 | 13 | /// 14 | [Pure] 15 | public static IAsyncEnumerable FromNullable(TResult? element) 16 | where TResult : struct 17 | => element.HasValue 18 | ? Return(element.Value) 19 | : AsyncEnumerable.Empty(); 20 | } 21 | -------------------------------------------------------------------------------- /Funcky.Async/AsyncSequence/AsyncSequence.RepeatRange.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class AsyncSequence 4 | { 5 | /// 6 | /// Generates a sequence that contains the same sequence of elements the given number of times. 7 | /// 8 | /// Type of the elements to be repeated. 9 | /// The sequence of elements to be repeated. 10 | /// The number of times to repeat the value in the generated sequence. 11 | /// Returns an infinite IEnumerable cycling through the same elements. 12 | [Pure] 13 | public static IAsyncBuffer RepeatRange(IAsyncEnumerable source, int count) 14 | => CycleBuffer.Create(source, count); 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.Async/AsyncSequence/AsyncSequence.Return.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class AsyncSequence 4 | { 5 | [Pure] 6 | public static IAsyncEnumerable Return(TResult element) 7 | => Return(elements: element); 8 | 9 | [Pure] 10 | public static IAsyncEnumerable Return(params TResult[] elements) 11 | => elements.ToAsyncEnumerable(); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.Async/Extensions/AsyncEnumerableExtensions/AsyncGrouping.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class AsyncEnumerableExtensions 6 | { 7 | internal class AsyncGrouping : IAsyncGrouping 8 | { 9 | private readonly IImmutableList _elements; 10 | 11 | internal AsyncGrouping(TKey key, IImmutableList elements) 12 | { 13 | Key = key; 14 | _elements = elements; 15 | } 16 | 17 | public TKey Key { get; } 18 | 19 | #pragma warning disable CS1998 20 | public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) 21 | #pragma warning restore CS1998 22 | { 23 | foreach (var element in _elements) 24 | { 25 | yield return element; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Funcky.Async/Extensions/AsyncEnumerableExtensions/ConcatToString.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class AsyncEnumerableExtensions 6 | { 7 | /// 8 | /// Concatenates the elements of the given sequence to a single string. 9 | /// 10 | /// Type of the elements in sequence. 11 | /// Concatenated string. 12 | [Pure] 13 | public static async ValueTask ConcatToStringAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) 14 | { 15 | var result = new StringBuilder(); 16 | 17 | await source.AggregateAsync(result, (builder, value) => builder.Append(value), cancellationToken).ConfigureAwait(false); 18 | 19 | return result.ToString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Funcky.Async/Extensions/AsyncEnumerableExtensions/Intersperse.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class AsyncEnumerableExtensions 4 | { 5 | /// Returns a sequence with the items of the source sequence interspersed with the given . 6 | [Pure] 7 | public static IAsyncEnumerable Intersperse(this IAsyncEnumerable source, TSource element) 8 | => source.WithFirst().SelectMany(item => item.IsFirst 9 | ? AsyncSequence.Return(item.Value) 10 | : AsyncSequence.Return(element).Append(item.Value)); 11 | } 12 | -------------------------------------------------------------------------------- /Funcky.Async/Extensions/AsyncEnumerableExtensions/WhereNotNull.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class AsyncEnumerableExtensions 4 | { 5 | [Pure] 6 | public static IAsyncEnumerable WhereNotNull(this IAsyncEnumerable source) 7 | where TSource : class 8 | => source.WhereSelect(Option.FromNullable); 9 | 10 | [Pure] 11 | public static IAsyncEnumerable WhereNotNull(this IAsyncEnumerable source) 12 | where TSource : struct 13 | => source.WhereSelect(Option.FromNullable); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async/Extensions/AsyncEnumerableExtensions/WithIndex.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class AsyncEnumerableExtensions 4 | { 5 | /// 6 | /// Returns a sequence mapping each element together with an Index starting at 0. The returned struct is deconstructible. 7 | /// 8 | /// The source sequence. 9 | /// Type of the elements in sequence. 10 | /// Returns a sequence mapping each element together with an Index starting at 0. 11 | [Pure] 12 | public static IAsyncEnumerable> WithIndex(this IAsyncEnumerable source) 13 | => source.Select((value, index) => new ValueWithIndex(value, index)); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async/IAsyncBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | /// 4 | /// Represents a buffer of an underlying resource and is accordingly. 5 | /// 6 | /// Element type. 7 | public interface IAsyncBuffer : IAsyncEnumerable, IAsyncDisposable; 8 | -------------------------------------------------------------------------------- /Funcky.Async/Monads/Option/OptionExtensions.ToAsyncEnumerable.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public static partial class OptionAsyncExtensions 4 | { 5 | /// 6 | /// Returns an that yields exactly one value when the option 7 | /// has an item and nothing when the option is empty. 8 | /// 9 | public static IAsyncEnumerable ToAsyncEnumerable(this Option option) 10 | where TItem : notnull 11 | => option.Match( 12 | none: AsyncEnumerable.Empty, 13 | some: AsyncSequence.Return); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async/ValueTaskFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Funcky.Async; 4 | 5 | internal static class ValueTaskFactory 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static ValueTask ValueTaskFromResult(TResult result) 9 | => new(result); 10 | 11 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 12 | public static ValueTask ValueTaskFromResult(TResult result, CancellationToken cancellationToken) 13 | => new(result); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Async/package.md: -------------------------------------------------------------------------------- 1 | ## About 2 | Provides async counterparts for [Funcky]'s `IEnumerable` extensions, 3 | `IEnumerable` generators, traversal and retrying. 4 | 5 | ## Main Types 6 | * `Funcky.AsyncSequence`—Generate asynchronous sequences. 7 | * `Funcky.Extensions.AsyncEnumerableExtensions`—Extensions for `IAsyncEnumerable`. 8 | * `Funcky.AsyncFunctional`—Async Retrying. 9 | 10 | ## Feedback & Contributing 11 | This package is released as open source under the MIT or Apache-2.0 license at your choice. 12 | Bug reports and contributions are welcome at the [GitHub repository](https://github.com/polyadic/funcky). 13 | 14 | [Funcky]: https://www.nuget.org/packages/Funcky 15 | -------------------------------------------------------------------------------- /Funcky.FsCheck/Funcky.FsCheck.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0;net6.0 4 | false 5 | Funcky.FsCheck 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Funcky.SourceGenerator.Test; 4 | 5 | public static class ModuleInitializer 6 | { 7 | [ModuleInitializer] 8 | public static void Init() 9 | { 10 | VerifySourceGenerators.Enable(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.CopiesStringSyntaxAttributeFromOriginalDefinition.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate, [global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("foo")] string format) => global::Funcky.Extensions.Target.TryParse(candidate, format, out var result) ? result : default(Funcky.Monads.Option); 11 | } 12 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.CopiesStringSyntaxAttributeFromOriginalDefinition.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate, string? hasDefault = null) => global::Funcky.Extensions.Target.TryParse(candidate, out var result, hasDefault) ? result : default(Funcky.Monads.Option); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMethodWithDefaultValuedArgument.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMultipleMethodsInASingleClass.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate) => global::Funcky.Extensions.Target.TryParse(candidate, out var result) ? result : default(Funcky.Monads.Option); 11 | [global::System.Diagnostics.Contracts.Pure] 12 | public static Funcky.Monads.Option ParseExactTargetOrNone(this string candidate) => global::Funcky.Extensions.Target.TryParseExact(candidate, out var result) ? result : default(Funcky.Monads.Option); 13 | } 14 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateMultipleMethodsInASingleClass.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithMultipleArgumentsToForward.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate, bool caseSensitive, global::System.IFormatProvider provider) => global::Funcky.Extensions.Target.TryParse(candidate, caseSensitive, provider, out var result) ? result : default(Funcky.Monads.Option); 11 | } 12 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithMultipleArgumentsToForward.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithTheSingleArgumentCandidate.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate) => global::Funcky.Extensions.Target.TryParse(candidate, out var result) ? result : default(Funcky.Monads.Option); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GenerateSingleMethodWithTheSingleArgumentCandidate.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GeneratesAllOverloadsForAGivenMethod.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate) => global::Funcky.Extensions.Target.TryParse(candidate, out var result) ? result : default(Funcky.Monads.Option); 11 | [global::System.Diagnostics.Contracts.Pure] 12 | public static Funcky.Monads.Option ParseTargetOrNone(this string candidate, bool caseSensitive) => global::Funcky.Extensions.Target.TryParse(candidate, caseSensitive, out var result) ? result : default(Funcky.Monads.Option); 13 | } 14 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GeneratesAllOverloadsForAGivenMethod.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GeneratesMethodWhenTargetIsNotNullableAnnotated.00.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: .g.cs 2 | // 3 | #nullable enable 4 | 5 | namespace Funcky.Extensions 6 | { 7 | public static partial class ParseExtensions 8 | { 9 | [global::System.Diagnostics.Contracts.Pure] 10 | public static Funcky.Monads.Option ParseTargetOrNone(this 11 | #nullable disable 12 | string 13 | #nullable enable 14 | candidate) => global::Funcky.Extensions.Target.TryParse(candidate, out var result) ? result : default(Funcky.Monads.Option); 15 | } 16 | } -------------------------------------------------------------------------------- /Funcky.SourceGenerator.Test/OrNoneGeneratorSnapshotTests.GeneratesMethodWhenTargetIsNotNullableAnnotated.01.verified.cs: -------------------------------------------------------------------------------- 1 | //HintName: OrNoneFromTryPatternAttribute.g.cs 2 | namespace Funcky.Internal 3 | { 4 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 5 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 6 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 7 | { 8 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 9 | => (Type, Method) = (type, method); 10 | 11 | public global::System.Type Type { get; } 12 | 13 | public string Method { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator/CodeSnippets.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.SourceGenerator; 2 | 3 | internal static class CodeSnippets 4 | { 5 | public const string OrNoneFromTryPatternAttribute = 6 | """ 7 | namespace Funcky.Internal 8 | { 9 | [global::System.Diagnostics.Conditional("COMPILE_TIME_ONLY")] 10 | [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true)] 11 | internal class OrNoneFromTryPatternAttribute : global::System.Attribute 12 | { 13 | public OrNoneFromTryPatternAttribute(global::System.Type type, string method) 14 | => (Type, Method) = (type, method); 15 | 16 | public global::System.Type Type { get; } 17 | 18 | public string Method { get; } 19 | } 20 | } 21 | """; 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator/Funcky.SourceGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Funcky.SourceGenerator 4 | Funcky.SourceGenerator 5 | netstandard2.0 6 | enable 7 | enable 8 | preview 9 | false 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Funcky.SourceGenerator/MethodPartial.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | 5 | namespace Funcky.SourceGenerator; 6 | 7 | internal record MethodPartial(string NamespaceName, string ClassName, ImmutableArray Methods, SyntaxTree SourceTree); 8 | -------------------------------------------------------------------------------- /Funcky.Test.Internal/Data/Person.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Internal.Data; 2 | 3 | public sealed class Person(int age) : IComparable, IEquatable 4 | { 5 | public int Age { get; } = age; 6 | 7 | public int CompareTo(Person? other) 8 | => other != null 9 | ? Age.CompareTo(other.Age) 10 | : -1; 11 | 12 | public static Person Create(int age) 13 | => new(age); 14 | 15 | public static Person? Create(int? age) 16 | => age.HasValue 17 | ? new Person(age.Value) 18 | : null; 19 | 20 | public bool Equals(Person? other) 21 | => other?.Age == Age; 22 | 23 | public override bool Equals(object? other) 24 | => Equals(other as Person); 25 | 26 | public override int GetHashCode() 27 | => Age.GetHashCode(); 28 | } 29 | -------------------------------------------------------------------------------- /Funcky.Test.Internal/DescendingIntComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Internal; 2 | 3 | public sealed class DescendingIntComparer : IComparer 4 | { 5 | private DescendingIntComparer() 6 | { 7 | } 8 | 9 | public static DescendingIntComparer Create() 10 | => new(); 11 | 12 | public int Compare(int x, int y) 13 | => y - x; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Test.Internal/Funcky.Test.Internal.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | preview 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/DisposableCulture.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace Funcky.Test.Extensions; 4 | 5 | internal class DisposableCulture : IDisposable 6 | { 7 | private readonly CultureInfo _lastCulture; 8 | private bool _disposedValue; 9 | 10 | public DisposableCulture(string culture) 11 | { 12 | _lastCulture = CultureInfo.CurrentCulture; 13 | CultureInfo.CurrentCulture = new CultureInfo(culture); 14 | } 15 | 16 | public void Dispose() 17 | { 18 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 19 | Dispose(disposing: true); 20 | GC.SuppressFinalize(this); 21 | } 22 | 23 | protected virtual void Dispose(bool disposing) 24 | { 25 | if (!_disposedValue) 26 | { 27 | if (disposing) 28 | { 29 | CultureInfo.CurrentCulture = _lastCulture; 30 | } 31 | 32 | _disposedValue = true; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/EnumerableExtensions/PartitionResultTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Extensions.EnumerableExtensions; 2 | 3 | public sealed class PartitionResultTest 4 | { 5 | [Fact] 6 | public void ReturnsTwoEmptyEnumerablesWhenSourceIsEmpty() 7 | { 8 | var (error, ok) = Enumerable.Empty>().Partition(); 9 | Assert.Empty(error); 10 | Assert.Empty(ok); 11 | } 12 | 13 | [Fact] 14 | public void PartitionsItemsIntoOkAndError() 15 | { 16 | var values = Sequence.Return(10, 20); 17 | var exceptions = Sequence.Return(new Exception("foo"), new InvalidOperationException("bar")); 18 | var input = values.Select(Result.Ok).Interleave(exceptions.Select(Result.Error)); 19 | 20 | var (error, ok) = input.Partition(); 21 | 22 | Assert.Equal(values, ok); 23 | Assert.Equal(exceptions, error); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/HttpHeadersNonValidatedExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | #if HTTP_HEADERS_NON_VALIDATED 2 | using System.Net.Http.Headers; 3 | using FsCheck; 4 | using FsCheck.Fluent; 5 | using FsCheck.Xunit; 6 | 7 | namespace Funcky.Test.Extensions; 8 | 9 | public sealed class HttpHeadersNonValidatedExtensionsTest 10 | { 11 | [Property] 12 | public Property InAnEmptyHttpHeadersNonValidatedGetValueOrNoneIsAlwaysNone(string header) 13 | { 14 | var nonValidated = default(HttpHeadersNonValidated); 15 | 16 | return nonValidated 17 | .GetValuesOrNone(header) 18 | .Match(none: true, some: False) 19 | .ToProperty(); 20 | } 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/ParseExtensions/ParseExtensionsTest.CacheControlHeaderValue.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Extensions.ParseExtensions; 2 | 3 | public sealed partial class ParseExtensionsTest 4 | { 5 | /// Backport of https://github.com/dotnet/runtime/pull/74863. 6 | [Fact] 7 | public void ReturnsEmptyValueForEmptyString() 8 | { 9 | var value = FunctionalAssert.Some(string.Empty.ParseCacheControlHeaderValueOrNone()); 10 | Assert.Equal(string.Empty, value.ToString()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/ParseExtensions/ParseExtensionsTest.Char.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Extensions.ParseExtensions; 2 | 3 | public sealed partial class ParseExtensionsTest 4 | { 5 | [Theory] 6 | [InlineData('a', "a")] 7 | [InlineData('x', "x")] 8 | [InlineData('1', "1")] 9 | [InlineData('δ', "δ")] 10 | public void ParseCharOrNoneReturnsTheOnlyCharacterInAString(char expected, string input) 11 | { 12 | FunctionalAssert.Some(expected, input.ParseCharOrNone()); 13 | } 14 | 15 | [Theory] 16 | [InlineData(null)] 17 | [InlineData("longer")] 18 | [InlineData("")] 19 | [InlineData("\ud83d\udd25")] // single fire emoji (outside BMP) 20 | public void ParseCharOrNoneReturnsNoneIfItCanParseItToACharacter(string? input) 21 | { 22 | FunctionalAssert.None(input.ParseCharOrNone()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/QueryableExtensions/FirstOrNoneTest.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Test.TestUtils; 2 | 3 | namespace Funcky.Test.Extensions.QueryableExtensions; 4 | 5 | public sealed class FirstOrNoneTest 6 | { 7 | [Fact] 8 | public void FirstOrNoneIsEvaluatedUsingExpressions() 9 | => _ = Enumerable.Empty() 10 | .AsQueryable() 11 | .PreventAccidentalUseAsEnumerable() 12 | .FirstOrNone(); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/QueryableExtensions/LastOrNoneTest.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Test.TestUtils; 2 | 3 | namespace Funcky.Test.Extensions.QueryableExtensions; 4 | 5 | public sealed class LastOrNoneTest 6 | { 7 | [Fact] 8 | public void LastOrNoneIsEvaluatedUsingExpressions() 9 | => _ = Enumerable.Empty() 10 | .AsQueryable() 11 | .PreventAccidentalUseAsEnumerable() 12 | .LastOrNone(); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/QueryableExtensions/SingleOrNoneTest.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Test.TestUtils; 2 | 3 | namespace Funcky.Test.Extensions.QueryableExtensions; 4 | 5 | public sealed class SingleOrNoneTest 6 | { 7 | [Fact] 8 | public void SingleOrNoneIsEvaluatedUsingExpressions() 9 | => _ = Enumerable.Empty() 10 | .AsQueryable() 11 | .PreventAccidentalUseAsEnumerable() 12 | .SingleOrNone(); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/SideEffect.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Extensions; 2 | 3 | internal sealed class SideEffect 4 | { 5 | private string _string = string.Empty; 6 | 7 | public void Store(string s) 8 | => _string = s; 9 | 10 | public string Retrieve() 11 | => _string; 12 | } 13 | -------------------------------------------------------------------------------- /Funcky.Test/Extensions/SkipOnMonoFact.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Extensions; 2 | 3 | internal sealed class SkipOnMonoFact : FactAttribute 4 | { 5 | public SkipOnMonoFact() 6 | { 7 | if (IsRunningOnMono()) 8 | { 9 | Skip = "This test does not work on Mono"; 10 | } 11 | } 12 | 13 | private static bool IsRunningOnMono() => Type.GetType("Mono.Runtime") is not null; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Test/FunctionalClass/ActionToUnitTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.FunctionalClass; 2 | 3 | public sealed class ActionToUnitTest 4 | { 5 | [Fact] 6 | public void OverloadResolutionWorks() 7 | { 8 | _ = ActionToUnit(ActionWithNoParameters); 9 | _ = ActionToUnit(ActionWithOneParameter); 10 | _ = ActionToUnit(ActionWithTwoParameters); 11 | } 12 | 13 | private static void ActionWithNoParameters() 14 | { 15 | } 16 | 17 | private static void ActionWithOneParameter(int foo) 18 | { 19 | } 20 | 21 | private static void ActionWithTwoParameters(int foo, int bar) 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Funcky.Test/FunctionalClass/ApplyTest.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Discard; 2 | 3 | namespace Funcky.Test.FunctionalClass; 4 | 5 | public class ApplyTest 6 | { 7 | [Fact] 8 | public void CanApplyParametersInAnyOrder() 9 | { 10 | var func = Linear0; 11 | var f1 = Fn(Linear0).Apply(__, __, 10); 12 | var f2 = func.Apply(2, __, 7); 13 | var f3 = Apply(Fn(Linear0), 42, __, __); 14 | Assert.Equal(Linear0(10, 2, 10), f1(10, 2)); 15 | Assert.Equal(Linear0(2, 10, 7), f2(10)); 16 | Assert.Equal(Linear0(42, 10, 2), f3(10, 2)); 17 | } 18 | 19 | private static int Linear0(int a, int b, int c) 20 | => a + (b * c); 21 | } 22 | -------------------------------------------------------------------------------- /Funcky.Test/FunctionalClass/FnTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.FunctionalClass; 2 | 3 | public sealed class FnTest 4 | { 5 | [Fact] 6 | public void FnHelpsToInferAMethodGroupsNaturalType() 7 | { 8 | var powCurried = Curry(Fn(Math.Pow)); 9 | var powUncurried = Uncurry(Fn(CurriedPow)); 10 | var flipped = Flip(Fn(Math.Pow)); 11 | } 12 | 13 | private static Func CurriedPow(double x) => y => Math.Pow(x, y); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Test/FunctionalClass/IdentityTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.FunctionalClass; 2 | 3 | public sealed class IdentityTest 4 | { 5 | [Fact] 6 | public void IdentityReturnsPassedValue() 7 | { 8 | const string value = "foo"; 9 | Assert.Equal(value, Identity(value)); 10 | } 11 | 12 | [Fact] 13 | public void IdentityCanBeUsedInSelect() 14 | { 15 | var list = new[] { "foo", "bar", "baz" }; 16 | var mappedList = list.Select(Identity).ToList(); 17 | Assert.Equal(list, mappedList); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Funcky.Test/FunctionalClass/NoOperationTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.FunctionalClass; 2 | 3 | public sealed class NoOperationTest 4 | { 5 | [Fact] 6 | public void GivenTheNoOperationFunctionWeCanApplyItToMatch() 7 | { 8 | var none = Option.None; 9 | 10 | var sideEffect = 0; 11 | none.Switch(none: NoOperation, some: i => sideEffect = i); 12 | 13 | Assert.Equal(0, sideEffect); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Funcky.Test/Monads/EitherTest.ToOption.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Monads; 2 | 3 | public sealed partial class EitherTest 4 | { 5 | public sealed class LeftOrNone 6 | { 7 | [Fact] 8 | public void ReturnsValueWhenEitherIsLeft() 9 | { 10 | FunctionalAssert.Some("test", Either.Left("test").LeftOrNone()); 11 | } 12 | 13 | [Fact] 14 | public void ReturnsNoneWhenEitherIsRight() 15 | { 16 | FunctionalAssert.None(Either.Return(10).LeftOrNone()); 17 | } 18 | } 19 | 20 | public sealed class RightOrNone 21 | { 22 | [Fact] 23 | public void ReturnsValueWhenEitherIsRight() 24 | { 25 | FunctionalAssert.Some("test", Either.Return("test").RightOrNone()); 26 | } 27 | 28 | [Fact] 29 | public void ReturnsNoneWhenEitherIsLeft() 30 | { 31 | FunctionalAssert.None(Either.Left(10).RightOrNone()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Funcky.Test/Monads/LazyTest.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.Monads; 2 | 3 | public sealed partial class LazyTest; 4 | -------------------------------------------------------------------------------- /Funcky.Test/Monads/OptionTest.Convenience.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Test.Monads; 4 | 5 | public sealed partial class OptionTest 6 | { 7 | [Fact] 8 | public void InspectNoneDoesNothingWhenOptionIsNone() 9 | { 10 | var option = Option.Some(10); 11 | option.InspectNone(() => throw new XunitException("Side effect was unexpectedly called")); 12 | } 13 | 14 | [Fact] 15 | public void InspectNoneCallsSideEffectWhenOptionIsNone() 16 | { 17 | var option = Option.None; 18 | 19 | var sideEffect = false; 20 | option.InspectNone(() => sideEffect = true); 21 | Assert.True(sideEffect); 22 | } 23 | 24 | [Theory] 25 | [MemberData(nameof(SomeAndNone))] 26 | public void InspectLeftReturnsOriginalValue(Option option) 27 | { 28 | Assert.Equal(option, option.InspectNone(NoOperation)); 29 | } 30 | 31 | public static TheoryData> SomeAndNone() 32 | => new() 33 | { 34 | Option.None, 35 | Option.Some(42), 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /Funcky.Test/Monads/ResultTest.Convenience.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Test.Monads; 4 | 5 | public sealed partial class ResultTest 6 | { 7 | [Fact] 8 | public void InspectErrorDoesNothingWhenResultIsOk() 9 | { 10 | var result = Result.Ok("foo"); 11 | result.InspectError(_ => throw new XunitException("Side effect was unexpectedly called")); 12 | } 13 | 14 | [Fact] 15 | public void InspectErrorCallsSideEffectWhenResultIsError() 16 | { 17 | var exception = new Exception("Bam!"); 18 | var result = Result.Error(exception); 19 | 20 | var sideEffect = Option.None; 21 | result.InspectError(v => sideEffect = v); 22 | FunctionalAssert.Some(exception, sideEffect); 23 | } 24 | 25 | [Theory] 26 | [MemberData(nameof(OkAndError))] 27 | public void InspectErrorReturnsOriginalValue(Result result) 28 | { 29 | Assert.Equal(result, result.InspectError(NoOperation)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Funcky.Test/Sequence/ConcatTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using FsCheck; 3 | using FsCheck.Fluent; 4 | using FsCheck.Xunit; 5 | 6 | namespace Funcky.Test; 7 | 8 | public sealed class ConcatTest 9 | { 10 | [Fact] 11 | public void ConcatenatedSequenceIsEmptyWhenNoSourcesAreProvided() 12 | { 13 | Assert.Empty(Sequence.Concat()); 14 | } 15 | 16 | [Fact] 17 | public void ConcatenatedSequenceIsEmptyWhenAllSourcesAreEmpty() 18 | { 19 | Assert.Empty(Sequence.Concat(ImmutableArray.Create(Enumerable.Empty(), Enumerable.Empty(), Enumerable.Empty()))); 20 | } 21 | 22 | [Property] 23 | public Property ConcatenatedSequenceContainsElementsFromAllSourcesInOrder(int[][] sources) 24 | { 25 | var expected = sources.Aggregate(ImmutableArray.Empty, (l, s) => l.AddRange(s)); 26 | var actual = Sequence.Concat(sources); 27 | return expected.SequenceEqual(actual).ToProperty(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Funcky.Test/Sequence/CycleTest.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | using FsCheck.Xunit; 4 | using Funcky.Test.TestUtils; 5 | 6 | namespace Funcky.Test; 7 | 8 | public sealed class CycleTest 9 | { 10 | [Property] 11 | public Property CycleCanProduceArbitraryManyItems(int value, PositiveInt arbitraryElements) 12 | => (Sequence.Cycle(value).Take(arbitraryElements.Get).Count() == arbitraryElements.Get) 13 | .ToProperty(); 14 | 15 | [Property] 16 | public Property CycleRepeatsTheElementArbitraryManyTimes(int value, PositiveInt arbitraryElements) 17 | => Sequence 18 | .Cycle(value) 19 | .IsSequenceRepeating(Sequence.Return(value)) 20 | .NTimes(arbitraryElements.Get) 21 | .ToProperty(); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.Test/Sequence/ReturnTest.cs: -------------------------------------------------------------------------------- 1 | using FsCheck; 2 | using FsCheck.Fluent; 3 | using FsCheck.Xunit; 4 | 5 | namespace Funcky.Test; 6 | 7 | public sealed class ReturnTest 8 | { 9 | [Property] 10 | public Property ReturnOfASingleItemElevatesThatItemIntoASingleItemedEnumerable(int item) 11 | { 12 | var sequence = Sequence.Return(item); 13 | 14 | return (sequence.SingleOrNone() == item).ToProperty(); 15 | } 16 | 17 | [Fact] 18 | public void SequenceReturnCreatesAnEnumerableFromAnArbitraryNumberOfParameters() 19 | { 20 | const string one = "Alpha"; 21 | const string two = "Beta"; 22 | const string three = "Gamma"; 23 | 24 | var sequence = Sequence.Return(one, two, three); 25 | 26 | Assert.Collection( 27 | sequence, 28 | element1 => Assert.Equal(one, element1), 29 | element2 => Assert.Equal(two, element2), 30 | element3 => Assert.Equal(three, element3)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/CountCreation.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed class CountCreation 4 | { 5 | public CountCreation() 6 | { 7 | Count += 1; 8 | } 9 | 10 | public static int Count { get; private set; } 11 | } 12 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal static class EnumerableExtensions 4 | { 5 | /// Prevents LINQ from optimizing by hiding the underlying source enumerable. 6 | internal static IEnumerable PreventLinqOptimizations(this IEnumerable source) 7 | { 8 | foreach (var element in source) 9 | { 10 | yield return element; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/EverythingIsEqual.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal class EverythingIsEqual : IEqualityComparer 4 | { 5 | public bool Equals(T? x, T? y) => true; 6 | 7 | public int GetHashCode(T obj) => 0; 8 | } 9 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnCall.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Test.TestUtils; 4 | 5 | internal static class FailOnCall 6 | { 7 | internal static TResult Function() 8 | => throw new XunitException("Function was unexpectedly called."); 9 | 10 | internal static TResult Function(T1 p1) 11 | => throw new XunitException("Function was unexpectedly called."); 12 | 13 | internal static TResult Function(T1 p1, T2 p2) 14 | => throw new XunitException("Function was unexpectedly called."); 15 | 16 | internal static TResult Function(T1 p1, T2 p2, T3 p3) 17 | => throw new XunitException("Function was unexpectedly called."); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerateCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky.Test.TestUtils; 5 | 6 | internal record FailOnEnumerateCollection(int Count) : ICollection 7 | { 8 | public bool IsReadOnly => true; 9 | 10 | public IEnumerator GetEnumerator() => throw new XunitException("Should not be enumerated"); 11 | 12 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 13 | 14 | public void Add(T item) => throw new NotSupportedException(); 15 | 16 | public void Clear() => throw new NotSupportedException(); 17 | 18 | public bool Contains(T item) => throw new NotSupportedException(); 19 | 20 | public void CopyTo(T[] array, int arrayIndex) => throw new NotSupportedException(); 21 | 22 | public bool Remove(T item) => throw new NotSupportedException(); 23 | } 24 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerateCollectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky.Test.TestUtils; 5 | 6 | internal class FailOnEnumerateCollectionWrapper(ICollection collection) : ICollection 7 | { 8 | public bool IsReadOnly => true; 9 | 10 | public int Count => collection.Count; 11 | 12 | public IEnumerator GetEnumerator() => throw new XunitException("Should not be enumerated"); 13 | 14 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 15 | 16 | public void Add(T item) => throw new NotSupportedException(); 17 | 18 | public void Clear() => throw new NotSupportedException(); 19 | 20 | public bool Contains(T item) => collection.Contains(item); 21 | 22 | public void CopyTo(T[] array, int arrayIndex) => collection.CopyTo(array, arrayIndex); 23 | 24 | public bool Remove(T item) => throw new NotSupportedException(); 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerateList.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed record FailOnEnumerateList(int Count) : FailOnEnumerateCollection(Count), IList 4 | { 5 | public T this[int index] 6 | { 7 | get => throw new NotSupportedException(); 8 | set => throw new NotSupportedException(); 9 | } 10 | 11 | public int IndexOf(T item) => throw new NotSupportedException(); 12 | 13 | public void Insert(int index, T item) => throw new NotSupportedException(); 14 | 15 | public void RemoveAt(int index) => throw new NotSupportedException(); 16 | } 17 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerateListWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed class FailOnEnumerateListWrapper(IList list) : FailOnEnumerateCollectionWrapper(list), IList 4 | { 5 | public T this[int index] 6 | { 7 | get => list[index]; 8 | set => throw new NotSupportedException(); 9 | } 10 | 11 | public int IndexOf(T item) => list.IndexOf(item); 12 | 13 | public void Insert(int index, T item) => throw new NotSupportedException(); 14 | 15 | public void RemoveAt(int index) => throw new NotSupportedException(); 16 | } 17 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerateReadOnlyCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky.Test.TestUtils; 5 | 6 | internal record FailOnEnumerateReadOnlyCollection(int Count) : IReadOnlyCollection 7 | { 8 | public IEnumerator GetEnumerator() => throw new XunitException("Should not be enumerated"); 9 | 10 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 11 | } 12 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/FailOnEnumerationSequence.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky.Test.TestUtils; 5 | 6 | internal sealed class FailOnEnumerationSequence : IEnumerable 7 | { 8 | public IEnumerator GetEnumerator() 9 | { 10 | throw new XunitException("Sequence was unexpectedly enumerated."); 11 | 12 | #pragma warning disable CS0162 // Unreachable code detected 13 | yield break; // this forces the method to emit the statemachine even though it is not reachable! 14 | #pragma warning restore CS0162 // Unreachable code detected 15 | } 16 | 17 | IEnumerator IEnumerable.GetEnumerator() 18 | => GetEnumerator(); 19 | } 20 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/IMyInterface.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal interface IMyInterface; 4 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/MyClass.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed class MyClass : IMyInterface; 4 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/OptionProducer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed class OptionProducer(int retriesNeeded, T result) 4 | where T : notnull 5 | { 6 | public int Called { get; private set; } 7 | 8 | public Option Produce() 9 | { 10 | Called += 1; 11 | 12 | return Option.FromBoolean(retriesNeeded == (Called - 1), result); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/RepeatingSequence.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | public static class RepeatingSequence 4 | { 5 | public static RepeatingSequenceHelper IsSequenceRepeating(this IEnumerable sequence, IEnumerable pattern) 6 | => new(sequence, pattern); 7 | 8 | public class RepeatingSequenceHelper(IEnumerable sequence, IEnumerable pattern) 9 | { 10 | public bool NTimes(int count) 11 | => Enumerable 12 | .Range(0, count) 13 | .Aggregate(true, AggregateEquality); 14 | 15 | public bool AggregateEquality(bool b, int i) 16 | => b && sequence 17 | .Skip(i * pattern.Count()) 18 | .Zip(pattern, (l, r) => l == r) 19 | .All(Identity); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Funcky.Test/TestUtils/SideEffect.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Test.TestUtils; 2 | 3 | internal sealed class SideEffect 4 | { 5 | public bool IsDone { get; private set; } 6 | 7 | public void Do() => IsDone = true; 8 | } 9 | -------------------------------------------------------------------------------- /Funcky.TrimmingTest/Funcky.TrimmingTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Funcky.TrimmingTest/Program.cs: -------------------------------------------------------------------------------- 1 | Console.WriteLine("Hello, World!"); 2 | -------------------------------------------------------------------------------- /Funcky.Xunit.Test/Extensions/ToTheoryDataExtensionTest.cs: -------------------------------------------------------------------------------- 1 | #if !XUNIT_V3 2 | namespace Funcky.Xunit.Test.Extensions; 3 | 4 | public sealed class ToTheoryDataExtensionTest 5 | { 6 | private const string StringValue = "Hello world!"; 7 | private const int IntValue = 1337; 8 | 9 | [Theory] 10 | [MemberData(nameof(TheoryFromEnumerable))] 11 | public void GivenAnEnumerableEachElementCreatesATest(int index) => 12 | Assert.InRange(index, 0, 9); 13 | 14 | public static TheoryData TheoryFromEnumerable() 15 | => Enumerable.Range(0, 10).ToTheoryData(); 16 | 17 | [Theory] 18 | [MemberData(nameof(TheoryFromRepeat))] 19 | public void GivenAnEnumerableOfTupleWeGetAllValues(int id, string value) 20 | { 21 | Assert.Equal(IntValue, id); 22 | Assert.Equal(StringValue, value); 23 | } 24 | 25 | public static TheoryData TheoryFromRepeat() 26 | => Sequence.Return(Tuple.Create(IntValue, StringValue)).ToTheoryData(); 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Funcky.Xunit.Test/Funcky.Xunit.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | preview 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Funcky.Xunit.Test/FunctionalAssertClass/ErrorTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Xunit.Test.FunctionalAssertClass; 4 | 5 | public sealed class ErrorTest 6 | { 7 | [Fact] 8 | public void DoesNotThrowForError() 9 | { 10 | FunctionalAssert.Error(Result.Error(new ArgumentException("foo"))); 11 | } 12 | 13 | [Fact] 14 | public void ThrowsForOk() 15 | { 16 | const string expectedMessage = 17 | """ 18 | FunctionalAssert.Error() Failure: Values differ 19 | Expected: Error(...) 20 | Actual: Ok(42) 21 | """; 22 | var exception = Assert.ThrowsAny(() => FunctionalAssert.Error(Result.Ok(42))); 23 | Assert.Equal(expectedMessage.ReplaceLineEndings(Environment.NewLine), exception.Message); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Xunit.Test/FunctionalAssertClass/NoneTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Funcky.Xunit.Test.FunctionalAssertClass; 4 | 5 | public sealed class NoneTest 6 | { 7 | [Fact] 8 | public void DoesNotThrowForNone() 9 | { 10 | FunctionalAssert.None(Option.None); 11 | } 12 | 13 | [Fact] 14 | public void ThrowsForSome() 15 | { 16 | const string expectedMessage = 17 | """ 18 | FunctionalAssert.None() Failure: Values differ 19 | Expected: None 20 | Actual: Some("something") 21 | """; 22 | var exception = Assert.ThrowsAny(() => FunctionalAssert.None(Option.Some("something"))); 23 | Assert.Equal(expectedMessage.ReplaceLineEndings(Environment.NewLine), exception.Message); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3.Test/Serializers/UnitSerializerTest.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Xunit.Serializers; 2 | 3 | namespace Funcky.Xunit.Test; 4 | 5 | public sealed class UnitSerializerTest 6 | { 7 | private static readonly UnitSerializer Serializer = new(); 8 | 9 | [Fact] 10 | public void RoundtripsUnit() 11 | { 12 | var serialized = Serializer.Serialize(Unit.Value); 13 | var deserialized = Serializer.Deserialize(typeof(Unit), serialized); 14 | Assert.Equal(Unit.Value, deserialized); 15 | } 16 | 17 | [Fact] 18 | public void UnitIsSerializable() 19 | { 20 | Assert.True(Serializer.IsSerializable(typeof(Unit), Unit.Value, out _)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/PublicAPI.Shipped.txt: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/RegisterEitherSerializerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Funcky.Xunit.Serializers; 3 | using Xunit.Sdk; 4 | 5 | namespace Funcky.Xunit; 6 | 7 | [AttributeUsage(AttributeTargets.Assembly)] 8 | [EditorBrowsable(EditorBrowsableState.Advanced)] 9 | public sealed class RegisterEitherSerializerAttribute : Attribute, IRegisterXunitSerializerAttribute 10 | { 11 | Type IRegisterXunitSerializerAttribute.SerializerType => typeof(EitherSerializer); 12 | 13 | Type[] IRegisterXunitSerializerAttribute.SupportedTypesForSerialization => [typeof(IEither)]; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/RegisterOptionSerializerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Funcky.Xunit.Serializers; 3 | using Xunit.Sdk; 4 | 5 | namespace Funcky.Xunit; 6 | 7 | [AttributeUsage(AttributeTargets.Assembly)] 8 | [EditorBrowsable(EditorBrowsableState.Advanced)] 9 | public sealed class RegisterOptionSerializerAttribute : Attribute, IRegisterXunitSerializerAttribute 10 | { 11 | Type IRegisterXunitSerializerAttribute.SerializerType => typeof(OptionSerializer); 12 | 13 | Type[] IRegisterXunitSerializerAttribute.SupportedTypesForSerialization => [typeof(IOption)]; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/RegisterUnitSerializerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Funcky.Xunit.Serializers; 3 | using Xunit.Sdk; 4 | 5 | namespace Funcky.Xunit; 6 | 7 | [AttributeUsage(AttributeTargets.Assembly)] 8 | [EditorBrowsable(EditorBrowsableState.Advanced)] 9 | public sealed class RegisterUnitSerializerAttribute : Attribute, IRegisterXunitSerializerAttribute 10 | { 11 | Type IRegisterXunitSerializerAttribute.SerializerType => typeof(UnitSerializer); 12 | 13 | Type[] IRegisterXunitSerializerAttribute.SupportedTypesForSerialization => [typeof(Unit)]; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/Serializers/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Xunit.Serializers; 2 | 3 | internal static class StringExtensions 4 | { 5 | public static Option StripPrefix(this string s, string prefix) 6 | => s.StartsWith(prefix) 7 | ? s[prefix.Length..] 8 | : Option.None; 9 | } 10 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/Serializers/UnitSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky.Xunit.Serializers; 5 | 6 | internal sealed class UnitSerializer : IXunitSerializer 7 | { 8 | public object Deserialize(Type type, string serializedValue) => Unit.Value; 9 | 10 | public bool IsSerializable(Type type, object? value, [NotNullWhen(false)] out string? failureReason) 11 | { 12 | failureReason = string.Empty; 13 | return type == typeof(Unit); 14 | } 15 | 16 | public string Serialize(object value) => string.Empty; 17 | } 18 | -------------------------------------------------------------------------------- /Funcky.Xunit.v3/build/Funcky.Xunit.v3.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Funcky.Xunit/CodeAnalysis/AssertMethodHasOverloadWithExpectedValueAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.CodeAnalysis; 2 | 3 | /// Marks FunctionalAssert methods that have an accompanying overload that 4 | /// takes the expected value. 5 | [AttributeUsage(AttributeTargets.Method)] 6 | internal sealed class AssertMethodHasOverloadWithExpectedValueAttribute : Attribute; 7 | -------------------------------------------------------------------------------- /Funcky.Xunit/FunctionalAssert/EqualOrThrow.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky; 5 | 6 | public static partial class FunctionalAssert 7 | { 8 | private static void EqualOrThrow( 9 | T expected, 10 | T actual, 11 | Action @throw) 12 | { 13 | try 14 | { 15 | Assert.Equal(expected, actual); 16 | } 17 | catch (XunitException) 18 | { 19 | @throw(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Funcky.Xunit/FunctionalAssertException.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Xunit.Sdk; 3 | 4 | namespace Funcky; 5 | 6 | internal static class FunctionalAssertException 7 | { 8 | private static readonly string NewLineAndIndent = Environment.NewLine + new string(' ', 10); // Length of "Expected: " and "Actual: " 9 | 10 | public static XunitException ForMismatchedValues( 11 | string expected, 12 | string actual, 13 | [CallerMemberName] string? assertionName = null) 14 | { 15 | var assertionLabel = assertionName is not null 16 | ? $"{nameof(FunctionalAssert)}.{assertionName}()" 17 | : nameof(FunctionalAssert); 18 | return new XunitException( 19 | $"{assertionLabel} Failure: Values differ{Environment.NewLine}" 20 | + $"Expected: {expected.Replace(Environment.NewLine, NewLineAndIndent)}{Environment.NewLine}" 21 | + $"Actual: {actual.Replace(Environment.NewLine, NewLineAndIndent)}"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Funcky.Xunit/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | static Funcky.Extensions.ToTheoryDataExtension.ToTheoryData(this System.Collections.Generic.IEnumerable!>!>! source) -> Xunit.TheoryData! 3 | -------------------------------------------------------------------------------- /Funcky.Xunit/readme.md: -------------------------------------------------------------------------------- 1 | # Funcky.Xunit 2 | ### Stack Trace 3 | All `FunctionalAssert` methods have `AggressiveInlining` set and re-throw the exceptions, because the stack trace should only contain the test method's 4 | name (just like the `Assert` exceptions from xUnit). 5 | -------------------------------------------------------------------------------- /Funcky.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /Funcky/Buffers/ListBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Buffers; 2 | 3 | internal static class ListBuffer 4 | { 5 | public static ListBuffer Create(IList list) => new(list); 6 | } 7 | 8 | internal sealed class ListBuffer(IList source) : CollectionBuffer(source), IList 9 | { 10 | public T this[int index] 11 | { 12 | get 13 | { 14 | ThrowIfDisposed(); 15 | return source[index]; 16 | } 17 | 18 | set 19 | { 20 | ThrowIfDisposed(); 21 | source[index] = value; 22 | } 23 | } 24 | 25 | public int IndexOf(T item) 26 | { 27 | ThrowIfDisposed(); 28 | return source.IndexOf(item); 29 | } 30 | 31 | public void Insert(int index, T item) 32 | { 33 | ThrowIfDisposed(); 34 | source.Insert(index, item); 35 | } 36 | 37 | public void RemoveAt(int index) 38 | { 39 | ThrowIfDisposed(); 40 | source.RemoveAt(index); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Funcky/CodeAnalysis/NonDefaultableAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.CodeAnalysis; 2 | 3 | /// Structs annotated with this attribute should not be instantiated with . 4 | [AttributeUsage(AttributeTargets.Struct)] 5 | internal sealed class NonDefaultableAttribute : Attribute; 6 | -------------------------------------------------------------------------------- /Funcky/CodeAnalysis/SyntaxSupportOnlyAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.CodeAnalysis; 2 | 3 | #pragma warning disable CS9113 // Parameter is unread. 4 | [AttributeUsage(AttributeTargets.Property)] 5 | internal sealed class SyntaxSupportOnlyAttribute(string syntaxFeature) : Attribute 6 | #pragma warning restore CS9113 // Parameter is unread. 7 | { 8 | public const string ListPattern = "list pattern"; 9 | } 10 | -------------------------------------------------------------------------------- /Funcky/CodeAnalysis/UseWithArgumentNamesAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.CodeAnalysis; 2 | 3 | /// Methods annotated with this attribute should be used with named arguments. 4 | [AttributeUsage(AttributeTargets.Method)] 5 | internal sealed class UseWithArgumentNamesAttribute : Attribute; 6 | -------------------------------------------------------------------------------- /Funcky/Compatibility/EnumerableCompatibility.cs: -------------------------------------------------------------------------------- 1 | #if !NET6_0_OR_GREATER 2 | using System.Collections; 3 | 4 | namespace System.Linq; 5 | 6 | internal static class EnumerableCompatibility 7 | { 8 | public static bool TryGetNonEnumeratedCount(this IEnumerable source, out int count) 9 | { 10 | if (source is ICollection collectionOfT) 11 | { 12 | count = collectionOfT.Count; 13 | return true; 14 | } 15 | 16 | if (source is ICollection collection) 17 | { 18 | count = collection.Count; 19 | return true; 20 | } 21 | 22 | count = 0; 23 | return false; 24 | } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /Funcky/Discard.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static class Discard 4 | { 5 | /// A convenient shortcut for getting a . 6 | /// Useful for using switch expressions purely with guards when matching on non-constant values 7 | /// or as an alternative to if / else if / else chains. 8 | /// 9 | /// using static Funcky.Discard; 10 | /// return __ switch 11 | /// { 12 | /// _ when user.IsFrenchAdmin() => "le sécret", 13 | /// _ when user.IsAdmin() => "secret", 14 | /// _ => "(redacted)", 15 | /// }; 16 | /// 17 | /// The name is intentionally two underscores as to not conflict with C#'s discard syntax. 18 | public static readonly Unit __ = Unit.Value; 19 | } 20 | -------------------------------------------------------------------------------- /Funcky/Extensions/ActionExtensions/Compose.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class ActionExtensions 4 | { 5 | /// 6 | /// Function composition with action. 7 | /// 8 | /// f ∘ g,v => f(g(v)). 9 | /// 10 | [Pure] 11 | public static Action Compose(this Action f, Func g) 12 | => v => f(g(v)); 13 | 14 | /// 15 | /// Function composition with Action. 16 | /// 17 | /// f ∘ g, () => f(g()). 18 | /// 19 | [Pure] 20 | public static Action Compose(this Action f, Func g) 21 | => () => f(g()); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ConcatToString.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | /// 6 | /// Concatenates the elements of the given sequence to a single string. 7 | /// 8 | /// Type of the elements in sequence. 9 | /// Concatenated string. 10 | [Pure] 11 | public static string ConcatToString(this IEnumerable source) 12 | => string.Concat(source); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/EitherPartitions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct EitherPartitions 4 | where TLeft : notnull 5 | where TRight : notnull 6 | { 7 | public EitherPartitions(IReadOnlyList left, IReadOnlyList right) => (Left, Right) = (left, right); 8 | 9 | public IReadOnlyList Left { get; } 10 | 11 | public IReadOnlyList Right { get; } 12 | 13 | public void Deconstruct(out IReadOnlyList left, out IReadOnlyList right) => (left, right) = (Left, Right); 14 | } 15 | 16 | public static class EitherPartitions 17 | { 18 | public static EitherPartitions Create(IReadOnlyList left, IReadOnlyList right) 19 | where TLeft : notnull 20 | where TRight : notnull 21 | => new(left, right); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ForEach.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | /// 6 | /// The IEnumerable version of foreach. You can apply an action to each element. This is only useful when you have side effects. 7 | /// 8 | /// the inner type of the enumerable. 9 | public static Unit ForEach(this IEnumerable source, Action action) 10 | => source 11 | .ForEach(ActionToUnit(action)); 12 | 13 | /// 14 | /// The IEnumerable version of foreach. You can apply an ]]> to each element. This is only useful when you have side effects. 15 | /// 16 | /// the inner type of the enumerable. 17 | public static Unit ForEach(this IEnumerable source, Func action) 18 | => source 19 | .Aggregate(Unit.Value, (_, element) => action(element)); 20 | } 21 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/GetNonEnumeratedCountOrNone.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | #if TRY_GET_NON_ENUMERATED_COUNT 6 | public static Option GetNonEnumeratedCountOrNone(this IEnumerable source) 7 | => source.TryGetNonEnumeratedCount(out var count) 8 | ? count 9 | : Option.None; 10 | #endif 11 | } 12 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/Inspect.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | /// 6 | /// An IEnumerable that calls a function on each element before yielding it. It can be used to encode side effects without enumerating. 7 | /// The side effect will be executed when enumerating the result. 8 | /// 9 | /// the inner type of the enumerable. 10 | /// returns an with the side effect defined by action encoded in the enumerable. 11 | [Pure] 12 | public static IEnumerable Inspect(this IEnumerable source, Action inspector) 13 | => source.Select(element 14 | => 15 | { 16 | inspector(element); 17 | return element; 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/Intersperse.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | /// Returns a sequence with the items of the source sequence interspersed with the given . 6 | [Pure] 7 | public static IEnumerable Intersperse(this IEnumerable source, TSource element) 8 | => source.WithFirst() 9 | .SelectMany(s => s.IsFirst 10 | ? Funcky.Sequence.Return(s.Value) 11 | : Funcky.Sequence.Return(element, s.Value)); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/None.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | /// 6 | /// Determines whether no element exists or satisfies a condition. 7 | /// 8 | [Pure] 9 | public static bool None(this IEnumerable source, Func predicate) 10 | => !source.Any(predicate); 11 | 12 | /// 13 | /// Determines whether a sequence contains no elements. 14 | /// 15 | [Pure] 16 | public static bool None(this IEnumerable source) 17 | => !source.Any(); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/PartitionResult.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class EnumerableExtensions 6 | { 7 | /// Partitions the result values in an into an error and ok partition. 8 | public static ResultPartitions Partition(this IEnumerable> source) 9 | where TValidResult : notnull 10 | => source.Partition(ResultPartitions.Create); 11 | 12 | /// Partitions the either values in an into an error and ok partition. 13 | public static TResult Partition(this IEnumerable> source, Func, IReadOnlyList, TResult> resultSelector) 14 | where TValidResult : notnull 15 | => source 16 | .Aggregate(new PartitionBuilder(), PartitionBuilder.Add) 17 | .Build(resultSelector); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/Partitions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct Partitions 4 | { 5 | public Partitions(IReadOnlyList @true, IReadOnlyList @false) => (True, False) = (@true, @false); 6 | 7 | public IReadOnlyList True { get; } 8 | 9 | public IReadOnlyList False { get; } 10 | 11 | public void Deconstruct(out IReadOnlyList @true, out IReadOnlyList @false) => (@true, @false) = (True, False); 12 | } 13 | 14 | public static class Partitions 15 | { 16 | public static Partitions Create(IReadOnlyList @true, IReadOnlyList @false) 17 | => new(@true, @false); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ResultPartitions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct ResultPartitions 4 | { 5 | public ResultPartitions(IReadOnlyList error, IReadOnlyList ok) => (Error, Ok) = (error, ok); 6 | 7 | public IReadOnlyList Error { get; } 8 | 9 | public IReadOnlyList Ok { get; } 10 | 11 | public void Deconstruct(out IReadOnlyList error, out IReadOnlyList ok) => (error, ok) = (Error, Ok); 12 | } 13 | 14 | public static class ResultPartitions 15 | { 16 | public static ResultPartitions Create(IReadOnlyList error, IReadOnlyList ok) 17 | => new(error, ok); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ValueWithFirst.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct ValueWithFirst 4 | { 5 | public ValueWithFirst(TValue value, bool isFirst) => (Value, IsFirst) = (value, isFirst); 6 | 7 | public TValue Value { get; } 8 | 9 | public bool IsFirst { get; } 10 | 11 | public void Deconstruct(out TValue value, out bool isFirst) => (value, isFirst) = (Value, IsFirst); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ValueWithIndex.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct ValueWithIndex 4 | { 5 | public ValueWithIndex(TValue value, int index) => (Value, Index) = (value, index); 6 | 7 | public TValue Value { get; } 8 | 9 | public int Index { get; } 10 | 11 | public void Deconstruct(out TValue value, out int index) => (value, index) = (Value, Index); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ValueWithLast.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct ValueWithLast 4 | { 5 | public ValueWithLast(TValue value, bool isLast) => (Value, IsLast) = (value, isLast); 6 | 7 | public TValue Value { get; } 8 | 9 | public bool IsLast { get; } 10 | 11 | public void Deconstruct(out TValue value, out bool isLast) => (value, isLast) = (Value, IsLast); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/ValueWithPrevious.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public readonly struct ValueWithPrevious 4 | where TValue : notnull 5 | { 6 | public ValueWithPrevious(TValue value, Option previous) => (Value, Previous) = (value, previous); 7 | 8 | public TValue Value { get; } 9 | 10 | public Option Previous { get; } 11 | 12 | public void Deconstruct(out TValue value, out Option previous) => (value, previous) = (Value, Previous); 13 | } 14 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumerableExtensions/WhereNotNull.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class EnumerableExtensions 4 | { 5 | [Pure] 6 | public static IEnumerable WhereNotNull(this IEnumerable source) 7 | where TSource : class 8 | => source.WhereSelect(Option.FromNullable); 9 | 10 | [Pure] 11 | public static IEnumerable WhereNotNull(this IEnumerable source) 12 | where TSource : struct 13 | => source.WhereSelect(Option.FromNullable); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky/Extensions/EnumeratorExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static class EnumeratorExtensions 4 | { 5 | /// Advances the enumerator and returns Some with new element of enumeration 6 | /// or if no more elements are available. 7 | public static Option MoveNextOrNone(this IEnumerator enumerator) 8 | where T : notnull 9 | => enumerator.MoveNext() 10 | ? enumerator.Current 11 | : Option.None; 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Extensions/FuncExtensions/Compose.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Extensions; 2 | 3 | public static partial class FuncExtensions 4 | { 5 | /// 6 | /// Function composition. 7 | /// 8 | /// f ∘ g, v => f(g(v)). 9 | /// 10 | [Pure] 11 | public static Func Compose(this Func f, Func g) 12 | => v => f(g(v)); 13 | 14 | /// 15 | /// Function composition. 16 | /// 17 | /// f ∘ g,() => f(g()). 18 | /// 19 | [Pure] 20 | public static Func Compose(this Func f, Func g) 21 | => () => f(g()); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky/Extensions/HttpHeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class HttpHeadersExtensions 6 | { 7 | public static Option> GetValuesOrNone(this HttpHeaders headers, string name) 8 | => headers.TryGetValues(name, out var result) 9 | ? Option.Some(result) 10 | : Option>.None; 11 | } 12 | -------------------------------------------------------------------------------- /Funcky/Extensions/HttpHeadersNonValidatedExtensions.cs: -------------------------------------------------------------------------------- 1 | #if HTTP_HEADERS_NON_VALIDATED 2 | using System.Net.Http.Headers; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | public static class HttpHeadersNonValidatedExtensions 7 | { 8 | public static Option GetValuesOrNone(this HttpHeadersNonValidated headers, string headerName) 9 | => headers.TryGetValues(headerName, out var values) 10 | ? values 11 | : Option.None; 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /Funcky/Extensions/JsonSerializerOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | #if JSON_SERIALIZER_OPTIONS_TRY_GET_TYPE_INFO 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization.Metadata; 4 | 5 | namespace Funcky.Extensions; 6 | 7 | public static class JsonSerializerOptionsExtensions 8 | { 9 | public static Option GetTypeInfoOrNone(this JsonSerializerOptions options, Type type) 10 | => options.TryGetTypeInfo(type, out var typeInfo) 11 | ? typeInfo 12 | : Option.None; 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Funcky/Extensions/ListExtensions/IList.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Internal.ValueMapper; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class ListExtensions 6 | { 7 | [Pure] 8 | public static Option IndexOfOrNone(this IList list, TValue value) 9 | => MapNotFoundToNone(list.IndexOf(value)); 10 | } 11 | -------------------------------------------------------------------------------- /Funcky/Extensions/OrderedDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | #if ORDERED_DICTIONARY 2 | using static Funcky.Internal.ValueMapper; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | public static class OrderedDictionaryExtensions 7 | { 8 | /// Determines the index of a specific key in the . 9 | /// The dictionary to search. 10 | /// The key to locate. 11 | /// 12 | /// is . 13 | /// The index of if found; otherwise, . 14 | public static Option IndexOfOrNone(this OrderedDictionary dictionary, TKey key) 15 | where TKey : notnull 16 | => MapNotFoundToNone(dictionary.IndexOf(key)); 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.AssemblyNameInfo.cs: -------------------------------------------------------------------------------- 1 | #if REFLECTION_ASSEMBLY_NAME_INFO 2 | using System.Reflection.Metadata; 3 | using Funcky.Internal; 4 | 5 | namespace Funcky.Extensions; 6 | 7 | [OrNoneFromTryPattern(typeof(AssemblyNameInfo), nameof(AssemblyNameInfo.TryParse))] 8 | public static partial class ParseExtensions; 9 | #endif 10 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.BigInteger.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using Funcky.Internal; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | [OrNoneFromTryPattern(typeof(BigInteger), nameof(BigInteger.TryParse))] 7 | public static partial class ParseExtensions; 8 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.Boolean.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(bool), nameof(bool.TryParse))] 6 | public static partial class ParseExtensions; 7 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.Char.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(char), nameof(char.TryParse))] 6 | public static partial class ParseExtensions; 7 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.DateOnly.cs: -------------------------------------------------------------------------------- 1 | #if DATE_ONLY_SUPPORTED 2 | using Funcky.Internal; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | [OrNoneFromTryPattern(typeof(DateOnly), nameof(DateOnly.TryParse))] 7 | [OrNoneFromTryPattern(typeof(DateOnly), nameof(DateOnly.TryParseExact))] 8 | public static partial class ParseExtensions; 9 | #endif 10 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.DateTime.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(DateTime), nameof(DateTime.TryParse))] 6 | [OrNoneFromTryPattern(typeof(DateTime), nameof(DateTime.TryParseExact))] 7 | public static partial class ParseExtensions; 8 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.DateTimeOffset.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(DateTimeOffset), nameof(DateTimeOffset.TryParse))] 6 | [OrNoneFromTryPattern(typeof(DateTimeOffset), nameof(DateTimeOffset.TryParseExact))] 7 | public static partial class ParseExtensions; 8 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.GenericNumber.cs: -------------------------------------------------------------------------------- 1 | #if GENERIC_MATH 2 | using System.Globalization; 3 | using System.Numerics; 4 | 5 | namespace Funcky.Extensions; 6 | 7 | public static partial class ParseExtensions 8 | { 9 | public static Option ParseNumberOrNone(this string value, NumberStyles style, IFormatProvider? provider) 10 | where TNumber : INumberBase 11 | => TNumber.TryParse(value, style, provider, out var result) 12 | ? result 13 | : Option.None; 14 | 15 | public static Option ParseNumberOrNone(this ReadOnlySpan value, NumberStyles style, IFormatProvider? provider) 16 | where TNumber : INumberBase 17 | => TNumber.TryParse(value, style, provider, out var result) 18 | ? result 19 | : Option.None; 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.Guid.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(Guid), nameof(Guid.TryParse))] 6 | [OrNoneFromTryPattern(typeof(Guid), nameof(Guid.TryParseExact))] 7 | public static partial class ParseExtensions; 8 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.IpPrimitives.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Funcky.Internal; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | [OrNoneFromTryPattern(typeof(IPAddress), nameof(IPAddress.TryParse))] 7 | #if IP_END_POINT_TRY_PARSE_SUPPORTED 8 | [OrNoneFromTryPattern(typeof(IPEndPoint), nameof(IPEndPoint.TryParse))] 9 | #endif 10 | #if IP_NETWORK 11 | [OrNoneFromTryPattern(typeof(IPNetwork), nameof(IPNetwork.TryParse))] 12 | #endif 13 | public static partial class ParseExtensions; 14 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.Numbers.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(byte), nameof(byte.TryParse))] 6 | [OrNoneFromTryPattern(typeof(sbyte), nameof(sbyte.TryParse))] 7 | [OrNoneFromTryPattern(typeof(short), nameof(short.TryParse))] 8 | [OrNoneFromTryPattern(typeof(ushort), nameof(ushort.TryParse))] 9 | [OrNoneFromTryPattern(typeof(int), nameof(int.TryParse))] 10 | [OrNoneFromTryPattern(typeof(uint), nameof(uint.TryParse))] 11 | [OrNoneFromTryPattern(typeof(long), nameof(long.TryParse))] 12 | [OrNoneFromTryPattern(typeof(ulong), nameof(ulong.TryParse))] 13 | [OrNoneFromTryPattern(typeof(float), nameof(float.TryParse))] 14 | [OrNoneFromTryPattern(typeof(double), nameof(double.TryParse))] 15 | [OrNoneFromTryPattern(typeof(decimal), nameof(decimal.TryParse))] 16 | public static partial class ParseExtensions; 17 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.TimeOnly.cs: -------------------------------------------------------------------------------- 1 | #if TIME_ONLY_SUPPORTED 2 | using Funcky.Internal; 3 | 4 | namespace Funcky.Extensions; 5 | 6 | [OrNoneFromTryPattern(typeof(TimeOnly), nameof(TimeOnly.TryParse))] 7 | [OrNoneFromTryPattern(typeof(TimeOnly), nameof(TimeOnly.TryParseExact))] 8 | public static partial class ParseExtensions; 9 | #endif 10 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.TimeSpan.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(TimeSpan), nameof(TimeSpan.TryParse))] 6 | [OrNoneFromTryPattern(typeof(TimeSpan), nameof(TimeSpan.TryParseExact))] 7 | public static partial class ParseExtensions; 8 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.TypeName.cs: -------------------------------------------------------------------------------- 1 | #if REFLECTION_TYPE_NAME 2 | using System.Reflection.Metadata; 3 | using Funcky.Internal; 4 | 5 | namespace Funcky.Extensions; 6 | 7 | [OrNoneFromTryPattern(typeof(TypeName), nameof(TypeName.TryParse))] 8 | public static partial class ParseExtensions; 9 | #endif 10 | -------------------------------------------------------------------------------- /Funcky/Extensions/ParseExtensions/ParseExtensions.Version.cs: -------------------------------------------------------------------------------- 1 | using Funcky.Internal; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | [OrNoneFromTryPattern(typeof(Version), nameof(Version.TryParse))] 6 | public static partial class ParseExtensions; 7 | -------------------------------------------------------------------------------- /Funcky/Extensions/PriorityQueueExtensions.cs: -------------------------------------------------------------------------------- 1 | #if PRIORITY_QUEUE 2 | namespace Funcky.Extensions; 3 | 4 | public static class PriorityQueueExtensions 5 | { 6 | public static Option<(TElement Element, TPriority Priority)> DequeueOrNone(this PriorityQueue priorityQueue) 7 | where TElement : notnull 8 | => priorityQueue.TryDequeue(out var element, out var priority) 9 | ? Option.Some((element, priority)) 10 | : Option<(TElement, TPriority)>.None; 11 | 12 | public static Option<(TElement Element, TPriority Priority)> PeekOrNone(this PriorityQueue priorityQueue) 13 | where TElement : notnull 14 | => priorityQueue.TryPeek(out var element, out var priority) 15 | ? Option.Some((element, priority)) 16 | : Option<(TElement, TPriority)>.None; 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/StringExtensions/IndexOfAnyOrNone.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Internal.ValueMapper; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class StringExtensions 6 | { 7 | [Pure] 8 | public static Option IndexOfAnyOrNone(this string haystack, char[] anyOf) 9 | => MapNotFoundToNone(haystack.IndexOfAny(anyOf)); 10 | 11 | [Pure] 12 | public static Option IndexOfAnyOrNone(this string haystack, char[] anyOf, int startIndex) 13 | => MapNotFoundToNone(haystack.IndexOfAny(anyOf, startIndex)); 14 | 15 | [Pure] 16 | public static Option IndexOfAnyOrNone(this string haystack, char[] anyOf, int startIndex, int count) 17 | => MapNotFoundToNone(haystack.IndexOfAny(anyOf, startIndex, count)); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Extensions/StringExtensions/LastIndexOfAnyOrNone.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Internal.ValueMapper; 2 | 3 | namespace Funcky.Extensions; 4 | 5 | public static partial class StringExtensions 6 | { 7 | [Pure] 8 | public static Option LastIndexOfAnyOrNone(this string haystack, char[] anyOf) 9 | => MapNotFoundToNone(haystack.LastIndexOfAny(anyOf)); 10 | 11 | [Pure] 12 | public static Option LastIndexOfAnyOrNone(this string haystack, char[] anyOf, int startIndex) 13 | => MapNotFoundToNone(haystack.LastIndexOfAny(anyOf, startIndex)); 14 | 15 | [Pure] 16 | public static Option LastIndexOfAnyOrNone(this string haystack, char[] anyOf, int startIndex, int count) 17 | => MapNotFoundToNone(haystack.LastIndexOfAny(anyOf, startIndex, count)); 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Functional/Identity.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Funcky; 4 | 5 | public static partial class Functional 6 | { 7 | [Pure] 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public static T Identity(T value) => value; 10 | } 11 | -------------------------------------------------------------------------------- /Funcky/Functional/PredicateComposition.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Functional 4 | { 5 | /// 6 | /// Returns a new predicate that is true when all given predicates are true. 7 | /// 8 | [Pure] 9 | public static Func All(params Func[] predicates) 10 | => value => predicates.All(predicate => predicate(value)); 11 | 12 | /// 13 | /// Returns a new predicate that is true when any of the given predicates are true. 14 | /// 15 | [Pure] 16 | public static Func Any(params Func[] predicates) 17 | => value => predicates.Any(predicate => predicate(value)); 18 | 19 | /// 20 | /// Returns a new predicate that inverts the given predicate. 21 | /// 22 | [Pure] 23 | public static Func Not(Func predicate) 24 | => value => !predicate(value); 25 | } 26 | -------------------------------------------------------------------------------- /Funcky/IBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | /// 4 | /// Represents a buffer of an underlying resource and is accordingly. 5 | /// 6 | /// Element type. 7 | public interface IBuffer : IEnumerable, IDisposable; 8 | -------------------------------------------------------------------------------- /Funcky/ILLink.LinkAttributes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Funcky/Internal/Aggregators/DecimalAverageAggregator.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Monads.Option; 2 | 3 | namespace Funcky.Internal.Aggregators; 4 | 5 | internal class DecimalAverageAggregator 6 | { 7 | public static readonly DecimalAverageAggregator Empty = new(); 8 | 9 | private readonly Option _sum; 10 | private readonly int _count; 11 | 12 | private DecimalAverageAggregator(int count = default, Option sum = default) 13 | => (_count, _sum) = (count, sum); 14 | 15 | public Option Average => _sum.Select(sum => sum / _count); 16 | 17 | public DecimalAverageAggregator Add(decimal term) 18 | => new(_count + 1, Some(_sum.Match(none: term, some: sum => sum + term))); 19 | 20 | public DecimalAverageAggregator Add(Option term) 21 | => term.Match(none: this, some: Add); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky/Internal/Aggregators/FloatAverageAggregator.cs: -------------------------------------------------------------------------------- 1 | using static Funcky.Monads.Option; 2 | 3 | namespace Funcky.Internal.Aggregators; 4 | 5 | internal class FloatAverageAggregator 6 | { 7 | public static readonly FloatAverageAggregator Empty = new(); 8 | 9 | // we calculate with double for higher precision and fewer overflow problems 10 | // The implementation in .NET seems to do the same, because we seem to have no aberration. 11 | private readonly Option _sum; 12 | private readonly int _count; 13 | 14 | private FloatAverageAggregator(int count = default, Option sum = default) 15 | => (_count, _sum) = (count, sum); 16 | 17 | public Option Average => _sum.Select(sum => (float)(sum / _count)); 18 | 19 | public FloatAverageAggregator Add(float term) 20 | => new(_count + 1, Some(_sum.Match(none: term, some: sum => sum + term))); 21 | 22 | public FloatAverageAggregator Add(Option term) 23 | => term.Match(none: this, some: Add); 24 | } 25 | -------------------------------------------------------------------------------- /Funcky/Internal/Aggregators/MaxAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal.Aggregators; 2 | 3 | internal static class MaxAggregator 4 | { 5 | public static Option Aggregate(Option min, TResult current) 6 | where TResult : notnull 7 | => min.Match(none: current, some: Maximum(current)); 8 | 9 | private static Func Maximum(TSource right) 10 | => left 11 | => Comparer.Default.Compare(left, right) > 0 12 | ? left 13 | : right; 14 | } 15 | -------------------------------------------------------------------------------- /Funcky/Internal/Aggregators/MinAggregator.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal.Aggregators; 2 | 3 | internal static class MinAggregator 4 | { 5 | public static Option Aggregate(Option min, TResult current) 6 | where TResult : notnull 7 | => min.Match(none: current, some: Minimum(current)); 8 | 9 | // For floats this defines a total order where NaN comes before negative infinity 10 | private static Func Minimum(TSource right) 11 | => left 12 | => Comparer.Default.Compare(left, right) < 0 13 | ? left 14 | : right; 15 | } 16 | -------------------------------------------------------------------------------- /Funcky/Internal/ComparisonResult.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal; 2 | 3 | internal static class ComparisonResult 4 | { 5 | public const int LessThan = -1; 6 | public const int Equal = 0; 7 | public const int GreaterThan = 1; 8 | } 9 | -------------------------------------------------------------------------------- /Funcky/Internal/FailToOption.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal; 2 | 3 | internal static class FailToOption 4 | where TResult : notnull 5 | { 6 | public static Option FromException(Func func) 7 | where TException : Exception 8 | { 9 | try 10 | { 11 | return func(); 12 | } 13 | catch (TException) 14 | { 15 | return Option.None; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Funcky/Internal/Mixer.cs: -------------------------------------------------------------------------------- 1 | #if !RANDOM_SHUFFLE 2 | using System.Collections.Immutable; 3 | 4 | namespace Funcky.Internal; 5 | 6 | internal static class Mixer 7 | { 8 | public static IReadOnlyList ToRandomList(this IList source, Random random) 9 | => Enumerable 10 | .Range(0, source.Count) 11 | .Aggregate(ImmutableList.Empty, AggregateShuffle(source, random)); 12 | 13 | private static Func, int, ImmutableList> AggregateShuffle(IList source, Random random) 14 | => (shuffled, currentIndex) 15 | => shuffled.Add(UseElement(source, currentIndex, random.Next(currentIndex, source.Count))); 16 | 17 | private static TSource UseElement(IList source, int currentIndex, int randomIndex) 18 | { 19 | var result = source[randomIndex]; 20 | 21 | source[randomIndex] = source[currentIndex]; 22 | 23 | return result; 24 | } 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /Funcky/Internal/OptionTupleExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal; 2 | 3 | internal static class OptionTupleExtensions 4 | { 5 | public static TResult Match( 6 | this (Option Left, Option Right) tuple, 7 | Func left, 8 | Func right, 9 | Func leftAndRight, 10 | Func none) 11 | where TLeft : notnull 12 | where TRight : notnull 13 | => tuple.Left.Match( 14 | some: leftItem => tuple.Right.Match( 15 | some: rightItem => leftAndRight(leftItem, rightItem), 16 | none: () => left(leftItem)), 17 | none: tuple.Right.Match( 18 | some: right, 19 | none: none)); 20 | } 21 | -------------------------------------------------------------------------------- /Funcky/Internal/RangeEnumerable.cs: -------------------------------------------------------------------------------- 1 | #if RANGE_SUPPORTED 2 | using System.Collections; 3 | 4 | namespace Funcky.Internal; 5 | 6 | internal class RangeEnumerable(Range range) : IEnumerable 7 | { 8 | public IEnumerator GetEnumerator() 9 | => range.GetEnumerator(); 10 | 11 | IEnumerator IEnumerable.GetEnumerator() 12 | => GetEnumerator(); 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /Funcky/Internal/Validators/ChunkSizeValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Funcky.Internal.Validators; 4 | 5 | internal static class ChunkSizeValidator 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static int Validate(int size) 9 | => size > 0 10 | ? size 11 | : throw new ArgumentOutOfRangeException(nameof(size), size, "Size must be bigger than 0"); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Internal/Validators/WindowWidthValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Funcky.Internal.Validators; 4 | 5 | internal static class WindowWidthValidator 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static int Validate(int width) 9 | => width > 0 10 | ? width 11 | : throw new ArgumentOutOfRangeException(nameof(width), width, "The width of the window must be larger than 0"); 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Internal/ValueMapper.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Internal; 2 | 3 | internal static class ValueMapper 4 | { 5 | private const int NotFoundValue = -1; 6 | 7 | [Pure] 8 | public static Option MapNotFoundToNone(int index) 9 | => index is NotFoundValue 10 | ? Option.None 11 | : index; 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Monads/Either/Either.Debugger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using static System.Diagnostics.DebuggerBrowsableState; 3 | 4 | namespace Funcky.Monads; 5 | 6 | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] 7 | [DebuggerTypeProxy(typeof(EitherDebugView<,>))] 8 | public readonly partial struct Either 9 | { 10 | [DebuggerBrowsable(Never)] 11 | private string DebuggerDisplay => Match( 12 | uninitialized: static () => "default", 13 | left: static _ => "Left", 14 | right: static _ => "Right"); 15 | } 16 | 17 | internal sealed class EitherDebugView(Either either) 18 | where TLeft : notnull 19 | where TRight : notnull 20 | { 21 | [DebuggerBrowsable(RootHidden)] 22 | public object Value => either.Match( 23 | uninitialized: static () => new { }, 24 | left: left => new { Value = left }, 25 | right: right => new { Value = right }); 26 | } 27 | -------------------------------------------------------------------------------- /Funcky/Monads/Either/EitherExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public static partial class EitherExtensions 4 | { 5 | /// Returns the left value or if the is a right value. 6 | [Pure] 7 | public static Option LeftOrNone(this Either either) 8 | where TLeft : notnull 9 | where TRight : notnull 10 | => either.Match( 11 | left: Option.Some, 12 | right: static _ => Option.None); 13 | 14 | /// Returns the right value or if the is a left value. 15 | [Pure] 16 | public static Option RightOrNone(this Either either) 17 | where TLeft : notnull 18 | where TRight : notnull 19 | => either.Match( 20 | left: static _ => Option.None, 21 | right: Option.Some); 22 | } 23 | -------------------------------------------------------------------------------- /Funcky/Monads/Either/IEither.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Funcky.Monads; 4 | 5 | /// Marker interface implemented for . 6 | [EditorBrowsable(EditorBrowsableState.Advanced)] 7 | public interface IEither 8 | { 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | internal void InternalImplementationOnly(); 11 | } 12 | -------------------------------------------------------------------------------- /Funcky/Monads/Lazy/Lazy.Core.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes; 3 | 4 | namespace Funcky.Monads; 5 | 6 | public static class Lazy 7 | { 8 | [Pure] 9 | public static Lazy FromFunc<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] T>(Func valueFactory) 10 | => new(valueFactory); 11 | 12 | #if LAZY_RETURN_CONSTRUCTOR 13 | [Pure] 14 | public static Lazy Return<[DynamicallyAccessedMembers(PublicParameterlessConstructor)] T>(T value) 15 | => new(value); 16 | #else 17 | [Pure] 18 | public static Lazy Return(T value) 19 | => new(() => value); 20 | #endif 21 | } 22 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/IOption.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Funcky.Monads; 4 | 5 | /// Marker interface implemented for . 6 | [EditorBrowsable(EditorBrowsableState.Advanced)] 7 | public interface IOption 8 | { 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | internal void InternalImplementationOnly(); 11 | } 12 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/Option.Debugger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using static System.Diagnostics.DebuggerBrowsableState; 3 | 4 | namespace Funcky.Monads; 5 | 6 | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] 7 | [DebuggerTypeProxy(typeof(OptionDebugView<>))] 8 | public readonly partial struct Option 9 | { 10 | [DebuggerBrowsable(Never)] 11 | private string DebuggerDisplay => Match( 12 | none: "None", 13 | some: _ => "Some"); 14 | } 15 | 16 | internal sealed class OptionDebugView(Option option) 17 | where T : notnull 18 | { 19 | [DebuggerBrowsable(RootHidden)] 20 | public object Value => option.Match( 21 | none: () => (object)new { }, 22 | some: value => new { Value = value }); 23 | } 24 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/Option.Equality.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public readonly partial struct Option : IEquatable> 4 | { 5 | public static bool operator ==(Option left, Option right) => left.Equals(right); 6 | 7 | public static bool operator !=(Option left, Option right) => !left.Equals(right); 8 | 9 | [Pure] 10 | public override bool Equals(object? obj) 11 | => obj is Option other && Equals(other); 12 | 13 | [Pure] 14 | public bool Equals(Option other) 15 | => OptionEqualityComparer.Default.Equals(this, other); 16 | 17 | [Pure] 18 | public override int GetHashCode() 19 | => OptionEqualityComparer.Default.GetHashCode(this); 20 | } 21 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/Option.FromNullable.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public static partial class Option 4 | { 5 | /// 6 | /// Creates an from a nullable value. 7 | /// 8 | [Pure] 9 | public static Option FromNullable(TItem? item) 10 | where TItem : class 11 | => item is null ? Option.None : Some(item); 12 | 13 | /// 14 | /// Creates an from a nullable value. 15 | /// 16 | [Pure] 17 | public static Option FromNullable(TItem? item) 18 | where TItem : struct 19 | => item.HasValue ? Some(item.Value) : Option.None; 20 | } 21 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/Option.ListPattern.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics; 3 | using Funcky.CodeAnalysis; 4 | 5 | namespace Funcky.Monads; 6 | 7 | public readonly partial struct Option 8 | where TItem : notnull 9 | { 10 | private const int NoneLength = 0; 11 | private const int SomeLength = 1; 12 | 13 | [Pure] 14 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 15 | [EditorBrowsable(EditorBrowsableState.Never)] 16 | [SyntaxSupportOnly(SyntaxSupportOnlyAttribute.ListPattern)] 17 | public int Count 18 | => _hasItem 19 | ? SomeLength 20 | : NoneLength; 21 | 22 | [Pure] 23 | [DebuggerBrowsable(DebuggerBrowsableState.Never)] 24 | [EditorBrowsable(EditorBrowsableState.Never)] 25 | [SyntaxSupportOnly(SyntaxSupportOnlyAttribute.ListPattern)] 26 | public TItem this[int index] 27 | => _hasItem && index is 0 28 | ? _item 29 | : throw new IndexOutOfRangeException("Index was out of range."); 30 | } 31 | -------------------------------------------------------------------------------- /Funcky/Monads/Option/Option.Monad.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public readonly partial struct Option 4 | { 5 | [Pure] 6 | public Option Select(Func selector) 7 | where TResult : notnull 8 | => Match( 9 | none: Option.None, 10 | some: item => selector(item)); 11 | 12 | [Pure] 13 | public Option SelectMany(Func> selector) 14 | where TResult : notnull 15 | => Match( 16 | none: Option.None, 17 | some: selector); 18 | 19 | [Pure] 20 | public Option SelectMany(Func> selector, Func resultSelector) 21 | where TResult : notnull 22 | where TOption : notnull 23 | => SelectMany( 24 | item => selector(item).Select( 25 | option => resultSelector(item, option))); 26 | } 27 | -------------------------------------------------------------------------------- /Funcky/Monads/Reader/Reader.Core.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.Monads; 2 | 3 | public delegate TResult Reader(TEnvironment environment) 4 | where TEnvironment : notnull 5 | where TResult : notnull; 6 | 7 | public static class Reader 8 | where TEnvironment : notnull 9 | { 10 | [Pure] 11 | public static Reader Return(TResult value) 12 | where TResult : notnull 13 | => _ 14 | => value; 15 | 16 | [Pure] 17 | public static Reader FromFunc(Func function) 18 | where TResult : notnull 19 | => function.Invoke; 20 | 21 | [Pure] 22 | public static Reader FromAction(Action action) 23 | => ActionToUnit(action).Invoke; 24 | } 25 | -------------------------------------------------------------------------------- /Funcky/Monads/Result/Result.Debugger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using static System.Diagnostics.DebuggerBrowsableState; 3 | 4 | namespace Funcky.Monads; 5 | 6 | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] 7 | [DebuggerTypeProxy(typeof(ResultDebugView<>))] 8 | public readonly partial struct Result 9 | { 10 | [DebuggerBrowsable(Never)] 11 | private string DebuggerDisplay => Match( 12 | ok: _ => "Ok", 13 | error: _ => "Error"); 14 | } 15 | 16 | internal sealed class ResultDebugView(Result result) 17 | where TValidResult : notnull 18 | { 19 | [DebuggerBrowsable(RootHidden)] 20 | public object Value => result.Match( 21 | ok: value => (object)new { Value = value }, 22 | error: exception => new { Exception = exception }); 23 | } 24 | -------------------------------------------------------------------------------- /Funcky/RequireClass.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using static System.ComponentModel.EditorBrowsableState; 3 | 4 | namespace Funcky; 5 | 6 | [EditorBrowsable(Never)] 7 | public sealed class RequireClass 8 | where T : class; 9 | -------------------------------------------------------------------------------- /Funcky/RequireStruct.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using static System.ComponentModel.EditorBrowsableState; 3 | 4 | namespace Funcky; 5 | 6 | [EditorBrowsable(Never)] 7 | public sealed class RequireStruct 8 | where T : struct; 9 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/ConstantDelayPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public class ConstantDelayPolicy(int maxRetries, TimeSpan delay) : IRetryPolicy 4 | { 5 | public int MaxRetries { get; } = maxRetries; 6 | 7 | public TimeSpan Delay(int retryCount) => delay; 8 | } 9 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/DoNotRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public sealed class DoNotRetryPolicy : IRetryPolicy 4 | { 5 | public int MaxRetries => 0; 6 | 7 | public TimeSpan Delay(int retryCount) => TimeSpan.Zero; 8 | } 9 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/ExponentialBackOffRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public sealed class ExponentialBackOffRetryPolicy(int maxRetries, TimeSpan firstDelay) : IRetryPolicy 4 | { 5 | private const double BaseFactor = 1.5; 6 | 7 | public int MaxRetries => maxRetries; 8 | 9 | public TimeSpan Delay(int retryCount) 10 | => firstDelay.Multiply(Exponential(retryCount)); 11 | 12 | private static double Exponential(int retryCount) 13 | => Math.Pow(BaseFactor, retryCount); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/IRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public interface IRetryPolicy 4 | { 5 | int MaxRetries { get; } 6 | 7 | TimeSpan Delay(int retryCount); 8 | } 9 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/LinearBackOffRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public sealed class LinearBackOffRetryPolicy(int maxRetries, TimeSpan firstDelay) 4 | : IRetryPolicy 5 | { 6 | public int MaxRetries => maxRetries; 7 | 8 | public TimeSpan Delay(int retryCount) => firstDelay.Multiply(retryCount); 9 | } 10 | -------------------------------------------------------------------------------- /Funcky/RetryPolicies/NoDelayRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky.RetryPolicies; 2 | 3 | public sealed class NoDelayRetryPolicy(int maxRetries) : ConstantDelayPolicy(maxRetries, TimeSpan.Zero); 4 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.Concat.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | /// 6 | /// Concatenates multiple sequences together. 7 | /// 8 | [Pure] 9 | public static IEnumerable Concat(params IEnumerable[] sources) 10 | => Concat(sources.AsEnumerable()); 11 | 12 | /// 13 | /// Concatenates multiple sequences together. 14 | /// 15 | [Pure] 16 | public static IEnumerable Concat(IEnumerable> sources) 17 | => sources 18 | .SelectMany(Identity); 19 | } 20 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.Cycle.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | /// 6 | /// Cycles the same element over and over again as an endless generator. 7 | /// 8 | /// Type of the element to be cycled. 9 | /// The element to be cycled. 10 | /// Returns an infinite IEnumerable cycling through the same elements. 11 | [Pure] 12 | public static IEnumerable Cycle(TResult element) 13 | => Successors(element, Identity); 14 | } 15 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.CycleMaterialized.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | /// 6 | /// Generates a sequence that contains the same sequence of elements over and over again as an endless generator. 7 | /// 8 | /// Type of the elements to be cycled. 9 | /// The sequence of elements which are cycled. Throws an exception if the sequence is empty. 10 | /// Returns an infinite IEnumerable repeating the same sequence of elements. 11 | /// Use if you need to cycle a lazy sequence. 12 | [Pure] 13 | public static IEnumerable CycleMaterialized(IReadOnlyCollection source) 14 | => source.Count > 0 15 | ? Cycle(source).SelectMany(Identity) 16 | : throw new InvalidOperationException("you cannot cycle an empty enumerable"); 17 | } 18 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.FromNullable.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable SA1010 // StyleCop support for collection expressions is missing 2 | namespace Funcky; 3 | 4 | public static partial class Sequence 5 | { 6 | /// An consisting of a single item or zero items. 7 | [Pure] 8 | public static IEnumerable FromNullable(TResult? element) 9 | where TResult : class 10 | => element is null ? [] : [element]; 11 | 12 | /// 13 | [Pure] 14 | public static IEnumerable FromNullable(TResult? element) 15 | where TResult : struct 16 | => element.HasValue ? [element.Value] : []; 17 | } 18 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.RepeatMaterialized.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | /// 6 | /// Generates a sequence that contains the same sequence of elements the given number of times. 7 | /// 8 | /// Type of the elements to be repeated. 9 | /// The sequence of elements to be repeated. 10 | /// The number of times to repeat the value in the generated sequence. 11 | /// Returns an infinite IEnumerable cycling through the same elements. 12 | /// Use if you need to cycle a lazy sequence. 13 | [Pure] 14 | public static IEnumerable RepeatMaterialized(IReadOnlyCollection source, int count) 15 | => Enumerable.Repeat(source, count).SelectMany(Identity); 16 | } 17 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.RepeatRange.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | /// 6 | /// Generates a sequence that contains the same sequence of elements the given number of times. 7 | /// 8 | /// Type of the elements to be repeated. 9 | /// The sequence of elements to be repeated. 10 | /// The number of times to repeat the value in the generated sequence. 11 | /// Returns an infinite IEnumerable cycling through the same elements. 12 | /// Use if you need to cycle an already materialized sequence. 13 | [Pure] 14 | public static IBuffer RepeatRange(IEnumerable source, int count) 15 | => CycleBuffer.Create(source, count); 16 | } 17 | -------------------------------------------------------------------------------- /Funcky/Sequence/Sequence.Return.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public static partial class Sequence 4 | { 5 | [Pure] 6 | public static IReadOnlyList Return(TResult element) 7 | => Return(elements: element); 8 | 9 | [Pure] 10 | public static IReadOnlyList Return(params TResult[] elements) 11 | => elements; 12 | } 13 | -------------------------------------------------------------------------------- /Funcky/Unit.cs: -------------------------------------------------------------------------------- 1 | namespace Funcky; 2 | 3 | public readonly struct Unit : IEquatable, IComparable 4 | { 5 | [Pure] 6 | public static Unit Value => default; 7 | 8 | [Pure] 9 | public static bool operator ==(Unit left, Unit right) => true; 10 | 11 | [Pure] 12 | public static bool operator !=(Unit left, Unit right) => false; 13 | 14 | [Pure] 15 | public static bool operator <(Unit left, Unit right) => false; 16 | 17 | [Pure] 18 | public static bool operator <=(Unit left, Unit right) => true; 19 | 20 | [Pure] 21 | public static bool operator >(Unit left, Unit right) => false; 22 | 23 | [Pure] 24 | public static bool operator >=(Unit left, Unit right) => true; 25 | 26 | [Pure] 27 | public bool Equals(Unit other) => true; 28 | 29 | [Pure] 30 | public override bool Equals(object? obj) => obj is Unit; 31 | 32 | [Pure] 33 | public override int GetHashCode() => 0; 34 | 35 | [Pure] 36 | public int CompareTo(Unit other) => 0; 37 | } 38 | -------------------------------------------------------------------------------- /Funcky/build/Funcky.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /GlobalUsings.Test.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /GlobalUsings.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Polyadic - This library is dual licensed 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SupportPolicy.md: -------------------------------------------------------------------------------- 1 | # Support Policy 2 | 3 | Funcky is a stable base-library which tries to support many scenario's in the .NET world. 4 | 5 | ## What Frameworks do we support? 6 | 7 | We support the all the actively supported modern Frameworks, .NET Standard 2.1, 2.0 And.NET 4.8+ 8 | 9 | * We only remove support for target frameworks on major version changes, to avoid breaking changes for the consumers. 10 | 11 | ## Testing 12 | 13 | Testing with old frameworks which are not supported anymore is getting painful, we therefore only test with frameworks which are still in active support. 14 | 15 | ## Next removal 16 | 17 | Funcky 4 will remove the following target frameworks: 18 | 19 | * net7.0 20 | * net6.0 21 | * net5.0 22 | * netcoreapp3.1 23 | -------------------------------------------------------------------------------- /Website/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Open Sans'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('./fonts/open-sans/OpenSans-Regular.woff2') format('woff2'); 7 | } 8 | 9 | @font-face{ 10 | font-family: 'Open Sans'; 11 | font-weight: 700; 12 | font-style: normal; 13 | font-stretch: normal; 14 | src: url('./fonts/open-sans/OpenSans-Bold.woff2') format('woff2'); 15 | } 16 | 17 | @font-face{ 18 | font-family: 'Source Code Pro'; 19 | font-weight: 400; 20 | font-style: normal; 21 | font-stretch: normal; 22 | src: url('./fonts/source-code-pro/SourceCodePro-Regular.ttf.woff2') format('woff2'); 23 | } 24 | 25 | @font-face { 26 | font-family: 'Vollkorn'; 27 | font-weight: 700; 28 | font-style: normal; 29 | font-stretch: normal; 30 | src: url('./fonts/vollkorn/Vollkorn-Bold.woff2') format('woff2'); 31 | } 32 | -------------------------------------------------------------------------------- /Website/fonts/open-sans/OpenSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Website/fonts/open-sans/OpenSans-Bold.woff2 -------------------------------------------------------------------------------- /Website/fonts/open-sans/OpenSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Website/fonts/open-sans/OpenSans-Regular.woff2 -------------------------------------------------------------------------------- /Website/fonts/source-code-pro/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Website/fonts/source-code-pro/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /Website/fonts/vollkorn/Vollkorn-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyadic/funcky/3e9ebca297c22144920f88e14962ce01934bfdc6/Website/fonts/vollkorn/Vollkorn-Bold.woff2 -------------------------------------------------------------------------------- /Website/icons/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Website/icons/diamond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Website/icons/package.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Website/icons/puzzle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Website/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirecting to {{Destination}} 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Website/tabs.js: -------------------------------------------------------------------------------- 1 | const panels = [...document.querySelectorAll('[role=tabpanel]')] 2 | const tabs = [...document.querySelectorAll('[role=tab]')] 3 | 4 | function updateActiveTab(activeTab) { 5 | const panel = document.getElementById(activeTab.getAttribute('aria-controls')) 6 | panels.forEach(p => p === panel ? p.classList.add('-active') : p.classList.remove('-active')) 7 | tabs.forEach(t => { t.setAttribute('aria-expanded', t === activeTab); t.setAttribute('aria-active', t === activeTab) }) 8 | } 9 | 10 | updateActiveTab(tabs.filter(t => t.hasAttribute('data-default-tab'))[0]) 11 | 12 | tabs.forEach(tab => tab.addEventListener('click', _ => updateActiveTab(tab))) 13 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.200", 4 | "rollForward": "feature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["*.DotSettings"] 3 | 4 | [default] 5 | extend-ignore-re = [ 6 | # Ignore the signature block in the `install.ps1` script 7 | "(?s)(#|//)\\s*SIG # Begin signature block.*?\\n\\s*(#|//)\\s*SIG # End signature block", 8 | # Line ignore with trailing `# spellchecker:disable-line` 9 | "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", 10 | # Line block with `# spellchecker:` 11 | "(?s)(