如何通过F#在函数中创建可变参数?

时间:2018-09-07 16:43:55

标签: f#

很抱歉我的问题,但我不理解与该问题有关的答案,所以我希望有人能进一步启发我。

我是一名新的数据科学专业的学生,​​我们将学习如何使用功能语言F#进行编程。我们正在学习算法,我想将算法编写为F#函数,以检查我在纸上的计算是否正确。

我收到以下错误消息:

"This value is not mutable. Consider using the mutable keyword let mutable n = expression"

我的代码如下:

let loop5( n ) =  

    let mutable x = 0

    while n > 0 do

        x <- x + 1
        n <- n + 1

    printfn "loop5(): x=%i for n=%i" x n 

loop5(4)

我正在尝试编写一个看起来像这样的函数(伪代码):

loop5(n)

    x = 0

    while n > 0

         x = x + 1
         n = n + 1

    return x

希望我提出了一个明确的问题,有人可以在这里帮助我:-)周末愉快

2 个答案:

答案 0 :(得分:1)

参考单元格

Ref单元规避了可变项的一些限制。实际上,ref单元是非常简单的数据类型,将可变字段包装成记录类型。引用单元格由F#定义如下:

type 'a ref = { mutable contents : 'a }

F#库包含用于处理ref单元的几个内置函数和运算符:

let ref v = { contents = v }      (* val ref  : 'a -> 'a ref *)
let (!) r = r.contents            (* val (!)  : 'a ref -> 'a *)
let (:=) r v = r.contents <- v    (* val (:=) : 'a ref -> 'a -> unit *)

ref函数用于创建ref单元!运算符用于读取ref单元的内容,而:=运算符用于为ref单元分配新值。这是fsi中的示例:

let x = ref "hello";;

val x : string ref

x;; (* returns ref instance *)
val it : string ref = {contents = "hello";}

!x;; (* returns x.contents *)
val it : string = "hello"

x := "world";; (* updates x.contents with a new value *)
val it : unit = ()

!x;; (* returns x.contents *)
val it : string = "world"

由于ref单元分配在堆上,因此可以在多个函数之间共享它们:

open System

let withSideEffects x =
    x := "assigned from withSideEffects function"

let refTest() =
    let msg = ref "hello"
    printfn "%s" !msg

    let setMsg() =
        msg := "world"

    setMsg()
    printfn "%s" !msg

    withSideEffects msg
    printfn "%s" !msg

let main() =
    refTest()
    Console.ReadKey(true) |> ignore

main()

withSideEffects函数的类型为val withSideEffects:string ref-> unit。 该程序输出以下内容:

你好    世界

从withSideEffects函数分配

withSideEffects函数之所以这样命名是因为它具有副作用,这意味着它可以更改其他函数中变量的状态。 Ref Cells应该像火一样对待。绝对必要时请谨慎使用,但一般应避免使用。如果您发现自己在从C / C ++转换代码时使用了Ref Cells,那么请暂时忽略一下效率,看看是否可以不用Ref Cells摆脱困境,或者在更糟糕的情况下使用mutable。您常常会偶然发现一种更优雅,更可维护的算法

混淆参考单元格

注意:尽管命令式编程广泛使用别名,但是这种做法存在许多问题。尤其是,由于任何变量的状态都可以在应用程序中任何其他位置的任意位置进行修改,因此程序难以遵循。另外,共享可变状态的多线程应用程序很难推理,因为一个线程可能会更改另一个线程中变量的状态,这可能导致许多与竞态条件和死锁有关的细微错误。 ref单元与C或C ++指针非常相似。可以指向两个或多个参考单元指向相同的存储器地址;该内存地址的更改将更改所有指向该地址的参考单元的状态。从概念上讲,此过程如下:

假设我们有3个ref单元在内存中查看相同的地址:

三个对值为7的整数的引用

cell1,cell2和cell3都指向内存中的相同地址。每个单元的.contents属性是7。假设在程序中的某个时刻,我们执行了代码cell1:= 10,这会将内存中的值更改为以下值:

三个对值为10的整数的引用

通过为cell1.contents分配新值,变量cell2和cell3也被更改。可以使用fsi如下所示:

let cell1 = ref 7;;
val cell1 : int ref

let cell2 = cell1;;
val cell2 : int ref

let cell3 = cell2;;
val cell3 : int ref

!cell1;;
val it : int = 7

!cell2;;
val it : int = 7

!cell3;;
val it : int = 7

cell1 := 10;;
val it : unit = ()

!cell1;;
val it : int = 10

!cell2;;
val it : int = 10

!cell3;;
val it : int = 10

答案 1 :(得分:1)

您正在尝试更改循环的参数n。该参数不是可变的,因此编译器不允许您使用。这正是错误告诉您的。

现在,通常要使错误消失,您可以使变量可变。但是,您不能使函数参数可变,因此这不是一种选择。

在这里您想考虑程序的含义。 loop函数是否需要将n的更新值传递回其调用者,还是整个变异都是其内部业务?如果是前者,请参阅@AnyMoose的答案,但是从您的示例和解释中,我怀疑是后者。如果是这种情况,只需简单地复制该参数并使用它即可:

let loop n' =
    let mutable x = 0
    let mutable n = n'

    ...

另外,我想指出的是,您编写的程序实际上将无限期地循环(或者直到它始终包裹在最大int值之前),因为您在增加的每一步中都没有减少n它。如果您希望程序在下一个冰河世纪之前真正完成,则需要使n随每次迭代减少:

n <- n - 1