【问题标题】:How to implement the List Monad (computation expression) with a condition?如何用条件实现 List Monad(计算表达式)?
【发布时间】:2017-04-29 05:21:47
【问题描述】:

我正在尝试了解如何使用 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)
    }

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

【问题讨论】:

  • 在计算表达式中应用过滤器存在缺陷,因为您是 breaking the rules
  • @Funk 提出了一个重要的观点。我现在没有时间详细讨论违反一致性规则的详细含义,但简而言之,如果你违反了一致性规则,就会出现不一致。 (在本月重言式比赛中,一句与“无论你走到哪里,你都在那儿”排名靠前。)也就是说,你应该期望let! foo = bar ; return fooreturn! bar 一样工作,但在示例中在我的回答中,情况不一定如此。当你试图弄清楚时,这会让你很头疼。

标签: f# computation-expression


【解决方案1】:

因为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) 元组上的类型错误而失败:

此表达式的类型应为“int”,但此处的类型为“'a * 'b

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

此构造导致代码比类型注释所指示的更通用。类型变量 'a 已被限制为类型“'b”。

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

此构造导致代码比类型注释所指示的更通用。类型变量'b 已被限制为类型“int”。

因此,在这两个约束之间,计算表达式的返回类型('b list)已被约束为 int list,但您的表达式改为返回 int * int list。在考虑了类型约束之后,您可能会得出这样的结论:这是行不通的。 但有一种方法可以让它发挥作用。关键是要意识到'b 类型将成为计算表达式的输出,在本例中,实际上是 元组 em> 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,它们是值类型,因此不实现任何接口。这导致了错误

此表达式的类型应为“System.IComparable”,但此处的类型为“int”。

但是,将谓词的两个参数都声明为 int 就成功了。

【讨论】:

    【解决方案2】:

    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! [] 返回空列表,从而启用过滤。 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 [])
          )
    

    【讨论】:

    • 这是拉德。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-11
    • 1970-01-01
    相关资源
    最近更新 更多