|
| 1 | + |
| 2 | +# SharpX |
| 3 | + |
| 4 | + |
| 5 | + |
| 6 | +SharpX is derived from [CSharpx](https://github.com/gsscoder/csharpx) 2.8.0-rc.2 (_which was practically a stable_) and [RailwaySharp](https://github.com/gsscoder/railwaysharp) 1.2.2. While both projects were meant mainly for source inclusion, SharpX is designed to be pulled from [NuGet](https://www.nuget.org/). |
| 7 | + |
| 8 | +The library contains functional types and other utilities, following _don't reinvent the wheel_ philosophy. This project was inspired by [Real-World Functional Programming](https://www.amazon.com/Real-World-Functional-Programming-Tomas-Petricek/dp/1933988924/ref=sr_1_1?keywords=Real-World+Functional+Programming&qid=1580118924&s=books&sr=1-1) and includes code from [MoreLINQ](https://github.com/morelinq/MoreLINQ). |
| 9 | + |
| 10 | +## Compatibility |
| 11 | + |
| 12 | +SharpX is almost fully compatible with projects using previous libraries. CSharpx changed symbols are listed below. |
| 13 | + |
| 14 | +- `StringExtensions::StripMl` renamed as `StripML`. |
| 15 | +- `ResultType` renamed as `OutcomeType`. |
| 16 | +- `Result` renamed as `Outcome`. |
| 17 | + |
| 18 | +## Targets |
| 19 | + |
| 20 | +- .NET Standard 2.0 |
| 21 | +- .NET Core 3.1 |
| 22 | +- .NET 5.0 |
| 23 | + |
| 24 | +## Install via NuGet |
| 25 | + |
| 26 | +If you prefer, you can install it via NuGet: |
| 27 | + |
| 28 | +```sh |
| 29 | +$ dotnet add package SharpX --version 1.0.0 |
| 30 | + Determining projects to restore... |
| 31 | + ... |
| 32 | +``` |
| 33 | + |
| 34 | +## Versions |
| 35 | + |
| 36 | +- In `develop` branch source code may differ also from latest preview. |
| 37 | +- The letest version on NuGet is [1.0.0](https://www.nuget.org/packages/SharpX/1.0.0). |
| 38 | +- The latest stable version on NuGet is [1.0.0](https://www.nuget.org/packages/SharpX/1.0.0). |
| 39 | + |
| 40 | +## [Maybe] |
| 41 | + |
| 42 | +- Encapsulates an optional value that can contain a value or being empty. |
| 43 | +- Similar to F# `'T option` / Haskell `data Maybe a = Just a | Nothing` type. |
| 44 | + |
| 45 | +```csharp |
| 46 | +var greet = true; |
| 47 | +var value = greet ? "world".ToMaybe() : Maybe.Nothing<string>(); |
| 48 | +value.Match( |
| 49 | + who => Console.WriteLine($"hello {who}!"), |
| 50 | + () => Environment.Exit(1)); |
| 51 | +``` |
| 52 | + |
| 53 | +- Supports LINQ syntax: |
| 54 | + |
| 55 | +```csharp |
| 56 | +var result1 = Maybe.Just(30); |
| 57 | +var result2 = Maybe.Just(10); |
| 58 | +var result3 = Maybe.Just(2); |
| 59 | + |
| 60 | +var sum = from r1 in result1 |
| 61 | + from r2 in result2 |
| 62 | + where r1 > 0 |
| 63 | + select r1 - r2 into temp |
| 64 | + from r3 in result3 |
| 65 | + select temp * r3; |
| 66 | + |
| 67 | +var value = sum.FromJust(); // outcome: 40 |
| 68 | +``` |
| 69 | + |
| 70 | +## Either |
| 71 | + |
| 72 | +- Represents a value that can contain either a value or an error. |
| 73 | +- Similar to Haskell `data Either a b = Left a | Right b` type. |
| 74 | +- Similar also to F# `Choice<'T, 'U>`. |
| 75 | +- Like in Haskell the convention is to let `Right` case hold the value and `Left` keep track of error or similar data. |
| 76 | +- If you want a more complete implementation of this kind of types, consider using `Result`. |
| 77 | + |
| 78 | +## Result |
| 79 | + |
| 80 | +This type was originally present in RailwaySharp. Check the test project to see a more complete usage example. |
| 81 | + |
| 82 | +``` csharp |
| 83 | +public static Result<Request, string> ValidateInput(Request input) |
| 84 | +{ |
| 85 | + if (input.Name == string.Empty) { |
| 86 | + return Result<Request, string>.FailWith("Name must not be blank"); |
| 87 | + } |
| 88 | + if (input.EMail == string.Empty) { |
| 89 | + return Result<Request, string>.FailWith("Email must not be blank"); |
| 90 | + } |
| 91 | + return Result<Request, string>.Succeed(input); |
| 92 | +} |
| 93 | + |
| 94 | +var request = new Request { Name = "Giacomo", EMail = "[email protected]" }; |
| 95 | +var result = Validation.ValidateInput(request); |
| 96 | +result.Match( |
| 97 | + (x, msgs) => { Logic.SendMail(x.EMail); }, |
| 98 | + msgs => { Logic.HandleFailure(msgs) }); |
| 99 | +``` |
| 100 | + |
| 101 | +## Outcome |
| 102 | + |
| 103 | +- Represents a value that can be a success or a failure in form of a type that can contains a custom error message and optionally an exception. |
| 104 | + |
| 105 | +```csharp |
| 106 | +Outcome ValidateArtifact(Artifact artifact) |
| 107 | +{ |
| 108 | + try { |
| 109 | + artifact = ArtifactManager.Load(artifact.Path); |
| 110 | + } |
| 111 | + catch (IOException e) { |
| 112 | + return Result.Failure($"Unable to load artifcat {path}:\n{e.Message}", exception: e); |
| 113 | + } |
| 114 | + return artifact.CheckIntegrity() switch { |
| 115 | + Integrity.Healthy => Outcome.Success(), |
| 116 | + _ => Outcome.Failure("Artifact integrity is compromised") |
| 117 | + }; |
| 118 | +} |
| 119 | + |
| 120 | +if (ValidateArtifact(artifact).MatchFailure(out Error error)) { |
| 121 | + //Error::ToString creates a string with message and exception details |
| 122 | + _logger.LogError(error.Exception.FromJust(), error.ToString()); |
| 123 | + Environment.Exit(1); |
| 124 | +} |
| 125 | +// do something useful with artifact |
| 126 | +``` |
| 127 | + |
| 128 | +## Unit |
| 129 | + |
| 130 | +- `Unit` is similar to `void` but, since it's a *real* type. `void` is not, in fact you can't declare a variable of that type. `Unit` allows the use functions without a result in a computation (*functional style*). It's essentially **F#** `unit` and **Haskell** `Unit`. |
| 131 | + |
| 132 | +```csharp |
| 133 | +// prints each word and returns 0 to the shell |
| 134 | +static int Main(string[] args) |
| 135 | +{ |
| 136 | + var sentence = "this is a sentence"; |
| 137 | + return (from _ in |
| 138 | + from word in sentence.Split() |
| 139 | + select Unit.Do(() => Console.WriteLine(word)) |
| 140 | + select 0).Distinct().Single(); |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +## CryptoRandom |
| 145 | + |
| 146 | +A thread safe random number generator based on [this code](https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/september/net-matters-tales-from-the-cryptorandom) compatible with `System.Random` interface. |
| 147 | + |
| 148 | +```csharp |
| 149 | +Random random = new CryptoRandom(); |
| 150 | + |
| 151 | +var int = randome.Next(9); // outcome: 3 |
| 152 | +``` |
| 153 | + |
| 154 | +## FSharpResultExtensions |
| 155 | + |
| 156 | +- Convenient extension methods to consume `FSharpResult<T, TError>` in simple and functional for other **.NET** languages. |
| 157 | + |
| 158 | +```csharp |
| 159 | +// pattern match like |
| 160 | +var result = Query.GetStockQuote("ORCL"); |
| 161 | +result.Match( |
| 162 | + quote => Console.WriteLine($"Price: {quote.Price}"), |
| 163 | + error => Console.WriteLine($"Trouble: {error}")); |
| 164 | +// mapping |
| 165 | +var result = Query.GetIndex(".DJI"); |
| 166 | +result.Map( |
| 167 | + quote => CurrencyConverter.Change(quote.Price, "$", "€")); |
| 168 | +``` |
| 169 | + |
| 170 | +- Blog [post](https://gsscoder.github.io/consuming-fsharp-results-in-c/) about it. |
| 171 | + |
| 172 | +## StringExtensions |
| 173 | + |
| 174 | +- General purpose and randomness string manipulation extensions. |
| 175 | + |
| 176 | +```csharp |
| 177 | +Console.WriteLine( |
| 178 | + "\t[hello\world@\t".Sanitize(normalizeWhiteSpace: true)); |
| 179 | +// outcome: ' hello world ' |
| 180 | +
|
| 181 | +Console.WriteLine( |
| 182 | + "I want to change a word".ApplyAt(4, word => word.Mangle())); |
| 183 | +// outcome like: 'I want to change &a word' |
| 184 | +``` |
| 185 | + |
| 186 | +## EnumerableExtensions |
| 187 | + |
| 188 | +- Most useful extension methods from [MoreLINQ](https://github.com/morelinq/MoreLINQ). |
| 189 | +- Some of these reimplemnted (e.g. `Choose` using `Maybe`): |
| 190 | +- **LINQ** `...OrDefault` implemented with `Maybe` type as return value. |
| 191 | + |
| 192 | +```csharp |
| 193 | +var numbers = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| 194 | +var evens = numbers.Choose(x => x % 2 == 0 |
| 195 | + ? Maybe.Just(x) |
| 196 | + : Maybe.Nothing<int>()); |
| 197 | +// outcome: {0, 2, 4, 6, 8} |
| 198 | +``` |
| 199 | + |
| 200 | +- With other useful methods too: |
| 201 | + |
| 202 | +```CSharp |
| 203 | +var sequence = new int[] {0, 1, 2, 3, 4}.Intersperse(5); |
| 204 | +// outcome: {0, 5, 1, 5, 2, 5, 3, 5, 4} |
| 205 | +var element = sequence.Choice(); |
| 206 | +// will choose a random element |
| 207 | +var sequence = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.ChunkBySize(3); |
| 208 | +// outcome: { [0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10] } |
| 209 | +var maybeFirst = new int[] {0, 1, 2}.FirstOrNothing(x => x == 1) |
| 210 | +// outcome: Just(1) |
| 211 | +``` |
| 212 | + |
| 213 | +## Icon |
| 214 | + |
| 215 | +[Tool](https://thenounproject.com/search/?q=tool&i=3902696) icon designed by Cattaleeya Thongsriphong from [The Noun Project](https://thenounproject.com/) |
0 commit comments