Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Monads everywhere #501

Open
wants to merge 10 commits into from

6 participants

@c-cube
Owner

Ok, now this is a bigger PR than usual. I started it after I looked at #10, for monads like 'a Lwt.t. It adds monad instances to batSeq, batList and batLazyList, adds a BatInterfaces.Traversable module signature that looks like

module type Traversable = sig
  type 'a t  (* container *)

  type 'a m (* monad *)

  val mapM : ('a -> 'b m) -> 'a t -> 'b t m

  val foldM : ('a -> 'b -> 'a m) -> 'a -> 'b t -> 'a m

  val sequence : 'a m t -> 'a t m
end

and functors Traversable that take a BatInterfaces.Monad and return a traversable for this monad and container, to aforementioned modules as well as BatEnum. For BatEnum, a similar WithMonad functor was already present so I modified it, but didn't rename it to preserve backward compatibility.

@UnixJunkie
Collaborator

I'm very interested by all this.
I'll try to read all the code for lists when I have some time.

@hcarty

The mapM naming scheme breaks the general standard of using underscores rather than camel case for value names. Would map_m be better, or, since the functions are in their own submodules anyway, use map without the suffix?

@c-cube
Owner

@hcarty map_m and fold_m seem reasonable, but I'm not sure we should abbreviate to map because it's not to be confused with the regular map function. The names are inspired from Haskell's library, but underscores seem fine.

@UnixJunkie
Collaborator

Maybe momap for monadic map? :)

@UnixJunkie
Collaborator

mmap is for system programming on Linux so that's not advisable
(mmap, munmap - map or unmap files or devices into memory)

@hcarty

@UnixJunkie I prefer the _m suffix over a prefix for readability purposes.

@c-cube Given that the functions are already in their own module is name confusion a significant issue? Or do you intend for these functions to be included directly in the affected modules (ex. Lwt_seq.map_m rather than Lwt_seq.Traverse.map_m)?

@c-cube
Owner

well, map_m and fold_m don't have exactly the same signature as usual map and fold functions, so I think it's fair they have a slightly different name. Typing two more chars shouldn't be too much of a problem ;)

@hcarty

Fair point. I'm sold on the _m suffix now, for what that's worth!

@c-cube c-cube referenced this pull request
Open

LWT support #10

@c-cube
Owner

I have no idea why List did not contain a monad instance before, it seems very strange (concat_map or bind is a very useful combinator!). I think this should be in the next release, if possible, at least the Monad instances (maybe not Traversable)...

@c-cube
Owner

Well, I still think having monadic bind instances for containers like List is very important. Should'nt we merge this?

@rgrinberg
Collaborator

+1 from me. Where's filter_m though?

@c-cube
Owner

@rgrinberg what would its type be? Something like this, I guess:

val filter_m : ('a -> bool) -> 'a t -> 'a t
@rgrinberg
Collaborator

I was thinking more of:

val filter_m: ('a -> bool m) -> 'a t -> 'a t

But looks like I'm full of shit because for a second I confused your functions with the usual generic monadic functions (from Control.Monad) with your monad derived traversable instances. Ignore me...

@UnixJunkie
Collaborator

in bind you define another bind function, that's quite ugly.
In batteries code there is a lot of loop functions, in case you don't have a better name for it.

@UnixJunkie
Collaborator

Can't this be merged?

@c-cube
Owner

I hope so! :)
I merged it in my own branch, but of course that's not the same thing at all.

@gasche
Owner

Can't this be merged?

Did someone (else than @c-cube) review the code, and do all the new or modified functions have tests?

@UnixJunkie
Collaborator

It would have been nice if the pull request was cut into smaller pieces which would have been smaller,
hence faster to review and easier to accept.

@c-cube
Owner

Well, I can still split it into smaller PRs if that's required (starting with the modification of Enum, then add List, Seq and LazyList separately).

@UnixJunkie
Collaborator

From what I read and understand, only BatList has new tests.
And not even for all the new functionalities: only the monadic bind and return
are exercised by tests.
I suggest the following pull request should be closed, and reopened into several smaller ones
that we can polish and integrate one after the other.

@gasche
Owner

For what it's worth, I don't think the patch is that large. But I don't have time for a review, so it's up to whoever is ready to do the work (possibly adding tests as well).

@hcarty

A visual review looks ok to me. I have not tested the path itself though.

@bluddy

Um... nothing's happened with this since April?

@gasche
Owner

@bluddy if you want to help the PR move forward, there is a rather easy way to do so: write tests for (some of the) extra functions, to help the PR satisfy the requirement that patches come with sufficient testing.

@gasche
Owner

For the record, I just started looking at this PR again, writing tests to eventually be done with it. I'm in the List Traversable instance, and I found a small bug:

let rec map_m f l = match l with
| [] -> M.return []
| x::l' ->
  map_m f l' >>= fun l' ->
  f x >>= fun x ->
  M.return (x::l')

This will perform the side-effects right-to-left instead of the expected left-to-right (be it only to match with List.map's semantics). It needs to be rewritten as (I did this in my local branch):

  f x >>= fun x ->
  map_m f l' >>= fun l' ->
  M.return (x::l')

(and then probably stick an accumulator and List.rev to make this tailrec)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 29, 2013
  1. @c-cube

    monad implementation for List

    c-cube authored
  2. @c-cube

    tests for List.Monad

    c-cube authored
Commits on Dec 30, 2013
  1. @c-cube
  2. @c-cube
Commits on Jan 6, 2014
  1. @c-cube
  2. @c-cube
Commits on Jan 7, 2014
  1. @c-cube
  2. @c-cube

    traversable instance for Enum

    c-cube authored
    Traversable for Enum, albeit with a different functor name, for retrocompatibility reasons
Commits on Jan 12, 2014
  1. @c-cube
Commits on Mar 3, 2014
  1. @c-cube
This page is out of date. Refresh to see the latest.
View
52 src/batEnum.ml
@@ -1177,40 +1177,40 @@ end
module WithMonad (Mon : BatInterfaces.Monad) =
struct
+ type 'a t = 'a mappable
type 'a m = 'a Mon.m
- let sequence enum =
- let (>>=) = Mon.bind and return = Mon.return in
- (* We use a list as an accumulator for the result sequence
- computed under the monad. A previous version of this code used
- a Queue instead, which was problematic for backtracking
- monads. Due to the destructive nature of Enums, the current
- version will still be problematic but at least the result will
- be consistent. *)
- let of_acc acc =
- (* we don't use List functions to avoid creating a cyclic
- dependency *)
- let li = ref (List.rev acc) in
- from (fun () ->
- match !li with
- | [] -> raise No_more_elements
- | hd::tl ->
- li := tl;
- hd)
- in
- let rec loop acc = match get enum with
- | None -> return (of_acc acc)
- | Some elem -> elem >>= (fun x -> loop (x :: acc))
- in
- loop []
+ let (>>=) = Mon.bind
let fold_monad f init enum =
- let (>>=) = Mon.bind and return = Mon.return in
let rec fold m = match get enum with
| None -> m
| Some x -> m >>= fun acc -> fold (f acc x)
in
- fold (return init)
+ fold (Mon.return init)
+
+ (* Comment for old sequence function:
+ "We use a list as an accumulator for the result enum
+ computed under the monad. A previous version of this code used
+ a Queue instead, which was problematic for backtracking
+ monads. Due to the destructive nature of Enums, the current
+ version will still be problematic but at least the result will
+ be consistent." *)
+
+ let map_m f enum =
+ let of_acc acc = MicroList.enum (List.rev acc)
+ in
+ let rec loop acc = match get enum with
+ | None -> Mon.return (of_acc acc)
+ | Some elem ->
+ f elem >>= fun x ->
+ loop (x :: acc)
+ in
+ loop []
+
+ let sequence e = map_m (fun x -> x) e
+
+ let fold_m = fold_monad
end
module Monad =
View
15 src/batEnum.mli
@@ -693,16 +693,13 @@ val ( @// ) : ('a -> 'b option) -> 'a t -> 'b t
This module will let you use sequence and fold_monad functions over enumerations.
*)
module WithMonad : functor (Mon : BatInterfaces.Monad) -> sig
- type 'a m = 'a Mon.m
- (** Type of the monadic elements. *)
+ include BatInterfaces.Traversable with type 'a t = 'a t and type 'a m = 'a Mon.m
- val sequence : 'a m t -> 'a t m
-(** [sequence e] evaluates each monadic elements (of type ['a m]) contained in the enumeration [e] to get a monadic enumeration of ['a] elements,
- of type ['a BatEnum.t m]. *)
-
-val fold_monad : ('a -> 'b -> 'a m) -> 'a -> 'b t -> 'a m
- (** [fold_monad f init e] does a folding of the enumeration [e] applying step by step the function [f] that gives back results in the [Mon] monad,
- with the [init] initial element. The result is a value in the [Mon] monad. *)
+ val fold_monad : ('a -> 'b -> 'a m) -> 'a -> 'b t -> 'a m
+ (** [fold_monad f init e] does a folding of the enumeration [e] applying step
+ by step the function [f] that gives back results in the [Mon] monad,
+ with the [init] initial element. The result is a value in the [Mon] monad.
+ Synonym to {!foldM} (see {!BatInterfaces.Traversable}) *)
end
(** The BatEnum Monad
View
10 src/batInterfaces.ml
@@ -34,3 +34,13 @@ module type Monad = sig
val bind : 'a m -> ('a -> 'b m) -> 'b m
val return: 'a -> 'a m
end
+
+module type Traversable = sig
+ type 'a t
+
+ type 'a m
+
+ val map_m : ('a -> 'b m) -> 'a t -> 'b t m
+ val fold_m : ('a -> 'b -> 'a m) -> 'a -> 'b t -> 'a m
+ val sequence : 'a m t -> 'a t m
+end
View
24 src/batInterfaces.mli
@@ -85,3 +85,27 @@ module type Monad = sig
val return: 'a -> 'a m
end
+
+(** Traversable structures allow one to traverse a structure, with a monadic
+ function *)
+module type Traversable = sig
+ type 'a t
+ (** Traversable *)
+
+ type 'a m
+ (** Monad *)
+
+ val map_m : ('a -> 'b m) -> 'a t -> 'b t m
+ (** Map each element to a value by a monadic action, and combine results
+ together in the monad. *)
+
+ val fold_m : ('a -> 'b -> 'a m) -> 'a -> 'b t -> 'a m
+ (** Fold on elements of the traversable structure, applying a monadic
+ action to obtain the next accumulator from the current accumulator
+ and element. *)
+
+ val sequence : 'a m t -> 'a t m
+ (** Basic building block for other functions. [sequence x] is similar
+ to [map_m identity x]. *)
+
+end
View
32 src/batLazyList.ml
@@ -321,6 +321,38 @@ let concat lol =
ignore (concat (lazy (Cons ((let () = failwith "foo" in nil), nil)))); true
*)
+(** {6 Monad} *)
+
+let concat_map f l = map f l |> concat
+
+module Monad = struct
+ type 'a m = 'a t
+ let return x = cons x nil
+ let bind x f = concat_map f x
+end
+
+module Traverse(M : BatInterfaces.Monad) = struct
+ type 'a t = 'a mappable
+ type 'a m = 'a M.m
+
+ let (>>=) = M.bind
+
+ let rec map_m f l = match l with
+ | lazy Nil -> M.return nil
+ | lazy (Cons (x,l')) ->
+ f x >>= fun x ->
+ map_m f l' >>= fun l' ->
+ M.return (cons x l')
+
+ let sequence l = map_m (fun x -> x) l
+
+ let rec fold_m f acc l = match l with
+ | lazy Nil -> M.return acc
+ | lazy (Cons (x, l')) ->
+ f acc x >>= fun acc ->
+ fold_m f acc l'
+end
+
(**
{6 Conversions}
*)
View
10 src/batLazyList.mli
@@ -391,6 +391,16 @@ val split_at : int -> 'a t -> 'a t * 'a t
val split_nth : int -> 'a t -> 'a t * 'a t
(** Obsolete. As [split_at]. *)
+(** {6 Monad} *)
+
+val concat_map : ('a -> 'b t) -> 'a t -> 'b t
+(** [concat_map f l] is the same as [map f l |> concat] *)
+
+module Monad : BatInterfaces.Monad with type 'a m = 'a t
+
+module Traverse(M : BatInterfaces.Monad) :
+ BatInterfaces.Traversable with type 'a t = 'a t and type 'a m = 'a M.m
+
(**{6 Dropping elements}*)
val unique : ?cmp:('a -> 'a -> int) -> 'a t -> 'a t
View
59 src/batList.ml
@@ -367,6 +367,65 @@ let interleave ?first ?last (sep:'a) (l:'a list) =
| (h::t, None, Some y) -> rev_append (aux [h] t) [y]
| (h::t, Some x, Some y) -> x::rev_append (aux [h] t) [y]
+module Monad = struct
+ type 'a m = 'a list
+
+ let return x = [x]
+
+ let bind l f =
+ let rec loop l f acc = match l with
+ | [] -> ()
+ | x::l' ->
+ let xs = f x in
+ let acc = _append xs acc in
+ loop l' f acc
+ and _append l acc = match l with
+ | [] -> acc
+ | x::l' -> _append l' (Acc.accum acc x)
+ in
+ let acc = Acc.dummy() in
+ loop l f acc;
+ acc.tl
+
+ (*$T
+ (Monad.bind [1;2;3] (fun x -> [x; x*2])) = [1;2;2;4;3;6]
+ *)
+
+ (*$Q
+ (Q.list_of_size (fun () -> 5) (Q.list Q.small_int)) \
+ (fun l -> Monad.bind l identity = flatten l)
+ (Q.list Q.small_int) (fun l -> l = Monad.bind l Monad.return)
+ *)
+end
+
+module Traverse(M : BatInterfaces.Monad) = struct
+ type 'a t = 'a list
+
+ type 'a m = 'a M.m
+
+ let (>>=) = M.bind
+
+ let rec sequence l = match l with
+ | [] -> M.return []
+ | xs::l' ->
+ xs >>= fun x ->
+ sequence l' >>= fun l' ->
+ M.return (x :: l')
+
+ let rec map_m f l = match l with
+ | [] -> M.return []
+ | x::l' ->
+ map_m f l' >>= fun l' ->
+ f x >>= fun x ->
+ M.return (x::l')
+
+ let rec fold_m f acc l = match l with
+ | [] -> M.return acc
+ | x::l' ->
+ f acc x >>= fun acc' ->
+ fold_m f acc' l'
+end
+
(*$= interleave & ~printer:(IO.to_string (List.print Int.print))
(interleave 0 [1;2;3]) [1;0;2;0;3]
(interleave 0 [1]) [1]
View
7 src/batList.mli
@@ -588,6 +588,13 @@ val interleave : ?first:'a -> ?last:'a -> 'a -> 'a list -> 'a list
[first; a0; sep; a1; sep; a2; sep; ...; sep; an; last] *)
+(** {6 Monadic interface} *)
+
+module Monad : BatInterfaces.Monad with type 'a m = 'a list
+
+module Traverse(M : BatInterfaces.Monad) :
+ BatInterfaces.Traversable with type 'a m = 'a M.m and type 'a t = 'a list
+
(** {6 BatEnum functions}
Abstraction layer.*)
View
31 src/batSeq.ml
@@ -290,6 +290,37 @@ let print ?(first="[") ?(last="]") ?(sep="; ") print_a out s = match s () with
iter (BatPrintf.fprintf out "%s%a" sep print_a) s;
BatInnerIO.nwrite out last
+let concat_map f e = concat (map f e)
+
+module Monad = struct
+ type 'a m = 'a t
+ let return x = cons x nil
+ let bind x f = concat_map f x
+end
+
+module Traverse(M : BatInterfaces.Monad) = struct
+ type 'a t = 'a mappable
+ type 'a m = 'a M.m
+
+ let (>>=) = M.bind
+
+ let rec map_m f e = match e () with
+ | Nil -> M.return nil
+ | Cons (x, e') ->
+ f x >>= fun x ->
+ map_m f e' >>= fun e' ->
+ M.return (cons x e')
+
+ let sequence m = map_m (fun x -> x) m
+
+ let rec fold_m f acc e = match e () with
+ | Nil -> M.return acc
+ | Cons (x, e') ->
+ f acc x >>= fun acc ->
+ fold_m f acc e'
+end
+
+
module Infix = struct
(** Infix operators matching those provided by {!BatEnum.Infix} *)
View
10 src/batSeq.mli
@@ -91,6 +91,16 @@ val concat : 'a t t -> 'a t
val flatten : 'a t t -> 'a t
(** Same as {!concat}. *)
+(** {6 Monad} *)
+
+val concat_map : ('a -> 'b t) -> 'a t -> 'b t
+(** [concat_map f s] is the same as [map f s |> concat] *)
+
+module Monad : BatInterfaces.Monad with type 'a m = 'a t
+
+module Traverse(M : BatInterfaces.Monad) :
+ BatInterfaces.Traversable with type 'a m = 'a M.m and type 'a t = 'a t
+
(** {6 Constructors} *)
val nil : 'a t
Something went wrong with that request. Please try again.