1 /*
2  * Copyright (C) 2023 Mai-Lapyst
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Affero General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Affero General Public License for more details.
13  * 
14  * You should have received a copy of the GNU Affero General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /** 
19  * Module to provide optionals for dlang.
20  * 
21  * License:   $(HTTP https://www.gnu.org/licenses/agpl-3.0.html, AGPL 3.0).
22  * Copyright: Copyright (C) 2023 Mai-Lapyst
23  * Authors:   $(HTTP codeark.it/Mai-Lapyst, Mai-Lapyst)
24  */
25 module ministd.optional;
26 
27 /// Exception when an Optional is None but a operation that assumes an Some was executed
28 class OptionalIsNoneException : Exception {
29     this (string s, string op) {
30         super("Optional " ~ s ~ " is none; operation " ~ op ~ " not permitted");
31     }
32 }
33 
34 /// ditto
35 alias OptionIsNoneException = OptionalIsNoneException;
36 
37 /// An optional of type `T`
38 struct Optional(T) {
39     private T value;
40     private bool _isSome = false;
41 
42     this(T value, bool isSome) @disable;
43 
44     private this(T value) {
45         this.value = value;
46         this._isSome = true;
47     }
48 
49     /**
50      * Takes the value out of the optional, leaving a None in it's place
51      * 
52      * Returns: the value the optional holds
53      * 
54      * Throws: $(REF OptionalIsNoneException) if the optional is a None instead of a Some
55      */
56     T take() {
57         if (!_isSome) {
58             throw new OptionalIsNoneException(.stringof, ".take()");
59         }
60         this._isSome = false;
61         return value;
62     }
63 
64     /** 
65      * Gets the value of the optional but leaving it as it.
66      * 
67      * Returns: the value the optional holds
68      * 
69      * Throws: $(REF OptionalIsNoneException) if the optional is a None instead of a Some
70      */
71     T get() {
72         if (!_isSome) {
73             throw new OptionalIsNoneException(.stringof, ".take()");
74         }
75         return value;
76     }
77 
78     /// Returns true if the optional is a None; false otherwise
79     bool isNone() {
80         return !_isSome;
81     }
82 
83     /// Returns true if the optional is a Some; false otherwise
84     bool isSome() {
85         return _isSome;
86     }
87 
88     /**
89      * Creates a None
90      * 
91      * Returns: A optional which is a None
92      */
93     static Optional none() {
94         return Optional();
95     }
96 
97     /**
98      * Creates a Some
99      * 
100      * Params:
101      *  value = the value to use for the Some
102      * 
103      * Returns: A optional which is a Some and holds the given value
104      */
105     static Optional some(T value) {
106         return Optional(value);
107     }
108 }
109 
110 /// ditto
111 alias Option = Optional;
112 
113 unittest {
114     Optional!int maybe_int = Optional!int.none();
115     try {
116         maybe_int.take();
117         assert(0, "Optional.take() should throw a OptionalIsNoneException if it is a None");
118     } catch (OptionalIsNoneException) {}
119 }
120 
121 unittest {
122     Optional!int maybe_int = Optional!int.some(42);
123     try {
124         auto i = maybe_int.take();
125         assert(i == 42, "Optional.take() should return the value hold by the Some");
126         assert(maybe_int.isNone(), "Optional should be a None after call to Optional.take()");
127     } catch (OptionalIsNoneException) {
128         assert(0, "Optional.take() should not throw a OptionalIsNoneException if it is a Some");
129     }
130 }