像F#中的cond一样的clojure

时间:2016-05-29 09:32:40

标签: clojure f#

我最近在F#中绕道而行,并遇到了一个叫做cond的宏。 以下是有关用法的示例:

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

这意味着以下伪代码:

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#中找到类似的东西,但我不能,所以我决定尝试自己编写。 我最终得到了类似的东西:

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那样干净。

任何想法我怎样才能改善这一点?

3 个答案:

答案 0 :(得分:7)

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

它将Cond定义为将测试函数作为参数

的部分活动模式
let (|Cond|_|) f  arg = 
    if f arg then Some () else None;;

使用它很容易

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

答案 1 :(得分:4)

在F#中,有这种表达的语言结构:

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#被编译并静态输入。

答案 2 :(得分:4)

你需要一种让条件体懒惰评估的方法。这是一种方法,通过使主体成为在迭代条件序列时调用的函数:

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子句匹配的模式。这给了你很好的惯用语法,并且通常在编译时检查你的匹配穷举,所以这是值得的。

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"

如果将其包装为活动模式,则获得更好的效果。