Clojure像F#中的cond

clojure like cond in F#

我最近从F#绕行Clojure,遇到一个名为cond的宏。
这是用法示例:

1
2
3
4
5
(cond
 (= target (nth arr mid)) mid  
 (< target (nth arr mid)) (search left (- mid 1))
 (> target (nth arr mid)) (search (+ mid 1) right)
 (= left right) -1)

这意味着伪代码中的以下内容:

1
2
3
4
if target == arr.[mid] then return mid
if target < arr.[mid] then return (call search(left, mid-1))
if target > arr.[mid] then return (call search(mid+1, right))
if left == right then return -1

这只是二进制搜索中的一个示例,以防您想知道左边和右边是什么,但并不是很重要。

我曾尝试在F#中找到类似的内容,但找不到,所以我决定尝试自己编写。
我最终得到了这样的东西:

1
2
3
4
5
6
7
8
9
10
type condition = bool * int

let cond (conds: condition seq) =
    conds |> Seq.pick(fun c -> if fst c then Some (snd c) else None)

cond [| ( (=) target arr.[mid], mid )
        ( (=) left right, -1 )
        ( (<) target arr.[mid], recSrch left (mid-1) )
        ( (>) target arr.[mid], recSrch (mid+1) right )
      |]

这里的问题是,我想在递归函数中使用它,因为马上就对recSrch left(mid-1)进行求值,所以我陷入了无限循环。 我希望仅在条件成立时对其进行评估。 另外,表格仍然不像Clojure中的表格干净。

有什么想法可以改善吗?


这是使用match的草图,我认为它非常接近clojure。

它定义cond为部分活动模式,以测试函数作为参数

1
2
let (|Cond|_|) f  arg =
    if f arg then Some () else None;;

使用它很容易

1
2
3
match 1 with
|Cond ( (=) 5) _ -> printfn"unlikely"
| _ -> printfn"likely";;

您需要一种使条件体延迟求值的方法。这是一种实现方法,通过使主体成为在遍历条件序列时要调用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
type condition = bool * (unit -> int)

let cond (conds: condition seq) =
    conds
    |> Seq.pick(fun c ->
        let pred, func = c
        if pred then Some (func()) else None)

cond [| ( (=) target arr.[mid], fun () -> mid )
        ( (=) left right, fun () -> -1 )
        ( (<) target arr.[mid], fun () -> recSrch left (mid-1) )
        ( (>) target arr.[mid], fun () -> recSrch (mid+1) right )
        |]

请注意,只有在条件列表应该是动态的情况下,才使用这样的方法。

对于静态条件,您可以使用when子句进行模式匹配。这为您提供了很好的惯用语法,并且通常在编译时检查匹配项的详尽性,因此非常值得。

1
2
3
4
5
6
7
let result =
    match target with
    | _ when target = arr.[mid] -> mid
    | _ when left = right -> -1
    | _ when target < arr.[mid] -> recSrch left (mid-1)    
    | _ when target > arr.[mid] -> recSrch (mid+1) right
    | _ -> failwith"you need this case if the compiler can't figure if your matches are exhaustive"

如果将其包装为活动模式,它将变得更好。


在F#中,有一种用于这种表达的语言构造:

1
2
3
4
if target = arr.[mid] then mid
elif target < arr.[mid] then call (search(left, mid-1))
elif target > arr.[mid] then call (search(mid+1, right))
else -1

...或者,总的来说:我将Clojure的cond宏视为模式匹配或if/elif/else块的等效项。它们显然不完全相同,因为Clojure是经过解释和动态键入的,而F#是经过编译和静态键入的。