我如何从在F#中迭代for循环的函数返回一个值

时间:2011-04-09 10:54:40

标签: loops f# functional-programming

我正在尝试循环数组并返回一个值,如下所示。但这在if语句之后给我一个错误。它说“这个表达式应该有类型单位但是有类型int”

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
for i = inputBits.Length - 1 to 0  do
    if inputBits.[i] then
        i
done

我该怎么做?我正在使用递归循环重新编码,因为它似乎是在函数式语言中进行此类循环的更可接受的方式,但我仍然想知道上面我做错了什么。

6 个答案:

答案 0 :(得分:8)

for循环不应返回值,它们只执行固定次数的操作,然后返回()(单位)。如果你想迭代并最终返回一些东西,你可以:

  • 在循环之外有一个引用,当你得到最终结果时,然后在循环返回引用内容之后

  • 直接使用递归函数

  • 使用一个更高阶的函数来封装遍历,让你专注于应用程序逻辑

如果您的数据结构支持,则更高级的功能很好。但是,诸如fold_left之类的简单遍历函数不支持过早地停止迭代。如果您希望支持这一点(显然在您的用例中会很有用),您必须使用具有过早退出支持的遍历。对于像你这样的简单函数,一个简单的递归函数可能是最简单的。

在F#中,还应该可以用命令式编写函数,使用yield将其转换为生成器,然后最终强制生成器获取结果。这可以看作是使用异常跳出循环的OCaml技术的对应物。

编辑:避免“过早停止”问题的一个很好的解决方案是使用一个懒惰的中间数据结构,它只能构建到第一个令人满意的结果。这是优雅而优秀的脚本风格,但仍然比直接退出支持或简单递归效率低。我想这取决于你的需求;这个函数是用在关键路径上的吗?

编辑以下是一些代码示例。它们是OCaml,数据结构不同(其中一些使用来自Batteries的库),但这些想法是相同的。

(* using a reference as accumulator *)
let most_significant_bit input_bits =
  let result = ref None in
  for i = Array.length input_bits - 1 downto 0 do
    if input_bits.(i) then
      if !result = None then
        result := Some i
  done;
  !result

let most_significant_bit input_bits =
  let result = ref None in
  for i = 0 to Array.length input_bits - 1 do
    if input_bits.(i) then
      (* only the last one will be kept *)
      result := Some i
  done;
  !result

(* simple recursive version *)
let most_significant_bit input_bits =
  let rec loop = function
    | -1 -> None
    | i ->
      if input_bits.(i) then Some i
      else loop (i - 1)
  in
  loop (Array.length input_bits - 1)

(* higher-order traversal *)
open Batteries_uni
let most_significant_bit input_bits =
  Array.fold_lefti
    (fun result i ->
      if input_bits.(i) && result = None then Some i else result)
    None input_bits

(* traversal using an intermediate lazy data structure 
   (a --- b) is the decreasing enumeration of integers in [b; a] *)
open Batteries_uni
let most_significant_bit input_bits =
  (Array.length input_bits - 1) --- 0
  |> Enum.Exceptionless.find (fun i -> input_bits.(i))

(* using an exception to break out of the loop; if I understand
   correctly, exceptions are rather discouraged in F# for efficiency
   reasons. I proposed to use `yield` instead and then force the
   generator, but this has no direct OCaml equivalent. *)
exception Result of int
let most_significant_bit input_bits =
  try
    for i = Array.length input_bits - 1 downto 0 do
      if input_bits.(i) then raise (Result i)
    done;
    None
  with Result i -> Some i

答案 1 :(得分:4)

为什么在使用高阶函数时使用循环?

我会写:

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    Seq.cast<bool> inputBits |> Seq.tryFindIndex id

Seq模块包含许多用于操作集合的函数。它通常是使用命令式循环的好选择。

  

但我仍然想知道我是什么   上面做错了。

for循环的主体是单位类型的表达式。你唯一能做的就是做副作用(修改一个可变值,打印......)。

在F#中,if then else与C语言中的? :类似。 thenelse部分必须具有相同的类型,否则在使用静态类型的语言中没有意义。缺少else时,编译器会认为它是else ()。因此,then必须具有unit类型。在for循环中放置值并不意味着return,因为所有内容都是F#中的值(包括if then)。

答案 2 :(得分:3)

+1为gasche
以下是F#中的一些示例。我添加了一个(第二个)来显示yield如何在序列表达式中与for一起使用,如gasche所述。

(* using a mutable variable as accumulator as per gasche's example *)
let findMostSignificantBitPosition (inputBits: BitArray) =
    let mutable ret = None // 0
    for i = inputBits.Length - 1 downto 0  do
        if inputBits.[i] then ret <- i
    ret

(* transforming to a Seq of integers with a for, then taking the first element *)
let findMostSignificantBitPosition2 (inputBits: BitArray) =
    seq {
        for i = 0 to inputBits.Length - 1 do
        if inputBits.[i] then yield i
    } |> Seq.head

(* casting to a sequence of bools then taking the index of the first "true" *)
let findMostSignificantBitPosition3 (inputBits: BitArray) =
    inputBits|> Seq.cast<bool>  |> Seq.findIndex(fun f -> f) 

修改:返回选项的版本

let findMostSignificantBitPosition (inputBits: BitArray) =
    let mutable ret = None
    for i = inputBits.Length - 1 downto 0  do
        if inputBits.[i] then ret <- Some i
    ret

let findMostSignificantBitPosition2 (inputBits: BitArray) =
    seq {
        for i = 0 to inputBits.Length - 1 do
        if inputBits.[i] then yield Some(i)
        else yield None
    } |> Seq.tryPick id

let findMostSignificantBitPosition3 (inputBits: BitArray) =
    inputBits|> Seq.cast<bool>  |> Seq.tryFindIndex(fun f -> f)

答案 3 :(得分:1)

我建议使用高阶函数(如Laurent所述)或显式编写递归函数(这是替换F#中循环的一般方法)。

如果你想看一些花哨的F#解决方案(这可能是使用一些临时懒惰数据结构的更好版本),那么你可以看一下为F#定义imperative computation builder的文章。这允许你写一些类似的东西:

let findMostSignificantBitPosition (inputBits:BitArray) = imperative {
    for b in Seq.cast<bool> inputBits do
      if b then return true
    return false }

有一些开销(与使用其他临时惰性数据结构一样),但它看起来就像C#:-)。 编辑我还在F#Snippets上发布了示例:http://fssnip.net/40

答案 4 :(得分:1)

我认为您在如何编写此代码时遇到问题的原因是您没有处理未找到设置位的失败情况。其他人已经发布了很多寻找这种方法的方法。以下是处理故障案例的几种方法。

选项

的失败案例
let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            None
        elif inputBits.[i] then 
            Some i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
match findMostSignificantBitPosition test with
| Some i -> printf "Most Significant Bit: %i" i
| None -> printf "Most Significant Bit Not Found"

异常的失败案例

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            failwith  "Most Significant Bit Not Found"
        elif inputBits.[i] then 
            i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
try 
    let i = findMostSignificantBitPosition test
    printf "Most Significant Bit: %i" i
with
    | Failure msg -> printf "%s" msg

失败案例为-1

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
    let rec loop i =
        if i = -1 then
            i
        elif inputBits.[i] then 
            i
        else
            loop (i - 1)

    loop (inputBits.Length - 1)

let test = new BitArray(1)
let i = findMostSignificantBitPosition test
if i <> -1 then
    printf "Most Significant Bit: %i" i
else
    printf "Most Significant Bit Not Found"

答案 5 :(得分:0)

其中一个选项是使用seq和findIndex方法:

let findMostSignificantBitPosition (inputBits:System.Collections.BitArray) =
  seq {
      for i = inputBits.Length - 1 to 0  do
          yield inputBits.[i]
  } |> Seq.findIndex(fun e -> e)