1 /**
2     Functional try
3 */
4 module ddash.functional.try_;
5 
6 ///
7 unittest {
8     import std.algorithm: map, each;
9 
10     auto arr = [1, 2, 3];
11 
12     int f(int i) {
13         if (i % 2 == 1) {
14             throw new Exception("NOT EVEN!!!");
15         }
16         return i;
17     }
18 
19     auto result = arr
20         .map!(try_!f)
21         .map!(r => r
22             .match!(
23                 (int _) => "even",
24                 (Exception _) => "odd"
25             )
26         );
27 
28     assert(result.equal(["odd", "even", "odd"]));
29 }
30 
31 import ddash.common;
32 
33 /**
34     Creates a Try range out of an alias to a function that could throw.
35 
36     Executing a Try range will produce a `Expected!(T, Exception)`
37 
38     You may also call `.match` directly on the try range.
39 
40     See_Also:
41         `try_`
42 
43     Since:
44         0.0.1
45 */
46 struct Try(alias fun) {
47     import ddash.utils.expect;
48     import ddash.lang.types: isVoid;
49 
50     bool empty = false;
51     bool resolved = false;
52 
53     alias T = Expect!(typeof(fun()), Exception);
54 
55     private T result;
56 
57     private void resolve() {
58         if (resolved) {
59             return;
60         }
61         scope(exit) resolved = true;
62         try {
63             static if (isVoid!(T.Expected)) {
64                 fun();
65             } else {
66                 result = T.expected(fun());
67             }
68         } catch (Exception ex) {
69             result = unexpected(ex);
70         }
71     }
72 
73     @property T front() {
74         resolve;
75         return result;
76     }
77 
78     void popFront() nothrow {
79         scope(exit) empty = true;
80         resolve;
81     }
82 
83     /**
84         Pass two lambdas to the match function. The first one handles the success case
85         and the second one handles the failure case.
86 
87         Calling match will execute the try function if it has not already done so
88 
89         Params:
90             handlers = lamda that handles the success case
91             handlers = lambda that handles the exception
92 
93         Returns:
94             Whatever the lambas return
95     */
96     auto match(handlers...)() {
97         resolve;
98         return result.match!(
99             (T.Expected t) {
100                 static if (isVoid!(T.Expected)) {
101                     return handlers[0]();
102                 } else {
103                     return handlers[0](t);
104                 }
105             },
106             (T.Unexpected ex) {
107                 return handlers[1](ex);
108             }
109         );
110     }
111 }
112 
113 /**
114     Creates a range expression out of a throwing functions
115 
116     Since:
117         - 0.0.1
118 */
119 template try_(alias func) {
120     auto try_(Args...)(auto ref Args args) {
121         return Try!(() => func(args))();
122     }
123 }
124 
125 ///
126 unittest {
127     import std.typecons: Flag;
128 
129     void f0(Flag!"throws" throws) {
130         if (throws) {
131             throw new Exception("f0");
132         }
133     }
134     int f1(Flag!"throws" throws) {
135         if (throws) {
136             throw new Exception("f1");
137         }
138         return 0;
139     }
140 
141     auto f0_throws = try_!f0(Yes.throws);
142     auto f0_nothrows = try_!f0(No.throws);
143 
144     auto f1_throws = try_!f1(Yes.throws);
145     auto f1_nothrows = try_!f1(No.throws);
146 
147     auto g() {
148         try {
149             throw new Exception("hahah");
150         } catch (Exception ex) {
151             return ex;
152         }
153     }
154 
155     assert(!f0_throws.front.isExpected);
156     assert( f0_nothrows.front.isExpected);
157     assert(!f1_throws.front.isExpected);
158     assert( f1_nothrows.front.isExpected);
159 }