简体   繁体   English

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

[英]How do I make a mutable argument in a function through F#?

Sorry for my question but I did not understand the answers that was related to this question so I hope someone can enlighten me further.抱歉我的问题,但我不明白与这个问题相关的答案,所以我希望有人能进一步启发我。

I am a new data science student and we are going to learn how to program in the functional language F#.我是一名新的数据科学专业的学生,​​我们将学习如何使用函数式语言 F# 进行编程。 We are learning about algorithms and I wanted to write the algorithms as F# functions to check if my calculations on paper were correct.我们正在学习算法,我想将算法编写为 F# 函数,以检查我在纸上的计算是否正确。

I get the following error saying:我收到以下错误消息:

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

My code looks like this:我的代码如下所示:

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)

I'm trying to write a function looking like this (pseudocode):我正在尝试编写一个看起来像这样的函数(伪代码):

loop5(n)

    x = 0

    while n > 0

         x = x + 1
         n = n + 1

    return x

Hope I made a clear question and someone can help me out here :-) Have a nice weekend希望我提出了一个明确的问题,有人可以在这里帮助我:-) 周末愉快

You're trying to mutate the loop's parameter n .您正在尝试改变循环的参数n The parameter is not mutable, so the compiler doesn't let you.该参数不可变,因此编译器不允许您这样做。 That's exactly what the error tells you.这正是错误告诉你的。

Now, normally, to make the error go away, you'd make the variable mutable.现在,通常,为了消除错误,您需要使变量可变。 However, you can't make a function parameter mutable, so that's not an option.但是,您不能使函数参数可变,因此这不是一个选项。

Here you want to think what the meaning of your program should be.在这里你要思考你的程序的意义应该是什么。 Does the loop function need to pass the updated value of n back to its caller, or is the whole mutation its internal business? loop函数是否需要将n的更新值传递回其调用者,或者整个突变是其内部业务? If it's the former, please see @AnyMoose's answer, but from your example and explanation, I suspect it's the latter.如果是前者,请参阅@AnyMoose 的回答,但从您的示例和解释来看,我怀疑是后者。 If that is the case, simply make a mutable copy of the parameter and work with it:如果是这种情况,只需制作参数的可变副本并使用它:

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

    ...

Separately, I want to point out that your program, as written, would actually loop indefinitely (or until it wraps around the max int value anyway), because instead of decreasing n at each step you're increasing it.另外,我想指出,您编写的程序实际上会无限循环(或直到它环绕最大 int 值为止),因为不是在每一步都减少n ,而是增加它。 If you want your program to actually finish before the next Ice Age, you need to make n decrease with each iteration:如果你想让你的程序在下一个冰河时代之前真正完成,你需要在每次迭代中减少n

n <- n - 1

Ref cells参考细胞

Ref cells get around some of the limitations of mutables. Ref 单元格绕过了可变参数的一些限制。 In fact, ref cells are very simple datatypes which wrap up a mutable field in a record type.事实上,引用单元是非常简单的数据类型,它将可变字段包装在记录类型中。 Ref cells are defined by F# as follows: Ref 单元格由 F# 定义如下:

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

The F# library contains several built-in functions and operators for working with ref cells: F# 库包含多个用于处理引用单元格的内置函数和运算符:

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

The ref function is used to create a ref cell, the ! ref 函数用于创建一个引用单元格,! operator is used to read the contents of a ref cell, and the := operator is used to assign a ref cell a new value.运算符用于读取引用单元格的内容,而 := 运算符用于为引用单元格分配新值。 Here is a sample in fsi:这是 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"

Since ref cells are allocated on the heap, they can be shared across multiple functions:由于 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()

The withSideEffects function has the type val withSideEffects : string ref -> unit. withSideEffects 函数的类型为 val withSideEffects : string ref -> unit。 This program outputs the following:该程序输出以下内容:

hello world你好,世界

Assigned from withSideEffects function从 withSideEffects 函数分配

The withSideEffects function is named as such because it has a side-effect, meaning it can change the state of a variable in other functions. withSideEffects 函数之所以如此命名是因为它具有副作用,这意味着它可以更改其他函数中变量的状态。 Ref Cells should be treated like fire. Ref Cells 应该像对待火一样对待。 Use it cautiously when it is absolutely necessary but avoid it in general.在绝对必要时谨慎使用,但一般应避免使用。 If you find yourself using Ref Cells while translating code from C/C++, then ignore efficiency for a while and see if you can get away without Ref Cells or at worst using mutable.如果您发现自己在从 C/C++ 翻译代码时使用了 Ref Cells,那么暂时忽略效率,看看您是否可以在没有 Ref Cells 的情况下逃脱,或者在最坏的情况下使用 mutable。 You would often stumble upon a more elegant and more maintanable algorithm你经常会偶然发现一种更优雅、更易于维护的算法

Aliasing Ref Cells别名参考单元格

Note: While imperative programming uses aliasing extensively, this practice has a number of problems.注意:虽然命令式编程广泛使用别名,但这种做法有很多问题。 In particular it makes programs hard to follow since the state of any variable can be modified at any point elsewhere in an application.特别是它使程序难以遵循,因为任何变量的状态都可以在应用程序的任何其他地方修改。 Additionally, multithreaded applications sharing mutable state are difficult to reason about since one thread can potentially change the state of a variable in another thread, which can result in a number of subtle errors related to race conditions and dead locks.此外,共享可变状态的多线程应用程序很难推理,因为一个线程可能会改变另一个线程中变量的状态,这可能导致许多与竞争条件和死锁相关的细微错误。 A ref cell is very similar to a C or C++ pointer.引用单元与 C 或 C++ 指针非常相似。 Its possible to point to two or more ref cells to the same memory address;可以将两个或多个引用单元指向同一个内存地址; changes at that memory address will change the state of all ref cells pointing to it.该内存地址的更改将更改指向它的所有引用单元的状态。 Conceptually, this process looks like this:从概念上讲,此过程如下所示:

Let's say we have 3 ref cells looking at the same address in memory:假设我们有 3 个 ref 单元在内存中查看相同的地址:

Three references to an integer with value 7对值为 7 的整数的三个引用

cell1, cell2, and cell3 are all pointing to the same address in memory. cell1、cell2 和 cell3 都指向内存中的相同地址。 The .contents property of each cell is 7. Let's say, at some point in our program, we execute the code cell1 := 10, this changes the value in memory to the following:每个单元格的 .contents 属性是 7。假设,在我们程序的某个时刻,我们执行代码 cell1 := 10,这会将内存中的值更改为以下内容:

Three references to an integer with value 10对值为 10 的整数的三个引用

By assigning cell1.contents a new value, the variables cell2 and cell3 were changed as well.通过为 cell1.contents 分配一个新值,变量 cell2 和 cell3 也发生了变化。 This can be demonstrated using fsi as follows:这可以使用 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM