如何实现带条件的List Monad(计算表达式)?

How to implement the List Monad (computation expression) with a condition?

我正在尝试了解如何使用 F# 计算表达式,这确实让我感到困惑。

以下示例对我来说有些意义。

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)
    }

我的问题是,如何向这个计算表达式添加条件?具体来说,您如何将其更改为 return 仅在 x 值严格小于 y 值的情况下的元素列表? (我们以后不要过滤掉它)。

computation expressions can be parameterized 以来,您可能首先想到尝试这样的事情:

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)
    }

但是,由于 (x,y) 元组的类型错误而失败:

This expression was expected to have type 'int' but here has type ''a * 'b'

还有两个编译器警告:在 FilteringListMonad 构造函数中 x < y 表达式的 y 处,有一个警告:

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'.

并且在 let! x = [1 .. 10] 表达式中的数字 1 上,有一个警告:

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'.

所以在这两个约束之间,return类型的计算表达式(一个'b list)已经被约束为int list,但是你的表达式是return取而代之的是 int * int list 。在考虑了一些类型约束之后,您可能会得出这样的结论:这是行不通的。 但有一种方法可以让它发挥作用。关键是要认识到将成为计算表达式输出的 'b 类型,在这个例子中,实际上是 tuple int * int,所以你重写谓词函数实际上只接受 'b 类型,然后一切正常:

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)
    }

请注意,我还必须指定谓词函数输入的类型。否则,F# 将它们概括为“实现 System.IComparable 的任何类型,但我传入了 ints,它们是值类型,因此不实现任何接口。这导致了错误

This expression was expected to have type 'System.IComparable' but here has type 'int'.

但是,将谓词的两个参数声明为 int 就可以了。

OP 已经接受了一个答案,我将提供一种不同的方法,这可能有助于理解 F#

中的计算表达式

可以使用有用的 ReturnFromZero 扩展计算表达式,如下所示:

type ListMonad() =
   member o.Bind        (m, f)  = List.collect f m
   member o.Return      x       = [x]
   member o.ReturnFrom  l       = l : _ list
   member o.Zero        ()      = []

let listM = ListMonad()

ReturnFrom 允许我们使用 return! [] return 清空列表作为启用过滤的结果。 Zero 是这个的简写,因为如果 else 分支未定义 Zero 则使用。

这允许我们像这样过滤:

let test = 
  listM {
    let! x = [ 1 .. 10]
    let! y = [2 .. 2 .. 20]
    if x % y = 0 then
      return (x,y)
//  By defining .Zero F# implicitly adds else branch if not defined
//  else
//    return! []
  }

F# 会将计算扩展为大致如下所示:

let testEq = 
  [ 1 .. 10] 
  |> List.collect 
      (fun x -> 
        [2 .. 2 .. 20] 
        |> List.collect (fun y -> if x % y = 0 then [x,y] else [])
      )