我试图理解如何使用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值,你将如何改变它以返回元素列表? (以后不要过滤掉它。)
答案 0 :(得分:6)
自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构造函数的y
表达式的x < y
上,有一个警告:
此构造导致代码不像类型注释所指示的那样通用。类型变量
'a
已被约束为类型“'b
”。
关于1
表达式中的let! x = [1 .. 10]
号,会出现警告:
此构造导致代码不像类型注释所指示的那样通用。类型变量
'b
已被约束为类型“int
”。
因此,在这两个约束之间,计算表达式的返回类型('b list
)已被约束为int list
,但您的表达式返回int * int list
。在考虑了类型约束之后,您可能会得出结论,这是不可行的。 但是有一种方法可以使它工作。关键是要意识到,作为计算表达式输出的'b
类型,在本例中,实际上是元组 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
的类型,但我传入int
s,它们是值类型,因此不实现任何接口。这导致错误
此表达式的类型应为“
System.IComparable
”,但此处的类型为“int
”。
但是,将谓词的两个参数声明为int
就可以了。
答案 1 :(得分:5)
OP已经接受了答案,我将提供一种不同的方法,可能有助于理解F#中的计算表达式
可以使用有用的ReturnFrom
和Zero
扩展计算表达式,如下所示:
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 [])
)