用函数式语言表示有状态的东西

时间:2011-06-19 00:58:55

标签: .net f# functional-programming

我一直在玩函数式语言(特别是F#),我真的很喜欢整个不可变/概念。但是,我对你如何用函数式语言表示有状态的东西感到有些迷茫。

例如,如何在函数式语言中重写以下内容? (任何功能语言都很好......只需要绕过它)

class state
{
    int current_time;
    bool is_completed() { 
        return current_time() - start_time > 30 seconds
    }
    double get_progress() { 
        return (current_time() - start_time) / 30 seconds
    }
    void start() {
        start_time = current_time();
    }
}
void main() {
    state s;
    s.start();
    while(s.is_completed() == false) { 
          print s.get_progress();
    }
    print "finished";
}

3 个答案:

答案 0 :(得分:8)

  

我对你如何用函数式语言表示有状态的东西感到有些迷茫。

最终,我们常用的计算机是有状态的东西。编程语言必须在某种程度上应对这一事实,或者放弃其主机的某些功能。

FP语言通过以下方式处理这一事实:

  • 允许您编写带状态的函数,并生成新状态(因此使函数成为无状态)
  • 将有状态概念包含在Monad(或F#,Computation Expression)中

至于您的代码,您需要查看其中的第一个选项。重写你的函数以接受当前时间为参数。

答案 1 :(得分:8)

您的示例包含一些在函数式语言中有所不同的方法:

  • 该类是可变的 - 在函数式语言中,您将使用不可变类型
  • 您正在使用current_time来获取当前时间,但这不是一个纯函数(它取决于某些全局变化状态)。在函数式语言(Haskell)中,这是不允许的(并且你必须使用monad),但大多数不纯的函数式语言(F#,OCaml)允许这样做。
  • 您的main函数使用循环 - 通常不鼓励在函数式语言中使用循环(尽管有些人支持它们)。

惯用的F#解决方案将处理第一个和最后一个点:

let currentTime() =
  System.DateTime.Now

type State(startTime) =
  static member Start() =
    State(currentTime())
  member x.IsCompleted =
    (currentTime() - startTime).TotalSeconds > 30.0
  member x.Progress =
    (currentTime() - startTime).TotalSeconds / 30.0

let main() =
  let s = State.Start()
  let rec loop () =
    if not s.IsCompleted then
      printf "%A" s.Progress
      loop ()
  loop ()
  printf "finished"

类型State在永不改变其本地字段值的意义上是不可变的。它不是纯粹的功能,因为它取决于(改变)当前时间,但这不是F#中的问题(你只需要知道它)。如果你需要一些修改状态的方法(你不需要),那么该方法将返回State的新实例(就像.NET string)。

main函数是使用递归而不是循环编写的 - 在这种情况下,它并不重要(循环在F#中也可以)。使用递归的关键是你可以将当前状态作为参数传递,并在进行递归调用时使用新实例(这实际上会在计算过程中改变当前状态)。

答案 2 :(得分:2)

我对F#了解不多,但据我所知,它与OCaml非常接近。所以这里有一些OCaml:

记录类型非常擅长。这是OCaml中的一些等效代码:

#load "unix.cma" ;;

type state = { start : float } ;;

let mystate = { start = Unix.time () } in
    let rec check () =
        if Unix.time () -. mystate.start > 30. then
            print_endline "finished"
        else
            check ()
    in
        check () ;;

(由于某种原因,Unix时间是OCaml中的浮点数;如果它让你感觉更好,你可以转换为int32。)