-
-
Notifications
You must be signed in to change notification settings - Fork 88
/
option.go
338 lines (280 loc) · 7.61 KB
/
option.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package mo
import (
"bytes"
"database/sql"
"database/sql/driver"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"reflect"
)
var optionNoSuchElement = fmt.Errorf("no such element")
// Some builds an Option when value is present.
// Play: https://go.dev/play/p/iqz2n9n0tDM
func Some[T any](value T) Option[T] {
return Option[T]{
isPresent: true,
value: value,
}
}
// None builds an Option when value is absent.
// Play: https://go.dev/play/p/yYQPsYCSYlD
func None[T any]() Option[T] {
return Option[T]{
isPresent: false,
}
}
// TupleToOption builds a Some Option when second argument is true, or None.
// Play: https://go.dev/play/p/gkrg2pZwOty
func TupleToOption[T any](value T, ok bool) Option[T] {
if ok {
return Some(value)
}
return None[T]()
}
// EmptyableToOption builds a Some Option when value is not empty, or None.
// Play: https://go.dev/play/p/GSpQQ-q-UES
func EmptyableToOption[T any](value T) Option[T] {
// 🤮
isZero := reflect.ValueOf(&value).Elem().IsZero()
if isZero {
return None[T]()
}
return Some(value)
}
// PointerToOption builds a Some Option when value is not nil, or None.
// Play: https://go.dev/play/p/yPVMj4DUb-I
func PointerToOption[T any](value *T) Option[T] {
if value == nil {
return None[T]()
}
return Some(*value)
}
// Option is a container for an optional value of type T. If value exists, Option is
// of type Some. If the value is absent, Option is of type None.
type Option[T any] struct {
isPresent bool
value T
}
// IsPresent returns false when value is absent.
// Play: https://go.dev/play/p/nDqIaiihyCA
func (o Option[T]) IsPresent() bool {
return o.isPresent
}
// IsAbsent returns false when value is present.
// Play: https://go.dev/play/p/23e2zqyVOQm
func (o Option[T]) IsAbsent() bool {
return !o.isPresent
}
// Size returns 1 when value is present or 0 instead.
// Play: https://go.dev/play/p/7ixCNG1E9l7
func (o Option[T]) Size() int {
if o.isPresent {
return 1
}
return 0
}
// Get returns value and presence.
// Play: https://go.dev/play/p/0-JBa1usZRT
func (o Option[T]) Get() (T, bool) {
if !o.isPresent {
return empty[T](), false
}
return o.value, true
}
// MustGet returns value if present or panics instead.
// Play: https://go.dev/play/p/RVBckjdi5WR
func (o Option[T]) MustGet() T {
if !o.isPresent {
panic(optionNoSuchElement)
}
return o.value
}
// OrElse returns value if present or default value.
// Play: https://go.dev/play/p/TrGByFWCzXS
func (o Option[T]) OrElse(fallback T) T {
if !o.isPresent {
return fallback
}
return o.value
}
// OrEmpty returns value if present or empty value.
// Play: https://go.dev/play/p/SpSUJcE-tQm
func (o Option[T]) OrEmpty() T {
return o.value
}
// ForEach executes the given side-effecting function of value is present.
func (o Option[T]) ForEach(onValue func(value T)) {
if o.isPresent {
onValue(o.value)
}
}
// Match executes the first function if value is present and second function if absent.
// It returns a new Option.
// Play: https://go.dev/play/p/1V6st3LDJsM
func (o Option[T]) Match(onValue func(value T) (T, bool), onNone func() (T, bool)) Option[T] {
if o.isPresent {
return TupleToOption(onValue(o.value))
}
return TupleToOption(onNone())
}
// Map executes the mapper function if value is present or returns None if absent.
// Play: https://go.dev/play/p/mvfP3pcP_eJ
func (o Option[T]) Map(mapper func(value T) (T, bool)) Option[T] {
if o.isPresent {
return TupleToOption(mapper(o.value))
}
return None[T]()
}
// MapNone executes the mapper function if value is absent or returns Option.
// Play: https://go.dev/play/p/_KaHWZ6Q17b
func (o Option[T]) MapNone(mapper func() (T, bool)) Option[T] {
if o.isPresent {
return Some(o.value)
}
return TupleToOption(mapper())
}
// FlatMap executes the mapper function if value is present or returns None if absent.
// Play: https://go.dev/play/p/OXO-zJx6n5r
func (o Option[T]) FlatMap(mapper func(value T) Option[T]) Option[T] {
if o.isPresent {
return mapper(o.value)
}
return None[T]()
}
// ToPointer returns value if present or a nil pointer.
// Play: https://go.dev/play/p/N43w92SM-Bs
func (o Option[T]) ToPointer() *T {
if !o.isPresent {
return nil
}
return &o.value
}
// MarshalJSON encodes Option into json.
func (o Option[T]) MarshalJSON() ([]byte, error) {
if o.isPresent {
return json.Marshal(o.value)
}
// if anybody find a way to support `omitempty` param, please contribute!
return json.Marshal(nil)
}
// UnmarshalJSON decodes Option from json.
func (o *Option[T]) UnmarshalJSON(b []byte) error {
if bytes.Equal(b, []byte("null")) {
o.isPresent = false
return nil
}
err := json.Unmarshal(b, &o.value)
if err != nil {
return err
}
o.isPresent = true
return nil
}
// MarshalText implements the encoding.TextMarshaler interface.
func (o Option[T]) MarshalText() ([]byte, error) {
return json.Marshal(o)
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (o *Option[T]) UnmarshalText(data []byte) error {
return json.Unmarshal(data, o)
}
// MarshalBinary is the interface implemented by an object that can marshal itself into a binary form.
func (o Option[T]) MarshalBinary() ([]byte, error) {
if !o.isPresent {
return []byte{0}, nil
}
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(o.value); err != nil {
return []byte{}, err
}
return append([]byte{1}, buf.Bytes()...), nil
}
// UnmarshalBinary is the interface implemented by an object that can unmarshal a binary representation of itself.
func (o *Option[T]) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return errors.New("Option[T].UnmarshalBinary: no data")
}
if data[0] == 0 {
o.isPresent = false
o.value = empty[T]()
return nil
}
buf := bytes.NewBuffer(data[1:])
dec := gob.NewDecoder(buf)
err := dec.Decode(&o.value)
if err != nil {
return err
}
o.isPresent = true
return nil
}
// GobEncode implements the gob.GobEncoder interface.
func (o Option[T]) GobEncode() ([]byte, error) {
return o.MarshalBinary()
}
// GobDecode implements the gob.GobDecoder interface.
func (o *Option[T]) GobDecode(data []byte) error {
return o.UnmarshalBinary(data)
}
// Scan implements the SQL sql.Scanner interface.
func (o *Option[T]) Scan(src any) error {
if src == nil {
o.isPresent = false
o.value = empty[T]()
return nil
}
// is is only possible to assert interfaces, so convert first
// https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#why-not-permit-type-assertions-on-values-whose-type-is-a-type-parameter
var t T
if tScanner, ok := interface{}(&t).(sql.Scanner); ok {
if err := tScanner.Scan(src); err != nil {
return fmt.Errorf("failed to scan: %w", err)
}
o.isPresent = true
o.value = t
return nil
}
if av, err := driver.DefaultParameterConverter.ConvertValue(src); err == nil {
if v, ok := av.(T); ok {
o.isPresent = true
o.value = v
return nil
}
}
return o.scanConvertValue(src)
}
// Value implements the driver Valuer interface.
func (o Option[T]) Value() (driver.Value, error) {
if !o.isPresent {
return nil, nil
}
return driver.DefaultParameterConverter.ConvertValue(o.value)
}
// leftValue returns an error if the Option is None, otherwise nil
//
//nolint:unused
func (o Option[T]) leftValue() error {
if !o.isPresent {
return optionNoSuchElement
}
return nil
}
// rightValue returns the value if the Option is Some, otherwise the zero value of T
//
//nolint:unused
func (o Option[T]) rightValue() T {
if !o.isPresent {
var zero T
return zero
}
return o.value
}
// hasLeftValue returns true if the Option represents a None state
//
//nolint:unused
func (o Option[T]) hasLeftValue() bool {
return !o.isPresent
}