How to implement the List Monad (computation expression) with a condition?
computation-expression, f#
Solution
Since computation expressions can be parameterized, you might first think to try something like this:
let filterAndCollect (pred : 'a -> 'b -> bool) (f : 'a -> 'b list) (m : 'a list) =
let f' a = [ for b in f a do if pred a b then yield b ]
List.collect f' m
type FilteringListMonad(pred) =
member o.Bind( (m:'a list), (f: 'a -> 'b list) ) = filterAndCollect pred f m
member o.Return(x) = [x]
let filteredList = FilteringListMonad(fun x y -> x < y)
let test2 =
filteredList {
let! x = [1 .. 10]
let! y = [2 .. 2 .. 20]
return (x,y)
}
However, that fails with a type error on the `(x,y)` tuple:
This expression was expected to have type '`int`' but here has type '`'a * 'b`'
There are also two compiler warnings: on the `y` of the `x < y` expression in the FilteringListMonad constructor, there's a warning:
This construct causes code to be less generic than indicated by the type annotations. The type variable `'a` has been constrained to be type '`'b`'.
And on the number `1` in the `let! x = [1 .. 10]` expression, there's a warning:
This construct causes code to be less generic than indicated by the type annotations. The type variable `'b` has been constrained to be type '`int`'.
So between these two constraints, the return type of the computation expression (a `'b list`) has been constrained to be `int list`, but your expression is returning a `int * int list` instead. After some thinking about the type constraints, you might conclude that there's no way this can work. But there's a way to make it work. The key is to realize that the `'b` type that will be the output of your computation expression is, in this example, actually the tuple `int * int`, so you rewrite the predicate function to actually just take that `'b` type, and then everything works:
let filterAndCollect (pred : 'b -> bool) (f : 'a -> 'b list) (m : 'a list) =
let f' a = [ for b in f a do if pred b then yield b ]
List.collect f' m
type FilteringListMonad(pred) =
member o.Bind( (m:'a list), (f: 'a -> 'b list) ) = filterAndCollect pred f m
member o.Return(x) = [x]
let filteredList = FilteringListMonad(fun (x:int,y:int) -> x < y)
let test2 =
filteredList {
let! x = [ 1 .. 10]
let! y = [2 .. 2 .. 20]
return (x,y)
}
Note that I also had to specify the types of the predicate function's inputs. Without that, F# was generalizing them to be "any type that implements `System.IComparable`, but I was passing in `int`s, which are value types and thus don't implement any interfaces. This led to the error
This expression was expected to have type '`System.IComparable`' but here has type '`int`'.
However, declaring both parameters to the predicate as `int` did the trick.
Problem
I'm trying to understand how to use F# computation expressions and it certainly puzzles me. The following example makes some amount of sense to me. ``` type ListMonad() = member o.Bind( (m:'a list), (f: 'a -> 'b list) ) = List.collect f m member o.Return(x) = [x] let list = ListMonad() let test = list { let! x = [ 1 .. 10] let! y = [2 .. 2 .. 20] return (x,y) } ``` My question is, how would you add a condition to this computation expression? Specifically, how would you alter it to return a list of elements only where the x value is strictly smaller than the y value? (Let's not filter it out afterward).