├── .editorconfig ├── .gitattributes ├── .gitignore ├── COPYING.txt ├── Optuple.sln ├── README.md ├── appveyor.yml ├── build.cmd ├── build.sh ├── global.json ├── lic └── Optional │ └── LICENSE ├── pack.cmd ├── pack.sh ├── src ├── Enumerable.cs ├── Option.cs ├── Optional.cs ├── Optuple.csproj ├── Public.cs └── Regex.cs ├── test.cmd ├── test.sh └── tests ├── BreakingAction.cs ├── BreakingCollection.cs ├── BreakingFunc.cs ├── BreakingList.cs ├── BreakingReadOnlyCollection.cs ├── BreakingReadOnlyList.cs ├── BreakingSequence.cs ├── EnumerableTests.cs ├── OptionTests.cs ├── OptionalTests.cs ├── Optuple.Tests.csproj └── RegexTests.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 11 | indent_size = 2 12 | 13 | [*.{sln}] 14 | indent_style = tab 15 | 16 | [*.{json,yml}] 17 | indent_size = 2 18 | 19 | [*.{cs,tt}] 20 | charset = utf-8 21 | indent_style = space 22 | indent_size = 4 23 | max_line_length = 100 24 | 25 | [*.cs] 26 | # Prefer "var" everywhere 27 | csharp_style_var_for_built_in_types = true:suggestion 28 | csharp_style_var_when_type_is_apparent = true:suggestion 29 | csharp_style_var_elsewhere = true:suggestion 30 | 31 | # Prefer method-like constructs to have a block body 32 | csharp_style_expression_bodied_methods = false:none 33 | csharp_style_expression_bodied_constructors = false:none 34 | csharp_style_expression_bodied_operators = false:none 35 | 36 | # Prefer property-like constructs to have an expression-body 37 | csharp_style_expression_bodied_properties = true:none 38 | csharp_style_expression_bodied_indexers = true:none 39 | csharp_style_expression_bodied_accessors = true:none 40 | 41 | # Suggest more modern language features when available 42 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 43 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 44 | csharp_style_inlined_variable_declaration = true:suggestion 45 | csharp_style_throw_expression = true:suggestion 46 | csharp_style_conditional_delegate_call = true:suggestion 47 | csharp_prefer_simple_default_expression = true:suggestion 48 | 49 | # Spacing 50 | csharp_space_after_cast = false 51 | csharp_space_after_keywords_in_control_flow_statements = true 52 | csharp_space_between_method_declaration_parameter_list_parentheses = false 53 | 54 | # Wrapping 55 | csharp_preserve_single_line_statements = true 56 | csharp_preserve_single_line_blocks = true 57 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.sh eol=lf 5 | 6 | # Custom for Visual Studio 7 | *.cs diff=csharp 8 | 9 | # Standard to msysgit 10 | *.doc diff=astextplain 11 | *.DOC diff=astextplain 12 | *.docx diff=astextplain 13 | *.DOCX diff=astextplain 14 | *.dot diff=astextplain 15 | *.DOT diff=astextplain 16 | *.pdf diff=astextplain 17 | *.PDF diff=astextplain 18 | *.rtf diff=astextplain 19 | *.RTF diff=astextplain 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/linux,macos,windows,visualstudio,visualstudiocode 2 | 3 | ### Linux ### 4 | *~ 5 | 6 | # temporary files which can be created if a process still has a handle open of a deleted file 7 | .fuse_hidden* 8 | 9 | # KDE directory preferences 10 | .directory 11 | 12 | # Linux trash folder which might appear on any partition or disk 13 | .Trash-* 14 | 15 | # .nfs files are created when an open file is removed but is still being accessed 16 | .nfs* 17 | 18 | ### macOS ### 19 | *.DS_Store 20 | .AppleDouble 21 | .LSOverride 22 | 23 | # Icon must end with two \r 24 | Icon 25 | 26 | # Thumbnails 27 | ._* 28 | 29 | # Files that might appear in the root of a volume 30 | .DocumentRevisions-V100 31 | .fseventsd 32 | .Spotlight-V100 33 | .TemporaryItems 34 | .Trashes 35 | .VolumeIcon.icns 36 | .com.apple.timemachine.donotpresent 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | ### VisualStudioCode ### 46 | .vscode/* 47 | !.vscode/settings.json 48 | !.vscode/tasks.json 49 | !.vscode/launch.json 50 | !.vscode/extensions.json 51 | .history 52 | 53 | ### Windows ### 54 | # Windows thumbnail cache files 55 | Thumbs.db 56 | ehthumbs.db 57 | ehthumbs_vista.db 58 | 59 | # Folder config file 60 | Desktop.ini 61 | 62 | # Recycle Bin used on file shares 63 | $RECYCLE.BIN/ 64 | 65 | # Windows Installer files 66 | *.cab 67 | *.msi 68 | *.msm 69 | *.msp 70 | 71 | # Windows shortcuts 72 | *.lnk 73 | 74 | ### VisualStudio ### 75 | ## Ignore Visual Studio temporary files, build results, and 76 | ## files generated by popular Visual Studio add-ons. 77 | ## 78 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 79 | 80 | # User-specific files 81 | *.suo 82 | *.user 83 | *.userosscache 84 | *.sln.docstates 85 | 86 | # User-specific files (MonoDevelop/Xamarin Studio) 87 | *.userprefs 88 | 89 | # Build results 90 | [Dd]ebug/ 91 | [Dd]ebugPublic/ 92 | [Rr]elease/ 93 | [Rr]eleases/ 94 | x64/ 95 | x86/ 96 | bld/ 97 | [Bb]in/ 98 | [Oo]bj/ 99 | [Ll]og/ 100 | 101 | # Visual Studio 2015 cache/options directory 102 | .vs/ 103 | # Uncomment if you have tasks that create the project's static files in wwwroot 104 | #wwwroot/ 105 | 106 | # MSTest test Results 107 | [Tt]est[Rr]esult*/ 108 | [Bb]uild[Ll]og.* 109 | 110 | # NUNIT 111 | *.VisualState.xml 112 | TestResult.xml 113 | 114 | # Build Results of an ATL Project 115 | [Dd]ebugPS/ 116 | [Rr]eleasePS/ 117 | dlldata.c 118 | 119 | # .NET Core 120 | project.lock.json 121 | project.fragment.lock.json 122 | artifacts/ 123 | **/Properties/launchSettings.json 124 | 125 | *_i.c 126 | *_p.c 127 | *_i.h 128 | *.ilk 129 | *.meta 130 | *.obj 131 | *.pch 132 | *.pdb 133 | *.pgc 134 | *.pgd 135 | *.rsp 136 | *.sbr 137 | *.tlb 138 | *.tli 139 | *.tlh 140 | *.tmp 141 | *.tmp_proj 142 | *.log 143 | *.vspscc 144 | *.vssscc 145 | .builds 146 | *.pidb 147 | *.svclog 148 | *.scc 149 | 150 | # Chutzpah Test files 151 | _Chutzpah* 152 | 153 | # Visual C++ cache files 154 | ipch/ 155 | *.aps 156 | *.ncb 157 | *.opendb 158 | *.opensdf 159 | *.sdf 160 | *.cachefile 161 | *.VC.db 162 | *.VC.VC.opendb 163 | 164 | # Visual Studio profiler 165 | *.psess 166 | *.vsp 167 | *.vspx 168 | *.sap 169 | 170 | # TFS 2012 Local Workspace 171 | $tf/ 172 | 173 | # Guidance Automation Toolkit 174 | *.gpState 175 | 176 | # ReSharper is a .NET coding add-in 177 | _ReSharper*/ 178 | *.[Rr]e[Ss]harper 179 | *.DotSettings.user 180 | 181 | # JustCode is a .NET coding add-in 182 | .JustCode 183 | 184 | # TeamCity is a build add-in 185 | _TeamCity* 186 | 187 | # DotCover is a Code Coverage Tool 188 | *.dotCover 189 | 190 | # Visual Studio code coverage results 191 | *.coverage 192 | *.coveragexml 193 | 194 | # NCrunch 195 | _NCrunch_* 196 | .*crunch*.local.xml 197 | nCrunchTemp_* 198 | 199 | # MightyMoose 200 | *.mm.* 201 | AutoTest.Net/ 202 | 203 | # Web workbench (sass) 204 | .sass-cache/ 205 | 206 | # Installshield output folder 207 | [Ee]xpress/ 208 | 209 | # DocProject is a documentation generator add-in 210 | DocProject/buildhelp/ 211 | DocProject/Help/*.HxT 212 | DocProject/Help/*.HxC 213 | DocProject/Help/*.hhc 214 | DocProject/Help/*.hhk 215 | DocProject/Help/*.hhp 216 | DocProject/Help/Html2 217 | DocProject/Help/html 218 | 219 | # Click-Once directory 220 | publish/ 221 | 222 | # Publish Web Output 223 | *.[Pp]ublish.xml 224 | *.azurePubxml 225 | # TODO: Uncomment the next line to ignore your web deploy settings. 226 | # By default, sensitive information, such as encrypted password 227 | # should be stored in the .pubxml.user file. 228 | #*.pubxml 229 | *.pubxml.user 230 | *.publishproj 231 | 232 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 233 | # checkin your Azure Web App publish settings, but sensitive information contained 234 | # in these scripts will be unencrypted 235 | PublishScripts/ 236 | 237 | # NuGet Packages 238 | *.nupkg 239 | # The packages folder can be ignored because of Package Restore 240 | **/packages/* 241 | # except build/, which is used as an MSBuild target. 242 | !**/packages/build/ 243 | # Uncomment if necessary however generally it will be regenerated when needed 244 | #!**/packages/repositories.config 245 | # NuGet v3's project.json files produces more ignorable files 246 | *.nuget.props 247 | *.nuget.targets 248 | 249 | # Microsoft Azure Build Output 250 | csx/ 251 | *.build.csdef 252 | 253 | # Microsoft Azure Emulator 254 | ecf/ 255 | rcf/ 256 | 257 | # Windows Store app package directories and files 258 | AppPackages/ 259 | BundleArtifacts/ 260 | Package.StoreAssociation.xml 261 | _pkginfo.txt 262 | 263 | # Visual Studio cache files 264 | # files ending in .cache can be ignored 265 | *.[Cc]ache 266 | # but keep track of directories ending in .cache 267 | !*.[Cc]ache/ 268 | 269 | # Others 270 | ClientBin/ 271 | ~$* 272 | *.dbmdl 273 | *.dbproj.schemaview 274 | *.jfm 275 | *.pfx 276 | *.publishsettings 277 | orleans.codegen.cs 278 | 279 | # Since there are multiple workflows, uncomment next line to ignore bower_components 280 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 281 | #bower_components/ 282 | 283 | # RIA/Silverlight projects 284 | Generated_Code/ 285 | 286 | # Backup & report files from converting an old project file 287 | # to a newer Visual Studio version. Backup files are not needed, 288 | # because we have git ;-) 289 | _UpgradeReport_Files/ 290 | Backup*/ 291 | UpgradeLog*.XML 292 | UpgradeLog*.htm 293 | 294 | # SQL Server files 295 | *.mdf 296 | *.ldf 297 | *.ndf 298 | 299 | # Business Intelligence projects 300 | *.rdl.data 301 | *.bim.layout 302 | *.bim_*.settings 303 | 304 | # Microsoft Fakes 305 | FakesAssemblies/ 306 | 307 | # GhostDoc plugin setting file 308 | *.GhostDoc.xml 309 | 310 | # Node.js Tools for Visual Studio 311 | .ntvs_analysis.dat 312 | node_modules/ 313 | 314 | # Typescript v1 declaration files 315 | typings/ 316 | 317 | # Visual Studio 6 build log 318 | *.plg 319 | 320 | # Visual Studio 6 workspace options file 321 | *.opt 322 | 323 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 324 | *.vbw 325 | 326 | # Visual Studio LightSwitch build output 327 | **/*.HTMLClient/GeneratedArtifacts 328 | **/*.DesktopClient/GeneratedArtifacts 329 | **/*.DesktopClient/ModelManifest.xml 330 | **/*.Server/GeneratedArtifacts 331 | **/*.Server/ModelManifest.xml 332 | _Pvt_Extensions 333 | 334 | # Paket dependency manager 335 | .paket/paket.exe 336 | paket-files/ 337 | 338 | # FAKE - F# Make 339 | .fake/ 340 | 341 | # JetBrains Rider 342 | .idea/ 343 | *.sln.iml 344 | 345 | # CodeRush 346 | .cr/ 347 | 348 | # Python Tools for Visual Studio (PTVS) 349 | __pycache__/ 350 | *.pyc 351 | 352 | # Cake - Uncomment if you are using it 353 | # tools/** 354 | # !tools/packages.config 355 | 356 | # Telerik's JustMock configuration file 357 | *.jmconfig 358 | 359 | # BizTalk build output 360 | *.btp.cs 361 | *.btm.cs 362 | *.odx.cs 363 | *.xsd.cs 364 | 365 | ### VisualStudio Patch ### 366 | # By default, sensitive information, such as encrypted password 367 | # should be stored in the .pubxml.user file. 368 | 369 | # End of https://www.gitignore.io/api/linux,macos,windows,visualstudio,visualstudiocode 370 | 371 | tests/coverage.* 372 | BenchmarkDotNet.Artifacts/ 373 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /Optuple.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29209.62 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Optuple", "src\Optuple.csproj", "{907B474E-A775-4F31-A206-45409DA557EE}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{64F18457-F1E4-49B3-8C6A-947A3A14EAEA}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | appveyor.yml = appveyor.yml 12 | build.cmd = build.cmd 13 | build.sh = build.sh 14 | COPYING.txt = COPYING.txt 15 | global.json = global.json 16 | pack.cmd = pack.cmd 17 | pack.sh = pack.sh 18 | README.md = README.md 19 | test.cmd = test.cmd 20 | test.sh = test.sh 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Optuple.Tests", "tests\Optuple.Tests.csproj", "{8EF646A4-75B3-42CD-B93F-212E80CCDF2A}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Debug|x64 = Debug|x64 29 | Debug|x86 = Debug|x86 30 | Release|Any CPU = Release|Any CPU 31 | Release|x64 = Release|x64 32 | Release|x86 = Release|x86 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|x64.Build.0 = Debug|Any CPU 39 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {907B474E-A775-4F31-A206-45409DA557EE}.Debug|x86.Build.0 = Debug|Any CPU 41 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|x64.ActiveCfg = Release|Any CPU 44 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|x64.Build.0 = Release|Any CPU 45 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|x86.ActiveCfg = Release|Any CPU 46 | {907B474E-A775-4F31-A206-45409DA557EE}.Release|x86.Build.0 = Release|Any CPU 47 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|x64.ActiveCfg = Debug|Any CPU 50 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|x64.Build.0 = Debug|Any CPU 51 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|x86.ActiveCfg = Debug|Any CPU 52 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Debug|x86.Build.0 = Debug|Any CPU 53 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|x64.ActiveCfg = Release|Any CPU 56 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|x64.Build.0 = Release|Any CPU 57 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|x86.ActiveCfg = Release|Any CPU 58 | {8EF646A4-75B3-42CD-B93F-212E80CCDF2A}.Release|x86.Build.0 = Release|Any CPU 59 | EndGlobalSection 60 | GlobalSection(SolutionProperties) = preSolution 61 | HideSolutionNode = FALSE 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {6AE6D50D-FED1-42E1-960D-CAF4ADF20B3E} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optuple 2 | 3 | Optuple is a .NET Standard library that enables a tuple of Boolean and some 4 | type (`T`), i.e. `(bool, T)`, to have the same semantics as an option type 5 | found in most functional languages. 6 | 7 | An option type is a discriminated union that either represents the absence of 8 | a value (_none_) or the value that's present (_some_). For example, F# has 9 | such a type that is defined as: 10 | 11 | ```f# 12 | type Option = | None | Some of T 13 | ``` 14 | 15 | Optuple, however, does not define any such type. Instead it primarily supplies 16 | extension methods for any `(bool, T)` (like `Match`, `Map` and more) to be 17 | treated like an option type. Suppose a value `x` of type `T`, then 18 | `(true, x)` will be treated like `Some x` and `(false, _)` will 19 | be treated like `None`. Note that in the _none case_, when the first 20 | element is `false`, Optuple completely ignores and disregards the second 21 | element. 22 | 23 | A library that wants to expose optional values needs no dependency on Optuple. 24 | It can just expose `(bool, T)` for some type `T`. The client of the library 25 | can use Optuple independently to get “optional semantics”. 26 | 27 | 28 | ## Usage 29 | 30 | ### Using the library 31 | 32 | To use Optuple simply import the following namespace: 33 | 34 | ```c# 35 | using Optuple; 36 | ``` 37 | 38 | An auxiliary namespace is also provided: 39 | 40 | ```c# 41 | using Optuple.Linq; // LINQ query syntax support 42 | ``` 43 | 44 | 45 | ### Creating optional values 46 | 47 | The most basic way to create optional values is to use the static `Option` 48 | class: 49 | 50 | ```c# 51 | var none = Option.None(); 52 | var some = Option.Some(42); 53 | ``` 54 | 55 | Similarly, a more general extension method is provided, allowing a specified 56 | predicate: 57 | 58 | ```c# 59 | string str = "foo"; 60 | var none = Option.SomeWhen(str, s => s == "bar"); // Return None if predicate is violated 61 | var none = Option.NoneWhen(str, s => s == "foo"); // Return None if predicate is satisfied 62 | ``` 63 | 64 | Clearly, optional values are conceptually quite similar to nullables. Hence, a 65 | method is provided to convert a nullable into an optional value: 66 | 67 | ```c# 68 | int? nullableWithoutValue = null; 69 | int? nullableWithValue = 2; 70 | var none = nullableWithoutValue.ToOption(); 71 | var some = nullableWithValue.ToOption(); 72 | ``` 73 | 74 | ### Retrieving values 75 | 76 | When retrieving values, Optuple forces you to consider both cases (that is 77 | if a value is present or not). 78 | 79 | Firstly, it is possible to check if a value is actually present: 80 | 81 | ```c# 82 | var isSome = option.IsSome(); //returns true if a value is present 83 | var isNone = option.IsNone(); //returns true if a value is not present 84 | ``` 85 | 86 | If you want to check if an option `option` satisfies some predicate, you can 87 | use the`Exists` method. 88 | 89 | ```c# 90 | var isGreaterThanHundred = option.Exists(val => val > 100); 91 | ``` 92 | 93 | The most basic way to retrieve a value from an `Option` is the following: 94 | 95 | ```c# 96 | // Returns the value if present, or otherwise an alternative value (42) 97 | var value = option.Or(42); 98 | ``` 99 | 100 | Similarly, the `OrDefault` function simply retrieves the default value for 101 | a given type: 102 | 103 | ```c# 104 | var none = Option.None(); 105 | var value = none.OrDefault(); // Value 0 106 | ``` 107 | 108 | ```c# 109 | var none = Option.None(); 110 | var value = none.OrDefault(); // null 111 | ``` 112 | 113 | 114 | In more elaborate scenarios, the `Match` method evaluates a specified 115 | function: 116 | 117 | ```c# 118 | // Evaluates one of the provided functions and returns the result 119 | var value = option.Match(x => x + 1, () => 42); 120 | 121 | // Or written in a more functional style (pattern matching) 122 | var value = option.Match( 123 | some: x => x + 1, 124 | none: () => 42 125 | ); 126 | ``` 127 | 128 | There is a similar `Match` function to simply induce side-effects: 129 | 130 | ```c# 131 | // Evaluates one of the provided actions 132 | option.Match(x => Console.WriteLine(x), () => Console.WriteLine(42)); 133 | 134 | // Or pattern matching style as before 135 | option.Match( 136 | some: x => Console.WriteLine(x), 137 | none: () => Console.WriteLine(42) 138 | ); 139 | ``` 140 | 141 | ### Transforming and filtering values 142 | 143 | A few extension methods are provided to safely manipulate optional values. 144 | 145 | The `Map` function transforms the inner value of an option. If no value is 146 | present none is simply propagated: 147 | 148 | ```c# 149 | var none = Option.None(); 150 | var stillNone = none.Map(x => x + 10); 151 | 152 | var some = Option.Some(42); 153 | var somePlus10 = some.Map(x => x + 10); 154 | ``` 155 | 156 | Finally, it is possible to perform filtering. The `Filter` function returns 157 | none, if the specified predicate is not satisfied. If the option is already 158 | none, it is simply returned as is: 159 | 160 | ```c# 161 | var none = Option.None(); 162 | var stillNone = none.Filter(x => x > 10); 163 | 164 | var some = Option.Some(10); 165 | var stillSome = some.Filter(x => x == 10); 166 | var none = some.Filter(x => x != 10); 167 | ``` 168 | 169 | ### Helpers as global methods 170 | 171 | If you [statically import][static-using] `OptionModule`: 172 | 173 | ```c# 174 | using static Optuple.OptionModule; 175 | ``` 176 | 177 | it will make the following common methods available for use without type 178 | qualification: 179 | 180 | - `Some` 181 | - `None<>` 182 | - `SomeWhen` 183 | - `NoneWhen` 184 | 185 | This permits you to, for example, simply write `Some(42)` and `None()` 186 | instead of `Option.Some(42)` and `None()`, respectively. 187 | 188 | ### Enumerating options 189 | 190 | [comment]: # (Move somewhere?!) 191 | 192 | Although options deliberately don't act as enumerables, you can easily convert 193 | an option to an enumerable by calling the `ToEnumerable()` method: 194 | 195 | ```c# 196 | var enumerable = option.ToEnumerable(); 197 | ``` 198 | 199 | By importing the `Optuple.Collections` namespace, you also get the following 200 | extension methods for sequences (`IEnumerable<>`) that are like their LINQ 201 | counterparts but return an option: 202 | 203 | - `FirstOrNone`: like [`FirstOrDefault`][FirstOrDefault] but returns an option 204 | - `LastOrNone`: like [`LastOrDefault`][LastOrDefault] but returns an option 205 | - `SingleOrNone`: like [`SingleOrDefault`][SingleOrDefault] but returns an option 206 | 207 | [FirstOrDefault]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.firstordefault 208 | [LastOrDefault]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.lastordefault 209 | [SingleOrDefault]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault 210 | 211 | Then there is: 212 | 213 | - `Filter`, which given a sequence of options, will return a sequence of _x_ 214 | values from those options in the original sequence that are _some x_. 215 | - `ListAll`, which given a sequence of options, will return _some list of 216 | x_ if all options in the original sequence are _some x_; otherwise it returns 217 | _none list_. 218 | 219 | ### Working with LINQ query syntax 220 | 221 | Optuple supports LINQ query syntax, to make the above transformations 222 | somewhat cleaner. 223 | 224 | To use LINQ query syntax you must import the following namespace: 225 | 226 | ```c# 227 | using Optuple.Linq; 228 | ``` 229 | 230 | This allows you to do fancy stuff such as: 231 | 232 | ```c# 233 | var personWithGreenHair = 234 | from person in FindPersonById(10) 235 | from hairstyle in GetHairstyle(person) 236 | from color in ParseStringToColor("green") 237 | where hairstyle.Color == color 238 | select person; 239 | ``` 240 | 241 | In general, this closely resembles a sequence of calls to `FlatMap` and 242 | `Filter`. However, using query syntax can be a lot easier to read in complex 243 | cases. 244 | 245 | ### Equivalence and comparison 246 | 247 | Two optional values are equal if the following is satisfied: 248 | 249 | * The two options have the same type 250 | * Both are none, both contain null values, or the contained values are equal 251 | 252 | 253 | --- 254 | 255 | Credit: A large portion of this documentation was dervied from that of the 256 | project [Optional]. Thank you, [Nils Lück][nlkl]! 257 | 258 | 259 | [Optional]: https://www.nuget.org/packages/Optional/ 260 | [nlkl]: https://github.com/nlkl 261 | [static-using]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-static 262 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | image: 3 | - Visual Studio 2019 4 | - Ubuntu 5 | skip_commits: 6 | files: 7 | - '*.md' 8 | - '*.txt' 9 | environment: 10 | DOTNET_CLI_TELEMETRY_OPTOUT: true 11 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 12 | skip_tags: true 13 | install: 14 | - cmd: curl -O https://dot.net/v1/dotnet-install.ps1 15 | - sh: curl -O https://dot.net/v1/dotnet-install.sh 16 | - sh: chmod +x dotnet-install.sh 17 | - ps: | 18 | $sdkVersion = (type .\global.json | ConvertFrom-Json).sdk.version 19 | if ($isWindows) { ./dotnet-install.ps1 -Version $sdkVersion } 20 | if ($isLinux) { ./dotnet-install.sh --version $sdkVersion } 21 | before_build: 22 | - dotnet --info 23 | build_script: 24 | - ps: >- 25 | $id = $env:APPVEYOR_REPO_COMMIT_TIMESTAMP -replace '([-:]|\.0+Z)', '' 26 | 27 | $id = $id.Substring(0, 13) 28 | 29 | if ($isWindows) { .\pack.cmd ci-$id } else { ./pack.sh ci-$id } 30 | test_script: 31 | - cmd: test.cmd 32 | - sh: ./test.sh 33 | - sh: # revert to following post merge of PR codecov/codecov-bash#138 34 | - sh: # curl -s https://codecov.io/bash > codecov 35 | - sh: curl -s https://raw.githubusercontent.com/codecov/codecov-bash/14662d32a4862918c31efafe4b450de1305a38e1/codecov > codecov 36 | - sh: chmod +x codecov 37 | - sh: ./codecov -f ./tests/coverage.opencover.xml 38 | artifacts: 39 | - path: dist\*.nupkg 40 | deploy: 41 | - provider: NuGet 42 | server: https://www.myget.org/F/raboof/api/v2/package 43 | api_key: 44 | secure: fhGwXyO35FSshRzs5GWmF1LJTrd1sIqmS/jNCSfO2LfOciuYAKiXuFMYZFGiTAl+ 45 | symbol_server: https://www.myget.org/F/raboof/symbols/api/v2/package 46 | on: 47 | branch: master 48 | notifications: 49 | - provider: Email 50 | to: 51 | - raboof-ci@googlegroups.com 52 | on_build_success: true 53 | on_build_failure: true 54 | on_build_status_changed: false 55 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd "%~dp0" 3 | call :main %* 4 | popd 5 | goto :EOF 6 | 7 | :main 8 | dotnet restore ^ 9 | && dotnet build --no-restore -c Debug ^ 10 | && dotnet build --no-restore -c Release 11 | goto :EOF 12 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | dotnet restore 5 | for c in Debug Release; do 6 | dotnet build --no-restore -c $c 7 | done 8 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "2.2.401" 4 | } 5 | } -------------------------------------------------------------------------------- /lic/Optional/LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atifaziz/Optuple/174c62e4b0de378e7c6acc12a3ffd0bb0d1c7e0f/lic/Optional/LICENSE -------------------------------------------------------------------------------- /pack.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd "%~dp0" 3 | call :main %* 4 | popd 5 | goto :EOF 6 | 7 | :main 8 | setlocal 9 | set VERSION_SUFFIX= 10 | if not "%~1"=="" set VERSION_SUFFIX=--version-suffix %~1 11 | call build ^ 12 | && dotnet pack ^ 13 | --no-build --include-symbols --include-source ^ 14 | -c Release -o ..\dist ^ 15 | %VERSION_SUFFIX% ^ 16 | src 17 | goto :EOF 18 | -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | if [ -n "$1" ]; then 5 | VERSION_SUFFIX="--version-suffix $1" 6 | else 7 | VERSION_SUFFIX= 8 | fi 9 | ./build.sh 10 | dotnet pack \ 11 | --no-build --include-symbols --include-source \ 12 | -c Release -o ../dist \ 13 | $VERSION_SUFFIX \ 14 | src 15 | -------------------------------------------------------------------------------- /src/Enumerable.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2018 Atif Aziz. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Collections 18 | { 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | using static OptionModule; 23 | 24 | static partial class EnumerableExtensions 25 | { 26 | public static IEnumerable Filter(this IEnumerable<(bool HasValue, T Value)> options) 27 | => options == null ? throw new ArgumentNullException(nameof(options)) 28 | : options.SelectMany(Option.ToEnumerable); 29 | 30 | public static (bool HasValue, List List) 31 | ListAll(this IEnumerable<(bool HasValue, T Value)> options) 32 | { 33 | if (options == null) throw new ArgumentNullException(nameof(options)); 34 | 35 | var list = new List(); 36 | foreach (var (some, item) in options) 37 | { 38 | if (!some) 39 | return default; 40 | list.Add(item); 41 | } 42 | return Some(list); 43 | } 44 | 45 | public static (bool HasValue, T Value) 46 | SingleOrNone(this IEnumerable source) 47 | { 48 | switch (source) 49 | { 50 | case null: throw new ArgumentNullException(nameof(source)); 51 | case IList list: return list.Count == 1 ? Some(list[0]) : default; 52 | case IReadOnlyList list: return list.Count == 1 ? Some(list[0]) : default; 53 | default: 54 | { 55 | using (var e = source.GetEnumerator()) 56 | { 57 | var item = e.MoveNext() ? Some(e.Current) : default; 58 | return !e.MoveNext() ? item : default; 59 | } 60 | } 61 | } 62 | } 63 | 64 | public static (bool HasValue, T Value) 65 | SingleOrNone(this IEnumerable source, 66 | Func predicate) => 67 | source.Where(predicate).SingleOrNone(); 68 | 69 | public static (bool HasValue, T Value) 70 | FirstOrNone(this IEnumerable source) 71 | { 72 | switch (source) 73 | { 74 | case null: throw new ArgumentNullException(nameof(source)); 75 | case IList list: return list.Count > 0 ? Some(list[0]) : default; 76 | case IReadOnlyList list: return list.Count > 0 ? Some(list[0]) : default; 77 | default: 78 | { 79 | using (var e = source.GetEnumerator()) 80 | return e.MoveNext() ? Some(e.Current) : default; 81 | } 82 | } 83 | } 84 | 85 | public static (bool HasValue, T Value) 86 | FirstOrNone(this IEnumerable source, 87 | Func predicate) => 88 | source.Where(predicate).FirstOrNone(); 89 | 90 | public static (bool HasValue, T Value) 91 | LastOrNone(this IEnumerable source) 92 | { 93 | switch (source) 94 | { 95 | case null: throw new ArgumentNullException(nameof(source)); 96 | case IList list: return list.Count > 0 ? Some(list[list.Count - 1]) : default; 97 | case IReadOnlyList list: return list.Count > 0 ? Some(list[list.Count - 1]) : default; 98 | default: 99 | { 100 | using (var e = source.GetEnumerator()) 101 | { 102 | if (!e.MoveNext()) 103 | return default; 104 | 105 | var last = e.Current; 106 | while (e.MoveNext()) 107 | last = e.Current; 108 | return Some(last); 109 | } 110 | } 111 | } 112 | } 113 | 114 | public static (bool HasValue, T Value) 115 | LastOrNone(this IEnumerable source, 116 | Func predicate) => 117 | source.Where(predicate).LastOrNone(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Option.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2018 Atif Aziz. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple 18 | { 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | static partial class OptionModule 23 | { 24 | public static (bool HasValue, T Value) Some(T value) => Option.Some(value); 25 | public static (bool HasValue, T Value) None() => Option.None(); 26 | 27 | public static (bool HasValue, T Value) SomeWhen(T value, Func predicate) => 28 | Option.SomeWhen(value, predicate); 29 | 30 | public static (bool HasValue, T Value) NoneWhen(T value, Func predicate) => 31 | Option.NoneWhen(value, predicate); 32 | } 33 | 34 | static partial class Option 35 | { 36 | public static (bool HasValue, T Value) Some(T value) => (true, value); 37 | public static (bool HasValue, T Value) None() => default; 38 | 39 | public static (bool HasValue, T Value) From(bool isSome, T value) => isSome ? Some(value) : None(); 40 | 41 | public static (bool HasValue, T Value) ToOption(this T? value) where T : struct => 42 | value is T x ? Some(x) : None(); 43 | 44 | public static bool IsSome(this (bool HasValue, T Value) option) => option.HasValue; 45 | public static bool IsNone(this (bool HasValue, T Value) option) => !option.HasValue; 46 | 47 | public static (bool HasValue, T Value) SomeWhen(T value, Func predicate) 48 | => predicate == null ? throw new ArgumentNullException(nameof(predicate)) 49 | : predicate(value) ? Some(value) : None(); 50 | 51 | public static (bool HasValue, T Value) NoneWhen(T value, Func predicate) 52 | => predicate == null ? throw new ArgumentNullException(nameof(predicate)) 53 | : predicate(value) ? None() : Some(value); 54 | 55 | public static TResult Match(this (bool HasValue, T Value) option, Func some, Func none) 56 | => some == null ? throw new ArgumentNullException(nameof(some)) 57 | : none == null ? throw new ArgumentNullException(nameof(none)) 58 | : option.IsSome() ? some(option.Value) : none(); 59 | 60 | public static void Match(this (bool HasValue, T Value) option, Action some, Action none) 61 | { 62 | if (some == null) throw new ArgumentNullException(nameof(some)); 63 | if (none == null) throw new ArgumentNullException(nameof(none)); 64 | 65 | if (option.IsSome()) some(option.Value); else none(); 66 | } 67 | 68 | public static void Do(this (bool HasValue, T Value) option, Action some) => 69 | option.Match(some, delegate { }); 70 | 71 | public static (bool HasValue, TResult Value) Bind(this (bool HasValue, T Value) first, Func function) 72 | => function == null ? throw new ArgumentNullException(nameof(function)) 73 | : first.IsSome() ? function(first.Value) : None(); 74 | 75 | public static (bool HasValue, TResult Value) Map(this (bool HasValue, T Value) option, Func mapper) 76 | => mapper == null ? throw new ArgumentNullException(nameof(mapper)) 77 | : option.Bind(x => Some(mapper(x))); 78 | 79 | public static T Get(this (bool HasValue, T Value) option) => 80 | option.IsSome() ? option.Value : throw new ArgumentException(nameof(option)); 81 | 82 | public static T OrDefault(this (bool HasValue, T Value) option) => 83 | option.Or(default); 84 | 85 | public static T Or(this (bool HasValue, T Value) option, T none) => 86 | option.IsSome() ? option.Value : none; 87 | 88 | public static int Count(this (bool, T) option) => option.IsSome() ? 1 : 0; 89 | 90 | public static bool Exists(this (bool HasValue, T Value) option, Func predicate) 91 | => predicate == null ? throw new ArgumentNullException(nameof(predicate)) 92 | : option.IsSome() && predicate(option.Value); 93 | 94 | public static (bool HasValue, T Value) Filter(this (bool, T) option, Func predicate) 95 | => predicate == null ? throw new ArgumentNullException(nameof(predicate)) 96 | : option.Bind(x => predicate(x) ? Some(x) : None()); 97 | 98 | public static T? ToNullable(this (bool HasValue, T Value) option) where T : struct => 99 | option.IsSome() ? (T?) option.Value : null; 100 | 101 | public static IEnumerable ToEnumerable(this (bool, T) option) => 102 | option.Match(Seq, System.Linq.Enumerable.Empty); 103 | 104 | static IEnumerable Seq(T x) { yield return x; } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Optional.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2018 Atif Aziz. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Linq 18 | { 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | static partial class Optional 23 | { 24 | public static (bool HasValue, TResult Value) Select(this (bool, T) option, Func selector) 25 | => selector == null ? throw new ArgumentNullException(nameof(selector)) 26 | : option.Map(selector); 27 | 28 | public static (bool HasValue, T Value) Where(this (bool, T) option, Func predicate) => 29 | option.Filter(predicate); 30 | 31 | public static (bool HasValue, TResult Value) SelectMany(this (bool, T) first, Func secondSelector) 32 | => secondSelector == null ? throw new ArgumentNullException(nameof(secondSelector)) 33 | : first.Bind(secondSelector); 34 | 35 | public static (bool HasValue, TResult Value) SelectMany(this (bool, TFirst) first, Func secondSelector, Func resultSelector) 36 | => secondSelector == null ? throw new ArgumentNullException(nameof(secondSelector)) 37 | : resultSelector == null ? throw new ArgumentNullException(nameof(resultSelector)) 38 | : first.Bind(x => secondSelector(x).Map(y => resultSelector(x, y))); 39 | 40 | public static (bool HasValue, T Value) Cast(this (bool, object) option) => 41 | from x in option 42 | select (T) x; 43 | 44 | public static bool All(this (bool, T) option, Func predicate) 45 | => predicate == null ? throw new ArgumentNullException(nameof(predicate)) 46 | : option.Match(predicate, () => true); 47 | 48 | public static T[] ToArray(this (bool, T) option) => 49 | option.Match(x => new[] { x }, () => EmptyArray.Value); 50 | 51 | public static List ToList(this (bool, T) option) => 52 | option.Match(x => new List { x }, () => new List()); 53 | 54 | static class EmptyArray 55 | { 56 | public static readonly T[] Value = new T[0]; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Optuple.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0;netstandard1.0 4 | 7.1 5 | 2.1.0 6 | Copyright © 2018 Atif Aziz. Portions © 2014 Nils Lück 7 | A .NET Standard library that adds option semantics to a tuple of Boolean and T. 8 | Atif Aziz 9 | Atif Aziz 10 | 11 | https://github.com/atifaziz/Optuple 12 | https://github.com/atifaziz/Optuple.git 13 | git 14 | option;optional;maybe;some;none;just;nothing;monad;linq;fp;functional 15 | COPYING.txt 16 | 17 | 18 | ..\bin\Debug\ 19 | 20 | 21 | ..\bin\Release\ 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Public.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple 2 | { 3 | public partial class Option {} 4 | public partial class OptionModule {} 5 | 6 | namespace Collections 7 | { 8 | public partial class EnumerableExtensions {} 9 | } 10 | 11 | namespace RegularExpressions 12 | { 13 | public partial class RegexExtensions {} 14 | } 15 | 16 | namespace Linq 17 | { 18 | public partial class Optional {} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Regex.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2018 Atif Aziz. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.RegularExpressions 18 | { 19 | using System; 20 | using System.Text.RegularExpressions; 21 | using static OptionModule; 22 | 23 | static partial class RegexExtensions 24 | { 25 | public static (bool Success, T Value) ToOption(this T group) where T : Group 26 | => group == null ? throw new ArgumentNullException(nameof(group)) 27 | : group.Success ? Some(group) 28 | : default; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd "%~dp0" 3 | call :main %* 4 | popd 5 | goto :EOF 6 | 7 | :main 8 | call build ^ 9 | && call :test Debug -p:CollectCoverage=true ^ 10 | -p:CoverletOutputFormat=opencover ^ 11 | -p:Exclude=[NUnit*]* ^ 12 | && call :test Release 13 | goto :EOF 14 | 15 | :test 16 | dotnet test --no-build tests -c %* 17 | goto :EOF 18 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cd "$(dirname "$0")" 4 | ./build.sh 5 | dotnet test --no-build tests -c Debug -p:CollectCoverage=true \ 6 | -p:CoverletOutputFormat=opencover \ 7 | -p:Exclude=[NUnit*]* 8 | dotnet test --no-build tests -c Release 9 | 10 | -------------------------------------------------------------------------------- /tests/BreakingAction.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Tests 2 | { 3 | using System; 4 | 5 | static class BreakingAction 6 | { 7 | public static readonly Action OfNone = () => throw new NotImplementedException(); 8 | public static Action Of() => delegate { throw new NotImplementedException(); }; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/BreakingCollection.cs: -------------------------------------------------------------------------------- 1 | #region Copyright (c) 2009 Atif Aziz. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Tests 18 | { 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Linq; 22 | 23 | class BreakingCollection : BreakingSequence, ICollection 24 | { 25 | protected readonly IList List; 26 | 27 | public BreakingCollection(params T[] values) : this ((IList) values) {} 28 | public BreakingCollection(IList list) => List = list; 29 | public BreakingCollection(int count) : 30 | this(Enumerable.Repeat(default(T), count).ToList()) {} 31 | 32 | public int Count => List.Count; 33 | 34 | public void Add(T item) => throw new NotImplementedException(); 35 | public void Clear() => throw new NotImplementedException(); 36 | public bool Contains(T item) => List.Contains(item); 37 | public void CopyTo(T[] array, int arrayIndex) => List.CopyTo(array, arrayIndex); 38 | public bool Remove(T item) => throw new NotImplementedException(); 39 | public bool IsReadOnly => true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/BreakingFunc.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Tests 2 | { 3 | using System; 4 | 5 | static class BreakingFunc 6 | { 7 | public static Func Of() => () => throw new NotImplementedException(); 8 | public static Func Of() => delegate { throw new NotImplementedException(); }; 9 | public static Func Of() => delegate { throw new NotImplementedException(); }; 10 | } 11 | } -------------------------------------------------------------------------------- /tests/BreakingList.cs: -------------------------------------------------------------------------------- 1 | #region Copyright (c) 2010 Leopold Bushkin. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Tests 18 | { 19 | using System; 20 | using System.Collections.Generic; 21 | 22 | /// 23 | /// This class implement but specifically prohibits enumeration using 24 | /// . It is provided to assist in testing extension 25 | /// methods that must not call , either because they 26 | /// should be using the indexer or because they are expected to be lazily evaluated. 27 | /// 28 | 29 | sealed class BreakingList : BreakingCollection, IList 30 | { 31 | public BreakingList() : this(new List()) {} 32 | public BreakingList(List list) : base(list) {} 33 | 34 | public int IndexOf(T item) => List.IndexOf(item); 35 | public void Insert(int index, T item) => throw new NotImplementedException(); 36 | public void RemoveAt(int index) => throw new NotImplementedException(); 37 | 38 | public T this[int index] 39 | { 40 | get => List[index]; 41 | set => throw new NotImplementedException(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/BreakingReadOnlyCollection.cs: -------------------------------------------------------------------------------- 1 | #region Copyright (c) 2018 Leandro Fernandes. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #endregion 15 | 16 | namespace Optuple.Tests 17 | { 18 | using System.Collections.Generic; 19 | 20 | class BreakingReadOnlyCollection : BreakingSequence, IReadOnlyCollection 21 | { 22 | readonly IReadOnlyCollection _collection; 23 | 24 | public BreakingReadOnlyCollection(params T[] values) : this ((IReadOnlyCollection) values) {} 25 | public BreakingReadOnlyCollection(IReadOnlyCollection collection) => _collection = collection; 26 | public int Count => _collection.Count; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/BreakingReadOnlyList.cs: -------------------------------------------------------------------------------- 1 | #region Copyright (c) 2018 Avi Levin. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Tests 18 | { 19 | using System.Collections; 20 | using System.Collections.Generic; 21 | 22 | /// 23 | /// This class implement but specifically prohibits enumeration using GetEnumerator(). 24 | /// It is provided to assist in testing extension methods that MUST NOT call the GetEnumerator() 25 | /// method of - either because they should be using the indexer or because they are 26 | /// expected to be lazily evaluated. 27 | /// 28 | 29 | sealed class BreakingReadOnlyList : BreakingReadOnlyCollection, IReadOnlyList 30 | { 31 | readonly IReadOnlyList _list; 32 | 33 | public BreakingReadOnlyList(params T[] values) : this ((IReadOnlyList) values) {} 34 | public BreakingReadOnlyList(IReadOnlyList list) : base (list) 35 | => _list = list; 36 | 37 | public T this[int index] => _list[index]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/BreakingSequence.cs: -------------------------------------------------------------------------------- 1 | #region Copyright (c) 2008 Jonathan Skeet. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | #endregion 16 | 17 | namespace Optuple.Tests 18 | { 19 | using System; 20 | using System.Collections; 21 | using System.Collections.Generic; 22 | 23 | /// 24 | /// Enumerable sequence which throws as soon as its 25 | /// enumerator is requested; used to check lazy evaluation. 26 | /// 27 | 28 | class BreakingSequence : IEnumerable 29 | { 30 | public IEnumerator GetEnumerator() => throw new InvalidOperationException(); 31 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/EnumerableTests.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Collections; 7 | using MoreLinq; 8 | using NUnit.Framework; 9 | using static System.Linq.Enumerable; 10 | using static OptionModule; 11 | using Enumerable = Collections.EnumerableExtensions; 12 | 13 | [TestFixture] 14 | public class EnumerableTests 15 | { 16 | public class FirstOrNone 17 | { 18 | [Test] 19 | public void NullSource() 20 | { 21 | var e = Assert.Throws(() => 22 | Enumerable.FirstOrNone(null)); 23 | Assert.That(e.ParamName, Is.EqualTo("source")); 24 | } 25 | 26 | public class Sequence 27 | { 28 | [Test] 29 | public void Empty() 30 | { 31 | var source = Empty().Hide(); 32 | var (t, x) = source.FirstOrNone(); 33 | Assert.That(t, Is.False); 34 | Assert.That(x, Is.Null); 35 | } 36 | 37 | [Test] 38 | public void One() 39 | { 40 | var source = MoreEnumerable.From(() => 123); 41 | var (t, x) = source.FirstOrNone(); 42 | Assert.That(t, Is.True); 43 | Assert.That(x, Is.EqualTo(123)); 44 | } 45 | 46 | [Test] 47 | public void Many() 48 | { 49 | var source = MoreEnumerable.From(() => 123, BreakingFunc.Of()); 50 | var (t, x) = source.FirstOrNone(); 51 | Assert.That(t, Is.True); 52 | Assert.That(x, Is.EqualTo(123)); 53 | } 54 | } 55 | 56 | public class List 57 | { 58 | [Test] 59 | public void Empty() 60 | { 61 | var source = new BreakingList(new List()); 62 | var (t, x) = source.FirstOrNone(); 63 | Assert.That(t, Is.False); 64 | Assert.That(x, Is.Null); 65 | } 66 | 67 | [Test] 68 | public void One() 69 | { 70 | var source = new BreakingList(new List { 123 }); 71 | var (t, x) = source.FirstOrNone(); 72 | Assert.That(t, Is.True); 73 | Assert.That(x, Is.EqualTo(123)); 74 | } 75 | 76 | [Test] 77 | public void Many() 78 | { 79 | var source = new BreakingList(new List { 123, 456 }); 80 | var (t, x) = source.FirstOrNone(); 81 | Assert.That(t, Is.True); 82 | Assert.That(x, Is.EqualTo(123)); 83 | } 84 | } 85 | 86 | public class ReadOnlyList 87 | { 88 | [Test] 89 | public void Empty() 90 | { 91 | var source = new BreakingReadOnlyList(new List()); 92 | var (t, x) = source.FirstOrNone(); 93 | Assert.That(t, Is.False); 94 | Assert.That(x, Is.Null); 95 | } 96 | 97 | [Test] 98 | public void One() 99 | { 100 | var source = new BreakingReadOnlyList(new List { 123 }); 101 | var (t, x) = source.FirstOrNone(); 102 | Assert.That(t, Is.True); 103 | Assert.That(x, Is.EqualTo(123)); 104 | } 105 | 106 | [Test] 107 | public void Many() 108 | { 109 | var source = new BreakingReadOnlyList(new List { 123, 456 }); 110 | var (t, x) = source.FirstOrNone(); 111 | Assert.That(t, Is.True); 112 | Assert.That(x, Is.EqualTo(123)); 113 | } 114 | } 115 | 116 | public class Predicate 117 | { 118 | [Test] 119 | public void NullSource() 120 | { 121 | var e = Assert.Throws(() => 122 | Enumerable.FirstOrNone(null, BreakingFunc.Of())); 123 | Assert.That(e.ParamName, Is.EqualTo("source")); 124 | } 125 | 126 | [Test] 127 | public void NullPredicate() 128 | { 129 | var e = Assert.Throws(() => 130 | Empty().FirstOrNone(null)); 131 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 132 | } 133 | 134 | [Test] 135 | public void Empty() 136 | { 137 | var source = Empty().Hide(); 138 | var (t, x) = source.FirstOrNone(BreakingFunc.Of()); 139 | Assert.That(t, Is.False); 140 | Assert.That(x, Is.Null); 141 | } 142 | 143 | public class One 144 | { 145 | static readonly IEnumerable Source = MoreEnumerable.From(() => 123); 146 | 147 | [Test] 148 | public void Match() 149 | { 150 | var (t, x) = Source.FirstOrNone(x => x > 100); 151 | Assert.That(t, Is.True); 152 | Assert.That(x, Is.EqualTo(123)); 153 | } 154 | 155 | [Test] 156 | public void NoneMatch() 157 | { 158 | var (t, x) = Source.FirstOrNone(_ => false); 159 | Assert.That(t, Is.False); 160 | Assert.That(x, Is.Zero); 161 | } 162 | } 163 | 164 | public class Many 165 | { 166 | static readonly IEnumerable Source = 167 | MoreEnumerable.From(() => 123, () => 456, () => 789); 168 | 169 | [Test] 170 | public void Match() 171 | { 172 | var (t, x) = Source.FirstOrNone(x => x >= 500); 173 | Assert.That(t, Is.True); 174 | Assert.That(x, Is.EqualTo(789)); 175 | } 176 | 177 | [Test] 178 | public void NoneMatch() 179 | { 180 | var (t, x) = Source.FirstOrNone(_ => false); 181 | Assert.That(t, Is.False); 182 | Assert.That(x, Is.Zero); 183 | } 184 | } 185 | } 186 | } 187 | 188 | public class SingleOrNone 189 | { 190 | [Test] 191 | public void NullSource() 192 | { 193 | var e = Assert.Throws(() => 194 | Enumerable.SingleOrNone(null)); 195 | Assert.That(e.ParamName, Is.EqualTo("source")); 196 | } 197 | 198 | public class Sequence 199 | { 200 | [Test] 201 | public void Empty() 202 | { 203 | var source = Empty().Hide(); 204 | var (t, x) = source.SingleOrNone(); 205 | Assert.That(t, Is.False); 206 | Assert.That(x, Is.Null); 207 | } 208 | 209 | [Test] 210 | public void One() 211 | { 212 | var source = MoreEnumerable.From(() => 123); 213 | var (t, x) = source.SingleOrNone(); 214 | Assert.That(t, Is.True); 215 | Assert.That(x, Is.EqualTo(123)); 216 | } 217 | 218 | [Test] 219 | public void Many() 220 | { 221 | var source = MoreEnumerable.From(() => 123, () => 456, BreakingFunc.Of()); 222 | var (t, x) = source.SingleOrNone(); 223 | Assert.That(t, Is.False); 224 | Assert.That(x, Is.Null); 225 | } 226 | } 227 | 228 | public class List 229 | { 230 | [Test] 231 | public void Empty() 232 | { 233 | var source = new BreakingList(new List()); 234 | var (t, x) = source.SingleOrNone(); 235 | Assert.That(t, Is.False); 236 | Assert.That(x, Is.Null); 237 | } 238 | 239 | [Test] 240 | public void One() 241 | { 242 | var source = new BreakingList(new List { 123 }); 243 | var (t, x) = source.SingleOrNone(); 244 | Assert.That(t, Is.True); 245 | Assert.That(x, Is.EqualTo(123)); 246 | } 247 | 248 | [Test] 249 | public void Many() 250 | { 251 | var source = new BreakingList(new List { 123, 456 }); 252 | var (t, x) = source.SingleOrNone(); 253 | Assert.That(t, Is.False); 254 | Assert.That(x, Is.Null); 255 | } 256 | } 257 | 258 | public class ReadOnlyList 259 | { 260 | [Test] 261 | public void Empty() 262 | { 263 | var source = new BreakingReadOnlyList(new List()); 264 | var (t, x) = source.SingleOrNone(); 265 | Assert.That(t, Is.False); 266 | Assert.That(x, Is.Null); 267 | } 268 | 269 | [Test] 270 | public void One() 271 | { 272 | var source = new BreakingReadOnlyList(new List { 123 }); 273 | var (t, x) = source.SingleOrNone(); 274 | Assert.That(t, Is.True); 275 | Assert.That(x, Is.EqualTo(123)); 276 | } 277 | 278 | [Test] 279 | public void Many() 280 | { 281 | var source = new BreakingReadOnlyList(new List { 123, 456 }); 282 | var (t, x) = source.SingleOrNone(); 283 | Assert.That(t, Is.False); 284 | Assert.That(x, Is.Null); 285 | } 286 | } 287 | 288 | public class Predicate 289 | { 290 | [Test] 291 | public void NullSource() 292 | { 293 | var e = Assert.Throws(() => 294 | Enumerable.SingleOrNone(null, BreakingFunc.Of())); 295 | Assert.That(e.ParamName, Is.EqualTo("source")); 296 | } 297 | 298 | [Test] 299 | public void NullPredicate() 300 | { 301 | var e = Assert.Throws(() => 302 | Empty().SingleOrNone(null)); 303 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 304 | } 305 | 306 | [Test] 307 | public void Empty() 308 | { 309 | var source = Empty().Hide(); 310 | var (t, x) = source.SingleOrNone(BreakingFunc.Of()); 311 | Assert.That(t, Is.False); 312 | Assert.That(x, Is.Null); 313 | } 314 | 315 | public class One 316 | { 317 | static readonly IEnumerable Source = MoreEnumerable.From(() => 123); 318 | 319 | [Test] 320 | public void NoneMatch() 321 | { 322 | var (t, x) = Source.SingleOrNone(_ => false); 323 | Assert.That(t, Is.False); 324 | Assert.That(x, Is.Zero); 325 | } 326 | 327 | [Test] 328 | public void OneMatch() 329 | { 330 | var (t, x) = Source.SingleOrNone(x => x > 100); 331 | Assert.That(t, Is.True); 332 | Assert.That(x, Is.EqualTo(123)); 333 | } 334 | } 335 | 336 | public class Many 337 | { 338 | static readonly IEnumerable Source = 339 | MoreEnumerable.From(() => 123, () => 456, () => 786); 340 | 341 | [Test] 342 | public void NoneMatch() 343 | { 344 | var (t, x) = Source.SingleOrNone(_ => false); 345 | Assert.That(t, Is.False); 346 | Assert.That(x, Is.Zero); 347 | } 348 | 349 | [Test] 350 | public void OneMatch() 351 | { 352 | var (t, x) = Source.SingleOrNone(x => x > 500); 353 | Assert.That(t, Is.True); 354 | Assert.That(x, Is.EqualTo(786)); 355 | } 356 | 357 | [Test] 358 | public void ManyMatch() 359 | { 360 | var (t, x) = 361 | Source.Concat(MoreEnumerable.From(BreakingFunc.Of())) 362 | .SingleOrNone(x => x > 200); 363 | Assert.That(t, Is.False); 364 | Assert.That(x, Is.Zero); 365 | } 366 | } 367 | } 368 | } 369 | 370 | public class LastOrNone 371 | { 372 | [Test] 373 | public void NullSource() 374 | { 375 | var e = Assert.Throws(() => 376 | Enumerable.LastOrNone(null)); 377 | Assert.That(e.ParamName, Is.EqualTo("source")); 378 | } 379 | 380 | public class Sequence 381 | { 382 | [Test] 383 | public void Empty() 384 | { 385 | var source = Empty().Hide(); 386 | var (t, x) = source.LastOrNone(); 387 | Assert.That(t, Is.False); 388 | Assert.That(x, Is.Null); 389 | } 390 | 391 | [Test] 392 | public void One() 393 | { 394 | var source = MoreEnumerable.From(() => 123); 395 | var (t, x) = source.LastOrNone(); 396 | Assert.That(t, Is.True); 397 | Assert.That(x, Is.EqualTo(123)); 398 | } 399 | 400 | [Test] 401 | public void Many() 402 | { 403 | var source = MoreEnumerable.From(() => 123, () => 456, () => 789); 404 | var (t, x) = source.LastOrNone(); 405 | Assert.That(t, Is.True); 406 | Assert.That(x, Is.EqualTo(789)); 407 | } 408 | } 409 | 410 | public class List 411 | { 412 | [Test] 413 | public void Empty() 414 | { 415 | var source = new BreakingList(new List()); 416 | var (t, x) = source.LastOrNone(); 417 | Assert.That(t, Is.False); 418 | Assert.That(x, Is.Null); 419 | } 420 | 421 | [Test] 422 | public void One() 423 | { 424 | var source = new BreakingList(new List { 123 }); 425 | var (t, x) = source.LastOrNone(); 426 | Assert.That(t, Is.True); 427 | Assert.That(x, Is.EqualTo(123)); 428 | } 429 | 430 | [Test] 431 | public void Many() 432 | { 433 | var source = new BreakingList(new List { 123, 456, 789 }); 434 | var (t, x) = source.LastOrNone(); 435 | Assert.That(t, Is.True); 436 | Assert.That(x, Is.EqualTo(789)); 437 | } 438 | } 439 | 440 | public class ReadOnlyList 441 | { 442 | [Test] 443 | public void Empty() 444 | { 445 | var source = new BreakingReadOnlyList(new List()); 446 | var (t, x) = source.LastOrNone(); 447 | Assert.That(t, Is.False); 448 | Assert.That(x, Is.Null); 449 | } 450 | 451 | [Test] 452 | public void One() 453 | { 454 | var source = new BreakingReadOnlyList(new List { 123 }); 455 | var (t, x) = source.LastOrNone(); 456 | Assert.That(t, Is.True); 457 | Assert.That(x, Is.EqualTo(123)); 458 | } 459 | 460 | [Test] 461 | public void Many() 462 | { 463 | var source = new BreakingReadOnlyList(new List { 123, 456, 789 }); 464 | var (t, x) = source.LastOrNone(); 465 | Assert.That(t, Is.True); 466 | Assert.That(x, Is.EqualTo(789)); 467 | } 468 | } 469 | 470 | public class Predicate 471 | { 472 | [Test] 473 | public void NullSource() 474 | { 475 | var e = Assert.Throws(() => 476 | Enumerable.LastOrNone(null, BreakingFunc.Of())); 477 | Assert.That(e.ParamName, Is.EqualTo("source")); 478 | } 479 | 480 | [Test] 481 | public void NullPredicate() 482 | { 483 | var e = Assert.Throws(() => 484 | Empty().LastOrNone(null)); 485 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 486 | } 487 | 488 | [Test] 489 | public void Empty() 490 | { 491 | var source = Empty().Hide(); 492 | var (t, x) = source.LastOrNone(BreakingFunc.Of()); 493 | Assert.That(t, Is.False); 494 | Assert.That(x, Is.Null); 495 | } 496 | 497 | public class One 498 | { 499 | static readonly IEnumerable Source = MoreEnumerable.From(() => 123); 500 | 501 | [Test] 502 | public void Match() 503 | { 504 | var (t, x) = Source.LastOrNone(x => x > 100); 505 | Assert.That(t, Is.True); 506 | Assert.That(x, Is.EqualTo(123)); 507 | } 508 | 509 | [Test] 510 | public void NoneMatch() 511 | { 512 | var (t, x) = Source.LastOrNone(_ => false); 513 | Assert.That(t, Is.False); 514 | Assert.That(x, Is.Zero); 515 | } 516 | } 517 | 518 | public class Many 519 | { 520 | static readonly IEnumerable Source = 521 | MoreEnumerable.From(() => 123, () => 456, () => 786); 522 | 523 | [Test] 524 | public void Match() 525 | { 526 | var (t, x) = Source.LastOrNone(x => x < 500); 527 | Assert.That(t, Is.True); 528 | Assert.That(x, Is.EqualTo(456)); 529 | } 530 | 531 | [Test] 532 | public void NoneMatch() 533 | { 534 | var (t, x) = Source.LastOrNone(_ => false); 535 | Assert.That(t, Is.False); 536 | Assert.That(x, Is.Zero); 537 | } 538 | } 539 | } 540 | } 541 | 542 | public class Filter 543 | { 544 | [Test] 545 | public void NullSource() 546 | { 547 | var e = Assert.Throws(() => 548 | Enumerable.Filter(null)); 549 | Assert.That(e.ParamName, Is.EqualTo("options")); 550 | } 551 | 552 | [Test] 553 | public void Laziness() 554 | { 555 | new BreakingSequence<(bool, object)>().Filter(); 556 | } 557 | 558 | [Test] 559 | public void Empty() 560 | { 561 | var result = Empty<(bool, object)>().Filter(); 562 | Assert.That(result, Is.Empty); 563 | } 564 | 565 | [Test] 566 | public void AllNone() 567 | { 568 | var result = Repeat(Option.None(), 10).Filter(); 569 | Assert.That(result, Is.Empty); 570 | } 571 | 572 | [Test] 573 | public void AllSome() 574 | { 575 | var xs = Range(1, 10); 576 | var result = xs.Select(Some).Filter(); 577 | Assert.That(result, Is.EqualTo(xs)); 578 | } 579 | 580 | [Test] 581 | public void NoneAndSome() 582 | { 583 | var xs = Range(1, 10); 584 | var result = xs.Select(x => x % 2 == 0 ? Some(x) : default).Filter(); 585 | Assert.That(result, Is.EqualTo(new[] { 2, 4, 6, 8, 10 })); 586 | } 587 | } 588 | 589 | public class ListAll 590 | { 591 | [Test] 592 | public void NullSource() 593 | { 594 | var e = Assert.Throws(() => 595 | Enumerable.ListAll(null)); 596 | Assert.That(e.ParamName, Is.EqualTo("options")); 597 | } 598 | 599 | [Test] 600 | public void Empty() 601 | { 602 | var (t, list) = Empty<(bool, object)>().ListAll(); 603 | Assert.That(t, Is.True); 604 | Assert.That(list, Is.Empty); 605 | } 606 | 607 | [Test] 608 | public void AllNone() 609 | { 610 | var (t, list) = Repeat(Option.None(), 10).ListAll(); 611 | Assert.That(t, Is.False); 612 | Assert.That(list, Is.Null); 613 | } 614 | 615 | [Test] 616 | public void AllSome() 617 | { 618 | var xs = Range(1, 10); 619 | var (t, list) = xs.Select(Some).ListAll(); 620 | Assert.That(t, Is.True); 621 | Assert.That(list, Is.EqualTo(xs)); 622 | } 623 | 624 | [Test] 625 | public void NoneAndSome() 626 | { 627 | var xs = 628 | MoreEnumerable.From( 629 | () => Some(1), 630 | () => Some(2), 631 | () => Some(3), 632 | None, 633 | BreakingFunc.Of<(bool, int)>()); 634 | 635 | var (t, list) = xs.ListAll(); 636 | Assert.That(t, Is.False); 637 | Assert.That(list, Is.Null); 638 | } 639 | } 640 | } 641 | } 642 | -------------------------------------------------------------------------------- /tests/OptionTests.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Tests 2 | { 3 | using System; 4 | using NUnit.Framework; 5 | using _ = System.Object; 6 | using static OptionModule; 7 | 8 | [TestFixture] 9 | public class OptionTests 10 | { 11 | [Test] 12 | public void SomeValue() 13 | { 14 | var (t, x) = Some(42); 15 | Assert.That(t, Is.True); 16 | Assert.That(x, Is.EqualTo(42)); 17 | } 18 | 19 | [Test] 20 | public void SomeNullReference() 21 | { 22 | var (t, x) = Some((object) null); 23 | Assert.That(t, Is.True); 24 | Assert.That(x, Is.Null); 25 | } 26 | 27 | [Test] 28 | public void SomeNullValue() 29 | { 30 | var (t, x) = Some((int?) null); 31 | Assert.That(t, Is.True); 32 | Assert.That(x, Is.Null); 33 | } 34 | 35 | [Test] 36 | public void None() 37 | { 38 | var (t, x) = None(); 39 | Assert.That(t, Is.False); 40 | Assert.That(x, Is.Zero); 41 | } 42 | 43 | [Test] 44 | public void IsSome() 45 | { 46 | var result = Some(42); 47 | Assert.That(result.IsSome(), Is.True); 48 | } 49 | 50 | [Test] 51 | public void SomeIsNotNone() 52 | { 53 | var result = Some(42); 54 | Assert.That(result.IsNone(), Is.False); 55 | } 56 | 57 | [Test] 58 | public void IsNone() 59 | { 60 | var result = None(); 61 | Assert.That(result.IsNone(), Is.True); 62 | } 63 | 64 | [Test] 65 | public void NoneIsNotSome() 66 | { 67 | var result = None(); 68 | Assert.That(result.IsSome(), Is.False); 69 | } 70 | 71 | [Test] 72 | public void ToOptionWithNullableNonNull() 73 | { 74 | int? i = 42; 75 | var result = i.ToOption(); 76 | Assert.That(result, Is.EqualTo(Some(42))); 77 | } 78 | 79 | [Test] 80 | public void ToOptionWithNullableNull() 81 | { 82 | int? i = null; 83 | var result = i.ToOption(); 84 | Assert.That(result, Is.EqualTo(None())); 85 | } 86 | 87 | [Test] 88 | public void From() 89 | { 90 | var result = Option.From(true, 42); 91 | Assert.That(result, Is.EqualTo(Some(42))); 92 | } 93 | 94 | [Test] 95 | public void FromNone() 96 | { 97 | var result = Option.From(false, 42); 98 | Assert.That(result, Is.EqualTo(None())); 99 | } 100 | 101 | [Test] 102 | public void SomeWhenWithNullPredicate() 103 | { 104 | var e = Assert.Throws(() => 105 | SomeWhen(42, null)); 106 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 107 | } 108 | 109 | [Test] 110 | public void SomeWhenWithTrueCondition() 111 | { 112 | var result = SomeWhen(42, n => n > 0); 113 | Assert.That(result, Is.EqualTo(Some(42))); 114 | } 115 | 116 | [Test] 117 | public void SomeWhenWithFalseCondition() 118 | { 119 | var result = SomeWhen(42, n => n < 0); 120 | Assert.That(result, Is.EqualTo(None())); 121 | } 122 | 123 | [Test] 124 | public void NoneWhenWithNullPredicate() 125 | { 126 | var e = Assert.Throws(() => 127 | NoneWhen(42, null)); 128 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 129 | } 130 | 131 | [Test] 132 | public void NoneWhenNullableIsNonNull() 133 | { 134 | var result = NoneWhen(42, n => n < 0); 135 | Assert.That(result, Is.EqualTo(Some(42))); 136 | } 137 | 138 | [Test] 139 | public void NoneWhenNullableIsNull() 140 | { 141 | var result = NoneWhen(42, n => n > 0); 142 | Assert.That(result, Is.EqualTo(None())); 143 | } 144 | 145 | [TestCase(true, 42)] 146 | [TestCase(false, 0)] 147 | public void MatchWithNullSomeFunction(bool f, int v) 148 | { 149 | var e = Assert.Throws(() => 150 | Option.From(f, v).Match(null, BreakingFunc.Of<_>())); 151 | Assert.That(e.ParamName, Is.EqualTo("some")); 152 | } 153 | 154 | [TestCase(true, 42)] 155 | [TestCase(false, 0)] 156 | public void MatchWithNullNoneFunction(bool f, int v) 157 | { 158 | var e = Assert.Throws(() => 159 | Option.From(f, v).Match(BreakingFunc.Of<_, _>(), null)); 160 | Assert.That(e.ParamName, Is.EqualTo("none")); 161 | } 162 | 163 | [Test] 164 | public void MatchSome() 165 | { 166 | var some = Some(3); 167 | var result = some.Match(i => "foobar".Substring(i), () => "none"); 168 | Assert.That(result, Is.EqualTo("bar")); 169 | } 170 | 171 | [Test] 172 | public void MatchNone() 173 | { 174 | var none = None(); 175 | var result = none.Match(i => "foobar".Substring(i), () => "foobar"); 176 | Assert.That(result, Is.EqualTo("foobar")); 177 | } 178 | 179 | [TestCase(true, 42)] 180 | [TestCase(false, 0)] 181 | public void MatchWithNullSomeAction(bool f, int v) 182 | { 183 | var e = Assert.Throws(() => 184 | Option.From(f, v).Match(null, BreakingAction.OfNone)); 185 | Assert.That(e.ParamName, Is.EqualTo("some")); 186 | } 187 | 188 | [TestCase(true, 42)] 189 | [TestCase(false, 0)] 190 | public void MatchWithNullNoneAction(bool f, int v) 191 | { 192 | var e = Assert.Throws(() => 193 | Option.From(f, v).Match(BreakingAction.Of(), null)); 194 | Assert.That(e.ParamName, Is.EqualTo("none")); 195 | } 196 | 197 | [Test] 198 | public void MatchSomeWithAction() 199 | { 200 | var some = Some(42); 201 | some.Match(x => Assert.That(x, Is.EqualTo(42)), Assert.Fail); 202 | } 203 | 204 | [Test] 205 | public void MatchNoneWithAction() 206 | { 207 | var none = None(); 208 | none.Match(x => Assert.Fail(), Assert.Pass); 209 | } 210 | 211 | [TestCase(true, 42)] 212 | [TestCase(false, 0)] 213 | public void DoWithNullAction(bool f, int v) 214 | { 215 | var e = Assert.Throws(() => 216 | Option.From(f, v).Do(null)); 217 | Assert.That(e.ParamName, Is.EqualTo("some")); 218 | } 219 | 220 | [Test] 221 | public void DoSome() 222 | { 223 | var some = Some(42); 224 | var result = 0; 225 | some.Do(x => result = x); 226 | Assert.That(result, Is.EqualTo(42)); 227 | } 228 | 229 | [Test] 230 | public void DoNone() 231 | { 232 | var none = None(); 233 | none.Do(x => Assert.Fail()); 234 | Assert.Pass(); 235 | } 236 | 237 | [TestCase(true, 42)] 238 | [TestCase(false, 0)] 239 | public void BindWithNullFunction(bool f, int v) 240 | { 241 | var e = Assert.Throws(() => 242 | Option.From(f, v).Bind(null)); 243 | Assert.That(e.ParamName, Is.EqualTo("function")); 244 | } 245 | 246 | [Test] 247 | public void BindSome() 248 | { 249 | var some = Some(3); 250 | var result = some.Bind(i => Some("foobar".Substring(i))); 251 | Assert.That(result, Is.EqualTo(Some("bar"))); 252 | } 253 | 254 | [Test] 255 | public void BindNone() 256 | { 257 | var none = None(); 258 | var result = none.Bind(i => Some("foobar".Substring(i))); 259 | Assert.That(result, Is.EqualTo(None())); 260 | } 261 | 262 | [TestCase(true, 42)] 263 | [TestCase(false, 0)] 264 | public void MapWithNullMapper(bool f, int v) 265 | { 266 | var e = Assert.Throws(() => 267 | Option.From(f, v).Map<_, _>(null)); 268 | Assert.That(e.ParamName, Is.EqualTo("mapper")); 269 | } 270 | 271 | [Test] 272 | public void MapSome() 273 | { 274 | var some = Some(42); 275 | var result = some.Map(n => new string((char) n, n)); 276 | var stars = new string('*', 42); 277 | Assert.That(result, Is.EqualTo(Some(stars))); 278 | } 279 | 280 | [Test] 281 | public void MapNone() 282 | { 283 | var none = None(); 284 | var result = none.Map(n => new string((char) n, n)); 285 | Assert.That(result, Is.EqualTo(None())); 286 | } 287 | 288 | [Test] 289 | public void GetSome() 290 | { 291 | var some = Some(42); 292 | Assert.That(some.Get(), Is.EqualTo(42)); 293 | } 294 | 295 | [Test] 296 | public void GetNone() 297 | { 298 | var none = None(); 299 | Assert.Throws(() => none.Get()); 300 | } 301 | 302 | [Test] 303 | public void OrDefaultSome() 304 | { 305 | var some = Some(42); 306 | Assert.That(some.OrDefault(), Is.EqualTo(42)); 307 | } 308 | 309 | [Test] 310 | public void OrDefaultNone() 311 | { 312 | var none = None(); 313 | Assert.That(none.OrDefault(), Is.Zero); 314 | } 315 | 316 | [Test] 317 | public void OrSome() 318 | { 319 | var some = Some(42); 320 | Assert.That(some.Or(-42), Is.EqualTo(42)); 321 | } 322 | 323 | [Test] 324 | public void OrNone() 325 | { 326 | var none = None(); 327 | Assert.That(none.Or(42), Is.EqualTo(42)); 328 | } 329 | 330 | [Test] 331 | public void CountSome() 332 | { 333 | Assert.That(Some(42).Count(), Is.EqualTo(1)); 334 | } 335 | 336 | [Test] 337 | public void CountNone() 338 | { 339 | Assert.That(None().Count(), Is.Zero); 340 | } 341 | 342 | [TestCase(true, 42)] 343 | [TestCase(false, 0)] 344 | public void ExistsWithNullPredicate(bool f, int v) 345 | { 346 | var e = Assert.Throws(() => 347 | Option.From(f, v).Exists(null)); 348 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 349 | } 350 | 351 | [Test] 352 | public void ExistsSome() 353 | { 354 | var some = Some(42); 355 | Assert.That(some.Exists(x => x > 0), Is.True); 356 | Assert.That(some.Exists(x => x < 0), Is.False); 357 | } 358 | 359 | [Test] 360 | public void ExistsNone() 361 | { 362 | var some = None(); 363 | Assert.That(some.Exists(x => x < 0), Is.False); 364 | Assert.That(some.Exists(x => x > 0), Is.False); 365 | } 366 | 367 | [TestCase(true, 42)] 368 | [TestCase(false, 0)] 369 | public void FilterWithNullPredicate(bool f, int v) 370 | { 371 | var e = Assert.Throws(() => 372 | Option.From(f, v).Filter(null)); 373 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 374 | } 375 | 376 | [Test] 377 | public void FilterSome() 378 | { 379 | var some = Some(42); 380 | var result1 = some.Filter(x => x > 0); 381 | var result2 = some.Filter(x => x < 0); 382 | Assert.That(result1, Is.EqualTo(some)); 383 | Assert.That(result2, Is.EqualTo(None())); 384 | } 385 | 386 | [Test] 387 | public void FilterNone() 388 | { 389 | var none = None(); 390 | var result1 = none.Filter(x => x > 42); 391 | var result2 = none.Filter(x => x < 42); 392 | Assert.That(result1, Is.EqualTo(none)); 393 | Assert.That(result2, Is.EqualTo(none)); 394 | } 395 | 396 | [Test] 397 | public void ToNullableSome() 398 | { 399 | var some = Some(42); 400 | var result = some.ToNullable(); 401 | Assert.That(result, Is.EqualTo(42)); 402 | } 403 | 404 | [Test] 405 | public void ToNullableNone() 406 | { 407 | var none = None(); 408 | var result = none.ToNullable(); 409 | Assert.That(result, Is.Null); 410 | } 411 | 412 | // Credit & inspiration: 413 | // https://github.com/nlkl/Optional/blob/fa0160d995af60e8378c28005d810ec5b74f2eef/src/Optional.Tests/MaybeTests.cs#L137-L202 414 | // Copyright (c) 2014 Nils Lück 415 | // The MIT License (MIT) 416 | // https://github.com/nlkl/Optional/blob/fa0160d995af60e8378c28005d810ec5b74f2eef/LICENSE 417 | 418 | public class CompareTo 419 | { 420 | static void LessThan((bool, T) lesser, (bool, T) greater) 421 | { 422 | Assert.That(lesser.CompareTo(greater), Is.LessThan(0)); 423 | Assert.That(greater.CompareTo(lesser), Is.GreaterThan(0)); 424 | } 425 | 426 | static void EqualTo((bool, T) left, (bool, T) right) 427 | { 428 | Assert.That(left.CompareTo(right), Is.Zero); 429 | Assert.That(right.CompareTo(left), Is.Zero); 430 | } 431 | 432 | [Test] 433 | public void ValueTypes() 434 | { 435 | var none = None(); 436 | var some1 = Some(1); 437 | var some2 = Some(2); 438 | 439 | LessThan(none, some1); 440 | LessThan(some1, some2); 441 | 442 | EqualTo(none, none); 443 | EqualTo(some1, some1); 444 | } 445 | 446 | [Test] 447 | public void Comparables() 448 | { 449 | var none = None(); 450 | var someNull = Some(null); 451 | var some1 = Some("1"); 452 | var some2 = Some("2"); 453 | 454 | LessThan(none, some1); 455 | LessThan(none, someNull); 456 | LessThan(someNull, some1); 457 | LessThan(some1, some2); 458 | 459 | EqualTo(none, none); 460 | EqualTo(someNull, someNull); 461 | EqualTo(some1, some1); 462 | } 463 | 464 | [Test] 465 | public void NonComparables() 466 | { 467 | var none = None(); 468 | var someNull = Some(null); 469 | var some1 = Some(new object()); 470 | var some2 = Some(new object()); 471 | 472 | Assert.Throws(() => some1.CompareTo(some2)); 473 | Assert.Throws(() => some2.CompareTo(some1)); 474 | 475 | LessThan(none, some1); 476 | LessThan(none, someNull); 477 | LessThan(someNull, some1); 478 | 479 | EqualTo(none, none); 480 | EqualTo(someNull, someNull); 481 | EqualTo(some1, some1); 482 | } 483 | } 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /tests/OptionalTests.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Linq.Tests 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using NUnit.Framework; 7 | using Optuple.Tests; 8 | using _ = System.Object; 9 | 10 | [TestFixture] 11 | public class OptionalTests 12 | { 13 | static T Fail() 14 | { 15 | Assert.Fail("Binding semantic error!"); 16 | return default; // never reaches here 17 | } 18 | 19 | [Test] 20 | public void SelectWithNullSelector() 21 | { 22 | var e = Assert.Throws(() => 23 | Option.Some(42).Select(null)); 24 | Assert.That(e.ParamName, Is.EqualTo("selector")); 25 | } 26 | 27 | [Test] 28 | public void Select() 29 | { 30 | var result = 31 | from n in Option.Some(42) 32 | select new string((char) n, n); 33 | 34 | var stars = new string('*', 42); 35 | Assert.That(result, Is.EqualTo(Option.Some(stars))); 36 | } 37 | 38 | [Test] 39 | public void SelectNone() 40 | { 41 | var result = 42 | from n in Option.None() 43 | select Fail(); 44 | 45 | Assert.That(result, Is.EqualTo(Option.None())); 46 | } 47 | 48 | [Test] 49 | public void WhereWithNullPredicate() 50 | { 51 | var e = Assert.Throws(() => 52 | Option.Some(42).Where(null)); 53 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 54 | } 55 | 56 | [Test] 57 | public void WhereSomeMeetsCondition() 58 | { 59 | var some = Option.Some(42); 60 | 61 | var result = 62 | from n in some 63 | where n > 0 64 | select n - n; 65 | 66 | Assert.That(result, Is.EqualTo(Option.Some(0))); 67 | } 68 | 69 | [Test] 70 | public void WhereSomeFailsCondition() 71 | { 72 | var result = 73 | from n in Option.Some(42) 74 | where n < 0 75 | select Fail(); 76 | 77 | Assert.That(result, Is.EqualTo(Option.None())); 78 | } 79 | 80 | [Test] 81 | public void WhereNone() 82 | { 83 | var none = Option.None(); 84 | 85 | var result = 86 | from n in none 87 | where Fail() 88 | select n; 89 | 90 | Assert.That(result, Is.EqualTo(none)); 91 | } 92 | 93 | [Test] 94 | public void SelectManySimpleWithNullSecondSelector() 95 | { 96 | var e = Assert.Throws(() => 97 | Option.Some(42).SelectMany<_, _>(null)); 98 | Assert.That(e.ParamName, Is.EqualTo("secondSelector")); 99 | } 100 | 101 | [Test] 102 | public void SelectManySimple() 103 | { 104 | var result = Option.Some(42).SelectMany(n => Option.Some(new string((char) n, n))); 105 | var stars = new string('*', 42); 106 | Assert.That(result, Is.EqualTo(Option.Some(stars))); 107 | } 108 | 109 | [Test] 110 | public void SelectManyWithNullSecondSelector() 111 | { 112 | var e = Assert.Throws(() => 113 | Option.Some(42).SelectMany(null, BreakingFunc.Of<_, _, _>())); 114 | Assert.That(e.ParamName, Is.EqualTo("secondSelector")); 115 | } 116 | 117 | [Test] 118 | public void SelectManyWithNullResultSelector() 119 | { 120 | var e = Assert.Throws(() => 121 | Option.Some(42).SelectMany<_, _, _>(BreakingFunc.Of<_, (bool, _)>(), null)); 122 | Assert.That(e.ParamName, Is.EqualTo("resultSelector")); 123 | } 124 | 125 | [Test] 126 | public void SelectMany() 127 | { 128 | var result = 129 | from n in Option.Some(42) 130 | from s in Option.Some(new string((char) n, n)) 131 | select string.Concat(n, s); 132 | 133 | var stars = new string('*', 42); 134 | Assert.That(result, Is.EqualTo(Option.Some("42" + stars))); 135 | } 136 | 137 | [Test] 138 | public void SelectManyNone() 139 | { 140 | var result = 141 | from n in Option.None() 142 | from s in Fail<(bool, string)>() 143 | select Fail(); 144 | 145 | Assert.That(result, Is.EqualTo(Option.None())); 146 | } 147 | 148 | [Test] 149 | public void SelectManySomeAndNone() 150 | { 151 | var result = 152 | from n in Option.Some(42) 153 | from s in Option.None() 154 | select Fail(); 155 | 156 | Assert.That(result, Is.EqualTo(Option.None())); 157 | } 158 | 159 | [Test] 160 | public void Cast() 161 | { 162 | var result = 163 | from int n in Option.Some((object) 42) 164 | select n; 165 | 166 | Assert.That(result, Is.EqualTo(Option.Some(42))); 167 | } 168 | 169 | [Test] 170 | public void CastInvalid() 171 | { 172 | Assert.Throws(() => 173 | { 174 | var _ = 175 | from string s in Option.Some((object) 42) 176 | select s; 177 | }); 178 | } 179 | 180 | [Test] 181 | public void CastNone() 182 | { 183 | var result = 184 | from int _ in Option.None() 185 | select Fail(); 186 | 187 | Assert.That(result, Is.EqualTo(Option.None())); 188 | } 189 | 190 | [Test] 191 | public void AllWithNullPredicate() 192 | { 193 | var e = Assert.Throws(() => 194 | Option.Some(42).All(null)); 195 | Assert.That(e.ParamName, Is.EqualTo("predicate")); 196 | } 197 | 198 | [Test] 199 | public void AllSomeTrue() 200 | { 201 | var result = Option.Some(42).All(n => n > 0); 202 | Assert.That(result, Is.True); 203 | } 204 | 205 | [Test] 206 | public void AllSomeFalse() 207 | { 208 | var result = Option.Some(42).All(n => n < 0); 209 | Assert.That(result, Is.False); 210 | } 211 | 212 | [Test] 213 | public void AllNone() 214 | { 215 | var result = Option.None().All(_ => Fail()); 216 | Assert.That(result, Is.True); 217 | } 218 | 219 | [Test] 220 | public void ToArraySome() 221 | { 222 | var result = Option.Some(42).ToArray(); 223 | Assert.That(result, Is.EqualTo(new[] { 42 })); 224 | } 225 | 226 | [Test] 227 | public void ToArrayNone() 228 | { 229 | var result = Option.None().ToArray(); 230 | Assert.That(result, Is.EqualTo(new int[0])); 231 | } 232 | 233 | [Test] 234 | public void ToListSome() 235 | { 236 | var result = Option.Some(42).ToList(); 237 | Assert.That(result, Is.EqualTo(new List { 42 })); 238 | } 239 | 240 | [Test] 241 | public void ToListNone() 242 | { 243 | var result = Option.None().ToList(); 244 | Assert.That(result, Is.EqualTo(new List())); 245 | } 246 | 247 | [Test] 248 | public void ToEnumerableSome() 249 | { 250 | var result = Option.Some(42).ToEnumerable(); 251 | Assert.That(result, Is.EqualTo(new[] { 42 })); 252 | } 253 | 254 | [Test] 255 | public void ToEnumerableNone() 256 | { 257 | var result = Option.None().ToList(); 258 | Assert.That(result, Is.EqualTo(Enumerable.Empty())); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /tests/Optuple.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 8 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/RegexTests.cs: -------------------------------------------------------------------------------- 1 | namespace Optuple.Tests 2 | { 3 | using System; 4 | using System.Text.RegularExpressions; 5 | using NUnit.Framework; 6 | using RegularExpressions; 7 | 8 | [TestFixture] 9 | public class RegexTests 10 | { 11 | [Test] 12 | public void NullThis() 13 | { 14 | var e = Assert.Throws(() => 15 | RegexExtensions.ToOption(null)); 16 | Assert.That(e.ParamName, Is.EqualTo("group")); 17 | } 18 | 19 | [Test] 20 | public void Success() 21 | { 22 | var match = Regex.Match("foo bar baz", @"\bbar\b"); 23 | var (ms, m) = match.ToOption(); 24 | Assert.That(ms, Is.True); 25 | Assert.That(m, Is.SameAs(match)); 26 | } 27 | 28 | [Test] 29 | public void Mismatch() 30 | { 31 | var (t, m) = Regex.Match(string.Empty, "foo").ToOption(); 32 | Assert.That(t, Is.False); 33 | Assert.That(m, Is.Null); 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------