1 module vtypechoice.optional;
2 
3 import core.lifetime : forward;
4 import std.sumtype;
5 
6 alias isOptional(T) = imported!"std.traits".isInstanceOf!(Optional, T);
7 alias isSome(T) = imported!"std.traits".isInstanceOf!(Some, T);
8 enum isNone(T) = is(imported!"std.traits".Unqual!T == None);
9 
10 struct Optional(T)
11 {
12 	import core.lifetime : forward;
13 	import std.traits : CopyConstness, isInstanceOf, Unqual;
14 	import std.meta : AliasSeq, IndexOf = staticIndexOf, Map = staticMap;
15 
16 
17 	alias Some = .Some!T;
18 	alias None = .None;
19 
20 
21 	static some(U)(U s)
22 	{
23 		static if (.isSome!U)
24 		{
25 			static assert(.isOptional!T, "Cannot convert Some!("~U.stringof~") to "~Some.stringof);
26 			// lets perform: Optional!(Optional!int).some(Some!int(3));
27 			CopyConstness!(U, Optional) res = { st: Some(T.some(s.get)) };
28 		}
29 		else
30 			CopyConstness!(U, Optional) res = { st: (CopyConstness!(U, Some)(s)) };
31 
32 		return res;
33 	}
34 
35 
36 	static some()(None)
37 	{
38 		static assert(.isOptional!T, "Cannot convert Some!(None) to "~Some.stringof);
39 		Optional res = { st: Some(T.none()) };
40 
41 		return res;
42 	}
43 
44 
45 	static none()
46 	{
47 		Optional res = { st: .none() };
48 
49 		return res;
50 	}
51 
52 
53 	bool opEquals(in Optional rhs) scope const
54 	{
55 		return st == rhs.st;
56 	}
57 
58 
59 	bool opEquals(in None _) scope const
60 	{
61 		return isNone();
62 	}
63 
64 
65 	bool opEquals(U)(in U rhs) scope const
66 		if (.isSome!U)
67 	{
68 		// allow comparing only with Some
69 		// turning opEquals into a template forces it to never cast to alias this
70 		// since Some alias to their template parameter type,
71 		// restricting the opEquals' parameter to Some would not evaluate
72 		// correctly if for example Some!(Some!T) was provided, as it would cast it
73 		// to Some!T making it the wrong comparison to perform
74 		enum errmsg = "Cannot compare "~U.stringof~" with "~Optional.stringof;
75 
76 		// ensure this is a valid type comparison
77 		// fail here for a better error message, otherwise it'll fail with
78 		// a generic SumType's message
79 		static assert(__traits(compiles, T.init == rhs.get), errmsg);
80 		return st.match!(
81 			(in None _) => false,
82 			(in T ok) => ok == rhs.get,
83 		);
84 	}
85 
86 
87 	auto ref opAssign(U)(U rhs) scope return
88 		if (.isSome!U)
89 	{
90 		enum errmsg = "Cannot assign "~U.stringof~" to "~Optional.stringof;
91 		static if (.isSome!(typeof(U.get)))
92 			static assert (isSome!T, errmsg);
93 
94 		static assert(__traits(compiles, () { T some; some = rhs.get; }), errmsg);
95 
96 		st = rhs;
97 
98 		return this;
99 	}
100 
101 
102 	auto ref opAssign(None) scope return
103 	{
104 		st = None();
105 
106 		return this;
107 	}
108 
109 
110 	bool isNone() scope const
111 	{
112 		return st.match!((in value) => .isNone!(typeof(value)));
113 	}
114 
115 	bool isSome() scope const
116 	{
117 		return !isNone();
118 	}
119 
120 
121 	  // ===================
122 	 // # Optional as range #
123 	// ===================
124 
125 	alias back = front;
126 	alias empty = isNone;
127 
128 	auto ref inout(T) front() scope return inout
129 	{
130 		return this.unwrap();
131 	}
132 
133 	alias length = isSome;
134 
135 
136 	alias opDollar(size_t dim : 0) = length;
137 
138 	ref opIndex() scope return
139 	{
140 		return this;
141 	}
142 
143 	ref inout(T) opIndex(size_t index) scope return inout
144 		in (index < length)
145 	{
146 		return this.unwrap();
147 	}
148 
149 	ref opIndex(size_t[2] dim) scope return
150 		in (dim[0] <= dim[1])
151 		in (dim[1] <= length)
152 	{
153 		return this;
154 	}
155 
156 	size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) scope return
157 	{
158 		return [start, end];
159 	}
160 
161 
162 	alias popBack = popFront;
163 	void popFront() scope { st = .none(); }
164 
165 
166 	inout(Optional) save() scope inout { return this; }
167 
168 
169 	SumType!(Some, None) st;
170 	alias st this;
171 }
172 
173 ///
174 version(vtypechoice_unittest)
175 @safe pure nothrow @nogc unittest
176 {
177 	alias O = Optional!int;
178 
179 	assert(O.some(3) == Some!int(3));
180 	assert(O.none() == None());
181 
182 	assert(O.some(3).match!(
183 		(None _) => false, // use Some/None to match
184 		(int) => true,   // or use the underlying type to match
185 	));
186 
187 	assert(O.init == O.init);
188 	assert(O.some(0) != O.none());
189 
190 	static assert(!__traits(compiles, O.init = Some!(Some!int).init));
191 	static assert(!__traits(compiles, Optional!O.init = Some!int.init));
192 	static assert(!__traits(compiles, O.init = Some!short.init)); // TODO: should this be allowed?
193 
194 	static assert(!__traits(compiles, O.some(none)));
195 	static assert( __traits(compiles, Optional!O.some(none)));
196 	static assert( __traits(compiles, Optional!(Optional!O).some(none.some)));
197 	static assert( __traits(compiles, Optional!(Optional!O).some(10.some.some)));
198 	static assert(!__traits(compiles, Optional!(Optional!O).some(10.some.some.some)));
199 	static assert(!__traits(compiles, Optional!(Optional!O).some(10.some)));
200 
201 	() @trusted
202 	{
203 		assert((O.some(3) = 10.some) == 10.some);
204 		assert((O.some(3) = none) == none);
205 	}
206 	();
207 }
208 
209 
210 struct Some(T)
211 {
212 	T get;
213 	alias get this;
214 }
215 
216 auto some(T, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(T value)
217 {
218 	enum untilFunName = ()
219 	{
220 		for (size_t i; i + funName.length < prettyFun.length; i++)
221 			if (prettyFun[i .. i + funName.length] == funName)
222 				return i;
223 
224 		return 0;
225 	} ();
226 
227 	static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Optional!U, U))
228 		return Type.some(value);
229 	else
230 		return Some!T(value);
231 }
232 
233 ///
234 version(vtypechoice_unittest)
235 @safe pure nothrow @nogc unittest
236 {
237 	static assert(__traits(compiles, () => 10.some));
238 	static assert(is(typeof(() { return 10.some; } ()) == Some!int));
239 	static assert(__traits(compiles, function Optional!int () => 10.some));
240 }
241 
242 
243 
244 struct None {}
245 
246 auto none(string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)()
247 {
248 	enum None n = {};
249 
250 	enum untilFunName = ()
251 	{
252 		for (size_t i; i + funName.length < prettyFun.length; i++)
253 			if (prettyFun[i .. i + funName.length] == funName)
254 				return i;
255 		return 0;
256 	} ();
257 
258 	static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Optional!U, U))
259 		return Type.none();
260 	else
261 		return n;
262 }
263 
264 ///
265 version(vtypechoice_unittest)
266 @safe pure nothrow @nogc unittest
267 {
268 	static assert(__traits(compiles, () => none));
269 	static assert(is(typeof(() { return none; } ()) == None));
270 
271 	static assert(__traits(compiles, function Optional!int () => none));
272 }
273 
274 
275   // ====================================
276  // # Optional's functional operations #
277 // ====================================
278 
279 auto andThen(alias pred, T)(auto ref scope Optional!T optional)
280 	if (isOptional!(typeof(pred(T.init))))
281 {
282 	alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)).Types[0]);
283 
284 	return optional.match!(
285 		(in None _) => Optional!Type.none(),
286 		(ref scope T value) => pred(value),
287 	);
288 }
289 
290 ///
291 version(vtypechoice_unittest)
292 @safe pure nothrow @nogc unittest
293 {
294 	alias O = Optional!int;
295 	assert(O.none.andThen!(i => O.some(i + 3)) == none);
296 	assert(O.some(7).andThen!(i => O.some(i + 3)) == 10.some);
297 }
298 
299 
300 auto andThen(alias pred, T)(auto ref scope Optional!T optional)
301 	if (isSome!(typeof(pred(T.init))) || isNone!(typeof(pred(T.init))))
302 {
303 	static if (isSome!(typeof(pred(T.init))))
304 		alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)))[0];
305 	else
306 		alias Type = T;
307 
308 	return optional.match!(
309 		(in None _) => Optional!Type.none(),
310 		(ref scope T value) { Optional!Type opt = { st: (pred(value)) }; return opt; },
311 	);
312 }
313 
314 ///
315 version(vtypechoice_unittest)
316 @safe pure nothrow @nogc unittest
317 {
318 	alias O = Optional!int;
319 	assert(O.none.andThen!(i => some(i + 3)) == none);
320 	assert(O.some(7).andThen!(i => none) == none);
321 }
322 
323 
324 auto andThen(alias pred, T)(auto ref scope Optional!T optional)
325 	if (!isOptional!(typeof(pred(T.init)))
326 		&& !isSome!(typeof(pred(T.init)))
327 		&& !isNone!(typeof(pred(T.init))))
328 {
329 	alias Type = typeof(pred(T.init));
330 
331 	return forward!optional.match!(
332 		(in None _) => Optional!Type.none(),
333 		(ref scope T value) => Optional!Type.some(pred(value)),
334 	);
335 }
336 
337 ///
338 version(vtypechoice_unittest)
339 @safe pure nothrow @nogc unittest
340 {
341 	alias O = Optional!int;
342 	assert(O.none.andThen!(i => i + 3) == none);
343 	assert(O.some(7).andThen!(i => i + 3) == 10.some);
344 }
345 
346 
347 Optional!T flatten(T)(auto ref scope Optional!(Optional!T) optional)
348 {
349 	return forward!optional.match!(
350 		(in None _) => Optional!T.none(),
351 		(in value) => value,
352 	);
353 }
354 
355 ///
356 version(vtypechoice_unittest)
357 @safe pure nothrow @nogc unittest
358 {
359 	alias O = Optional!(Optional!int);
360 	static assert(is(typeof(O.init.flatten()) == Optional!int));
361 
362 	assert(O.some(3.some).flatten() == 3.some);
363 	assert(O.none.flatten() == none);
364 }
365 
366 
367 template fmap(alias pred)
368 {
369 	auto fmap(T)(auto ref scope Optional!T optional)
370 	{
371 		alias Type = typeof(pred(T.init));
372 		return optional.match!(
373 			(in None _) => Optional!Type.none(),
374 			(ref scope T value) => Optional!Type.some(pred(value)),
375 		);
376 	}
377 }
378 
379 ///
380 version(vtypechoice_unittest)
381 @safe pure nothrow @nogc unittest
382 {
383 	assert(Optional!int.some(3).fmap!(n => n + 7) == 10.some);
384 
385 	import std.algorithm: equal, map;
386 	import std.range : only;
387 	assert(Optional!int.some(3).only.map!(fmap!(n => n + 7)).equal(10.some.only));
388 }
389 
390 
391 template fmapOr(alias pred)
392 {
393 	auto fmapOr(U, T)(auto ref scope Optional!T optional, auto ref scope U other)
394 	{
395 		// simmulate an implicit cast to pred's return type
396 		// if the return type is know to be ulong and an int is provided,
397 		// the program would compile and run if U was written as size_t
398 		// however, as it isn't, it would fail because !is(size_t : int)
399 		// by placing Ok!T first the return type will be pred's return type
400 		// forcing other to be implicitly castable to it
401 		return optional.match!(
402 			(ref scope Some!T value) => pred(value.get),
403 			(in None _) => other,
404 		);
405 	}
406 }
407 
408 ///
409 version(vtypechoice_unittest)
410 @safe pure nothrow @nogc unittest
411 {
412 	auto x = Optional!string.some("foo");
413 	assert(x.fmapOr!(str => str.length)(42) == 3);
414 
415 	auto y = Optional!string.none();
416 	assert(y.fmapOr!(str => str.length)(42) == 42);
417 }
418 
419 
420 template fmapOrElse(alias pred, alias orElse)
421 {
422 	auto fmapOrElse(T)(auto ref scope Optional!T optional)
423 		if (is(typeof(orElse()) : typeof(pred(T.init))))
424 	{
425 		// see: fmapOr
426 		return optional.match!(
427 			(ref scope Some!T value) => pred(value.get),
428 			(in None _) => orElse(),
429 		);
430 	}
431 }
432 
433 ///
434 version(vtypechoice_unittest)
435 @safe pure nothrow @nogc unittest
436 {
437 	auto x = Optional!string.some("foo");
438 	assert(x.fmapOrElse!(str => str.length, () => 42) == 3);
439 
440 	auto y = Optional!string.none();
441 	assert(y.fmapOrElse!(str => str.length, () => 42) == 42);
442 }
443 
444 
445 auto ref handle(alias pred, T)(return auto ref scope Optional!T optional)
446 {
447 	optional.match!(
448 		(in None _) {},
449 		(ref scope T value) { cast(void) pred(value); },
450 	);
451 
452 	return optional;
453 }
454 
455 ///
456 version(vtypechoice_unittest)
457 @safe pure nothrow @nogc unittest
458 {
459 	assert(Optional!int.some(3).handle!((ref n) => n += 7) == 10.some);
460 	assert(Optional!int.some(3).handle!((ref n) => n + 7) == 3.some);
461 	assert(Optional!int.none.handle!((ref n) => n = 0) == .none);
462 }
463 
464 
465 bool has(T)(auto ref scope Optional!T optional, in T other)
466 {
467 	return optional.match!(
468 		(in None _) => false,
469 		(in T value) => value == other,
470 	);
471 }
472 
473 version(vtypechoice_unittest)
474 @safe pure nothrow @nogc unittest
475 {
476 	assert(Optional!int.some(3).has(3));
477 	assert(!Optional!int.some(3).has(0));
478 	assert(!Optional!int.none.has(3));
479 }
480 
481 
482 ref or(T)(return auto ref scope Optional!T optional, return auto ref scope Optional!T other)
483 {
484 	return optional.isSome() ? optional : other;
485 }
486 
487 ///
488 version(vtypechoice_unittest)
489 // @safe but @trusted due to assign
490 @trusted pure nothrow @nogc unittest
491 {
492 	auto a = Optional!int.some(3);
493 	auto b = Optional!int.none();
494 	assert(a.or(b) is a);
495 	assert(b.or(a) is a);
496 
497 	a = Optional!int.none();
498 	b = Optional!int.none();
499 	assert(a.or(b) is b);
500 
501 	a = Optional!int.some(3);
502 	b = Optional!int.some(10);
503 	assert(a.or(b) is a);
504 }
505 
506 
507 auto ref orElse(alias pred, T)(return auto ref scope Optional!T optional)
508 	if (is(typeof(pred()) == Optional!T))
509 {
510 	return optional.match!(
511 		(in Some!T) => optional,
512 		(in None _) => pred(),
513 	);
514 }
515 
516 ///
517 version(vtypechoice_unittest)
518 @safe pure nothrow @nogc unittest
519 {
520 	// source: https://doc.rust-lang.org/std/option/enum.Option.html#method.or_else
521 	alias O = Optional!string;
522 
523 	auto nobody = function O() => none;
524 	auto vikings = function O() => "vikings".some;
525 
526 	assert(O.some("barbarians").orElse!vikings == "barbarians".some);
527 	assert(O.none.orElse!vikings == "vikings".some);
528 	assert(O.none.orElse!nobody == none);
529 }
530 
531 
532 auto ref inout(T) unwrap(T)(auto ref scope inout Optional!T optional)
533 {
534 	return optional.match!(
535 		function ref inout(T) (in inout None _) => assert(0),
536 		function ref inout(T) (return ref inout(T) value) => value,
537 	);
538 }
539 
540 version(vtypechoice_unittest)
541 @safe pure nothrow @nogc unittest
542 {
543 	assert(Optional!int.some(3).unwrap() == 3);
544 
545 	static assert(!__traits(compiles, &Optional!int.some(0).unwrap())); // not an lvalue
546 	immutable opt = Optional!int.some(0);
547 	static assert(__traits(compiles, &opt.unwrap())); // ok lvalue
548 
549 	static assert(is(typeof(opt.unwrap()) == immutable int));
550 }
551 
552 
553 
554   // =====================
555  // # Optional as a range #
556 // =====================
557 
558 @safe pure nothrow @nogc unittest
559 {
560 	static assert(__traits(compiles, () => Optional!int.init[0]));
561 
562 	auto r = Optional!int.some(3);
563 
564 	assert(r[0] == 3);
565 	assert(r[0 .. $] is r);
566 
567 
568 	import std.algorithm.comparison : equal;
569 	import std.range : only;
570 
571 	assert(r.equal(3.only));
572 	assert(r[0 .. $].equal(3.only));
573 	assert(r.length == 1);
574 
575 
576 	import std.algorithm.iteration : map, joiner;
577 
578 	assert(Optional!int.none.map!(n => n + 1).empty);
579 	assert(r.only.joiner.front == 3);
580 }