Che*_*vas 4 monads recursion f# abstract-syntax-tree
我正在阅读Mark Seemann的这篇优秀文章.
在其中,他简单地演示了如何使用自由monad来使用纯函数来模拟交互.
我理解它足以能够编写这样的程序,我可以理解这种方法的优点.虽然有一点代码,但我想知道它的含义.
let rec bind f = function
| Free instruction -> instruction |> mapI (bind f) |> Free
| Pure x -> f x
Run Code Online (Sandbox Code Playgroud)
该函数是递归的.
我可能(可能)错过了一些基本的东西,这意味着递归永远不会走得太远,请让我知道.
我认为前两个问题的答案是"它取决于" - 有一些与免费monad相关的开销,你当然可以遇到堆栈溢出,但你总是可以使用一个避免这种情况的实现,如果你正在做我/哦,那么开销可能也不算太差.
我认为免费monad的更大问题是它们只是太复杂的抽象.它们可以让您解决一个问题(抽象如何使用大量迭代运行代码),但代价是您使代码变得非常复杂.大多数时候,我认为你可以保持一个"功能核心"作为一个很好的可测试的纯粹部分和围绕它的"命令性包装",这很简单,你不需要测试它并抽象它.
免费monad是非常通用的建模方法,你可以使用更具体的表示.对于阅读和写作,你可以这样做:
type Instruction =
| WriteLine of string
| ReadLine of (string -> Instruction list)
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这不仅仅是一个简单的列表 - ReadLine很棘手,因为它需要一个函数,当从控制台使用字符串调用时,会生成更多指令(可能或可能不依赖于此字符串):
let program =
[ yield WriteLine "Enter your name:"
yield ReadLine (fun name ->
[ if name <> "" then
yield WriteLine ("Hello " + name) ] ) ]
Run Code Online (Sandbox Code Playgroud)
这表示"Hello",但仅当您输入非空名称时,它才会显示说明取决于您阅读的名称.然后运行程序的代码如下所示:
let rec runProgram program =
match program with
| [] -> ()
| WriteLine s :: rest ->
Console.WriteLine s
runProgram rest
| ReadLine f :: rest ->
let input = Console.ReadLine()
runProgram (f input @ rest)
Run Code Online (Sandbox Code Playgroud)
前两种情况很好,完全是尾递归的.最后一种情况是尾递归runProgram,但在调用时它不是尾递归的f.这应该没问题,因为f只生成指令而不调用runProgram.它还使用慢速列表附加@,但你可以通过使用更好的数据结构来解决这个问题(如果它实际上是一个问题).
免费monad基本上是对此的抽象 - 但我个人认为他们走得太远,如果我真的需要这个,那么上面的东西 - 具体说明 - 是更好的解决方案,因为它更简单.