package sugar

  1. Overview
  2. Docs

Source file s.ml

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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
(**
  Sugar module signatures.
 *)


(**
  Signatures for the dependencies used in Sugar's module builders.
*)
module Params = struct


(**
  A generic signature describing a monad.
*)
module type Monad = sig

  type 'a t
  (** A parametric type representing any OCaml value. *)

  val return: 'a -> 'a t
  (** Creates a constant value in this monad.  *)

  val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
  (** Waits for the conclusion of the monad in the left,
      and then, apply the unwrapped value to the function in the right.
   *)

end



(**
  Conventional module type to define the errors inside a project.
  This is how Sugar understands the error handling layer of a project.

  Like:
  {[
    module MyError = struct
      type error = Not_found | Invalid_arg of string
    end
  ]}

  This module might be used to create blocking or asynchronous error handling
  layers, using the Sugar functors, like:
  {[
    module MyResult = Sugar.Result.Make (MyError)

    module MyResult2 = Sugar.Promise.Make (Lwt) (MyError)
    module MyResult2 = MyResult.For (Lwt)
  ]}
*)
module type Error = sig

  type t
  (**
    This type describes the errors of your project. It's one of the main requirements
    to create a result monad.

    If you don't want to specify your errors upfront, you can still use something like [unit] or
    [string] as error type.
  *)

end



(**
  This signature describes an [Error] module that has some control over unexpected exceptions.

  If you want to handle unexpected exceptions as they appear, you should probably define
  an error case with the type [exn], like in the code below:
  {[
  module Error = struct
    type t =
      | Because_reasons
      | Unexpected of exn

    let panic e = Unexpected e
  end
  ]}
 *)
module type Strict_error = sig
  include Error

  val panic : exn -> t
  (**
    When an exception is detected, this module can either terminate the process with a proper
    message or chose convert the error to the type {!t}.
  *)

end


(**
  A monad that provides some awareness about unexpected exceptions.

  This module is related to {{!Sugar.S.Params.Strict_error} Strict_error}.
*)
module type Strict_monad = sig
  include Monad

  val catch : (unit -> 'a t) -> (exn -> 'a t) -> 'a t
  (**
    Checks if the monad returned by the thunk raised an exception, and applies
    the given error handler if necessary.

    This function has intentionally the
    exact signature as [Lwt.catch]. This means the [Lwt] module is already a [Strict_monad]:
    {[
    let _ =
      (module Lwt: Sugar.Params.Strict_monad)
    ]}
   *)
end


end

open Params



module type Promise = sig

  type error
  (** Error definition imported from your project *)

  type 'a value = ('a, error) Result.result
  (** An alias for [Pervasives.result] that can only work with errors of your project. *)

  type 'a monad
  (**
    This type is an alias to the underlining monad used to create your [Promise] module.
    In other words, it points to the main type of your threading library ([Lwt.t] for Lwt,
    or [Async.Std.Deferred.t] for Async).
   *)

  type 'a result = 'a value monad
  (**
    This type describes a result monad inside a project.

    For example, if the concrete module is built on top of Lwt,
    a value of type [unit result] will be translated as [(unit, error) Pervasives.result Lwt.t].
  *)

  val (>>=): 'a result -> ('a -> 'b result) -> 'b result
  (**
    This combinator is also called [bind].

    It can be used to chain sequential operations with the current monad.
    If the computation in the left failed, the operator will propagate the error,
    skipping the function completely.
  *)

  val bind:  'a result -> ('a -> 'b result) -> 'b result
  (**
     Similar to {{!Sugar__S.Result.bind} S.Result.bind}
   *)

   val bind_unless: 'a result -> (error -> 'a result) -> 'a result
  (**
     Similar to {{!Sugar__S.Result.bind_unless} S.Result.bind_unless}
   *)

   val map:  'a result -> ('a -> 'b) -> 'b result
   (**
      Similar to {{!Sugar__S.Result.map} S.Result.map}
    *)

    val return: 'a -> 'a result
    (**
       Similar to {{!Sugar__S.Result.return} S.Result.return}
    *)


  val throw: error -> 'a result
  (**
    Similar to {{!Sugar__S.Result.throw} S.Result.throw}
  *)

  module Infix : sig

    val (>>|): 'a result -> ('a -> 'b) -> 'b result
    (**
      {{!Sugar.S.Result.Infix.(>>|)} More...}
    *)

    val (<$>): ('a -> 'b) -> 'a result -> 'b result
    (** Applicative combinator for map *)

    val (<*>): ('a -> 'b) result -> 'a result -> 'b result
    (** Applicative combinator for parallel execution of function and operand *)

    val (>>>=): 'a monad -> ('a -> 'b monad) -> 'b monad
    (** An alias for UserMonad.(>>=)

        This combinator provides direct access to the monad used to create
        this module. *)

    val (>---------): 'a result -> (error -> 'a result) -> 'a result
    (**
      "Broom" combinator

      This is an alias for the function {!bind_unless}. It provides syntatic
      sugar to create error handlers in a more intuitive way.

      There's a secret message behind the form of this combinator.
      It has the same number of characters sufficient for the whole block
      in the next line. For example:

      {[
      let program1 () =
        do_something ()
        >---------
        ( fun e ->
          return ()
        )

      let program2 () =
        do_something ()
        >---------
        ( function
          e -> return ()
        )
      ]}
    *)

    val ( >> ) : unit result -> 'b result Lazy.t -> 'b result
    (**
      {{!Sugar.S.Result.Infix.(>>)} More...}
    *)

    val (>>>): 'a result -> 'b result Lazy.t -> 'b result
    (**
      {{!Sugar.S.Result.Infix.(>>>)} More...}
    *)

  end

  val unwrap: 'a value monad -> 'a monad
  (**
    Unwraps the successful value as a normal value in the threading monad.
    If the value is not successful, it will raise an [Invalid_arg] exception.
  *)

  val unwrap_or: (error -> 'a monad) -> 'a value monad -> 'a monad
  (**
    Unwraps the successful value as a value in the threading monad.
    Different from [unwrap], you can assign an error handler to be
    executed if the computation failed.
  *)

  val expect: 'a value monad -> string -> 'a monad
  (**
    Extracts a successful value from an computation, or raises and [Invalid_arg]
    exception with a customized error message.
  *)

end

(**
  This interface specifies an error handling layer for monadic computations.

  Sugar value modules work with any monad.

  {[
    module MyMonad = struct
      type 'a monad = 'a Lwt.t
      let return = Lwt.return
      let (>>=) = Lwt.bind
    end
    module MyResult = Sugar.Promise.Make (MyMonad) (MyError)
  ]}
*)
module type Strict_promise = sig
  include Promise

  (**
    Disable exception handling. Open this module with you don't want to catch exceptions.
  *)
  module NoExceptions : Promise
    with type error := error
    and type 'a monad := 'a monad

end


(**
  Common definitions for the default result monad.
*)
module type Result_partials = sig

  type error
  (** Error definition from your project *)


  type 'a result = ('a, error) Result.result
  (** An alias for the result type in the stdlib *)

  val bind:  'a result -> ('a -> 'b result) -> 'b result
  (** Apply the binding only if the computation was successful.
      You can use the operator {{!(>>=)} >>=} instead of this function for syntatic sugar *)

  val bind_unless: 'a result -> (error -> 'a result) -> 'a result
  (** Apply the binding only if the computation failed.

      Notice that an error handler must be provided, and this handler
      must throw an error or provide an equivalent for the result type of the
      previous computation.

      You can use the operator {{!Infix.(>---------)} >---------} instead of this function for syntatic sugar *)

  val map:  'a result -> ('a -> 'b) -> 'b result
  (**
    Apply a function to the result of a successful computation. This function
    makes it ease to work with non error aware functions.

    Example:
    {[
    open Sugar.Option

    let twenty =
     map (Some 10) (fun n -> n + n)
    ]}

    You could also use the combinator {{!Infix.(>>|)} >>|} for syntatic sugar. *)

  val return: 'a -> 'a result
  (** Return a value in a successful computation.
      This function should be used with its counterpart, [throw] *)


  val throw: error -> 'a result
  (**
    Return an error as the result of a computation.

    Like the [return] function, [throw] helps you hide the internals of your
    result type and keep a clean code.

    If you are still at the beginning of your project, and don't have your
    errors defined yet, this function still is a great help. For example,
    the code bellow have the same usage as the function [failwith], but is a lot
    safer.

    {[
     module MyResult = Sugar.MakeResult (struct error = string end)
     open MyResult
     let run (): int result =
       if true then
         return 10
       else
         throw "something bad happend"
    ]}

    You could also not describe your errors at all for some time, and
    use the {!Sugar.Option} module to create error aware computations, like:

    {[
     open Sugar.Option
     let run (): string result =
       if true then
         return "hello world"
       else
         throw ()
    ]} *)

  module Infix : sig

    val (>---------): 'a result -> (error -> 'a result) -> 'a result
    (**
      Combinator used to introduce an error handler block to "clean errors".

      There's a secret message behind the form of this combinator.
      It has the same number of characters sufficient for the whole block
      in the next line. For example:

      {[
      let program1 () =
       do_something ()
       >---------
       ( fun e ->
         return ()
       )

      let program2 () =
       do_something ()
       >---------
       ( function
         e -> return ()
       )
      ]}

      So beyond the clean aesthetics similar to markdown, we are
      implying that a developer should never handle errors in an open
      anonymous function. *)


    val (>>|): 'a result -> ('a -> 'b) -> 'b result
    (**
      Combinator for map with semantic similar to bind

      As its name sugests, this is an alias for the function {{!map} map}.
      Its intended use is to help integrate with functions that are not error
      aware.

      For example, considere the function [let double x = x + x] in the code
      fragments bellow:

      {[
       open Sugar.Option

       let twenty =
         match Some 10 with
         | None -> None
         | Some n -> Some (double n)

        let using_bind_combinator =
         Some 10
         >>=
         ( fun n ->
           return (double n)
         )

       let using_map_combinator =
         Some 10
         >>| double
      ]}
      *)


    val (<$>): ('a -> 'b) -> 'a result -> 'b result
    val (<*>): ('a -> 'b) result -> 'a result -> 'b result


    val (>>>): 'a result -> 'b result Lazy.t -> 'b result
    (**
      Ignore operator.

      This is a type of semicolon combinator that can be used to ignore any
      result. It is supposed to be used with the keyword lazy as in [>>>lazy].
      It can be used to replace the creation of an anonymous function to discard
      the previous value, like:

      {[
      (* instead of writing *)
      do_something ()
      >>=
      ( fun _ ->
        do_something_else ()
      )

      (* you can write *)
      ( do_something ()      ) >>>lazy
      ( do_something_else () )
      ]}

      For more information, look at {!(>>)}.
    *)

    val (>>): unit result -> 'b result Lazy.t -> 'b result
    (**
      A sequential semicolon combinator.

      This operator is supposed to be used with the keyword lazy, as [>>lazy].
      To reduce cognitive load, it's interesting to treat [>>lazy] as one word.

      To chain monadic expressions that are not related to each other.
      Instead of writing code like:

      {[
      puts "hello"
      >>=
      ( fun () ->
        puts "monadic"
      )
      >>=
      ( fun () ->
        puts "world"
      )
      ]}

      You can use these form:
      {[
      ( puts "hello"   ) >>lazy
      ( puts "monadic" ) >>lazy
      ( puts "world"   )
      ]}

      Or this alternative form that has exactly the structure as the example above:
      {[
      ( puts "hello"   )>>lazy(
        puts "monadic" )>>lazy(
        puts "world"
      )
      ]}

      Notice that the expression on the left must be of type [unit result]. If you wish to
      ignore other type of [result]s, use {{!(>>>)} >>>lazy}
    *)

  end


  val (>>=): 'a result -> ('a -> 'b result) -> 'b result
  (**
    Bind combinator

    If the computation in the left is successful, the operator will
    Take the inner value and feed it to the function in the right. This is an
    alias for the function [bind].

    If the computation in the left failed, the operator will propagate the error,
    skipping the function completely.
  *)


  val unwrap: 'a result -> 'a
  (**
    Unwraps the successful result as a normal value in the threading monad.
    If the value is not successful, it will raise an Invalid_arg exception.
  *)


  val unwrap_or: (error -> 'a) -> 'a result -> 'a
  (**
    Unwraps the successful result as a value in the threading monad.
    Different from [unwrap], you can assign an error handler to be
    executed if the computation failed. Example:
    {[
    let run () =
      get_data ()
      |> unwrap_or (fun _ -> "default")
    ]}
  *)


  val expect: 'a result -> string -> 'a
  (**
    Extracts a successful value from an computation, or raises and Invalid_arg
    exception with the defined parameter.
  *)

end


(**
  The signature for the default result monad.
*)
module type Result = sig
  include Result_partials

  (**
    Create a new result module based on the current one, but wrapped around a monad.
  *)
  module For : functor (UserMonad:Monad) -> Promise
    with type error := error
    and type 'a monad := 'a UserMonad.t
end


(**
  The signature for a result monad that has some awareness about unexpected exceptions.
*)
module type Strict_result = sig
  include Result_partials

  (**
    Create a new result module based on the current one, but wrapped around a monad.
  *)
  module For : functor (UserMonad:Params.Strict_monad) -> Strict_promise
    with type error := error
    and type 'a monad := 'a UserMonad.t


  (**
    Disable exception handling
  *)
  module NoExceptions : Result
    with type error := error
end
OCaml

Innovation. Community. Security.