对这个问题的简短回答是,无法保证 F# 中的引用透明性。F# 的一大优点是它与其他 .NET 语言具有出色的互操作性,但与 Haskell 等更孤立的语言相比,它的缺点是存在副作用,您必须处理它们。
如何在 F# 中实际处理副作用完全是一个不同的问题。
实际上,没有什么可以阻止您以与在 Haskell 中非常相似的方式将效果引入 F# 中的类型系统,尽管实际上您是“选择加入”这种方法,而不是强制执行。
你真正需要的只是一些这样的基础设施:
/// A value of type IO<'a> represents an action which, when performed (e.g. by calling the IO.run function), does some I/O which results in a value of type 'a.
type IO<'a> =
private
|Return of 'a
|Delay of (unit -> 'a)
/// Pure IO Functions
module IO =
/// Runs the IO actions and evaluates the result
let run io =
match io with
|Return a -> a
|Delay (a) -> a()
/// Return a value as an IO action
let return' x = Return x
/// Creates an IO action from an effectful computation, this simply takes a side effecting function and brings it into IO
let fromEffectful f = Delay (f)
/// Monadic bind for IO action, this is used to combine and sequence IO actions
let bind x f =
match x with
|Return a -> f a
|Delay (g) -> Delay (fun _ -> run << f <| g())
return
在 内带来一个值IO
。
fromEffectful
具有副作用的功能unit -> 'a
并将其带入IO
.
bind
是一元绑定函数,可让您对效果进行排序。
run
运行 IO 来执行所有封闭的效果。这就像unsafePerformIO
在 Haskell 中一样。
然后,您可以使用这些原始函数定义一个计算表达式构建器,并为自己提供许多不错的语法糖。
另一个值得问的问题是,这在 F# 中有用吗?
F# 和 Haskell 之间的根本区别在于 F# 默认情况下是渴望的语言,而 Haskell 默认情况下是惰性的。Haskell 社区(我怀疑.NET 社区,在较小程度上)已经了解到,当您将惰性评估和副作用/IO 结合起来时,可能会发生非常糟糕的事情。
当你在 Haskell 的 IO monad 中工作时,你(通常)保证了 IO 的顺序性,并确保一个 IO 在另一个之前完成。您还可以保证影响发生的频率和时间。
我喜欢在 F# 中提出的一个例子是:
let randomSeq = Seq.init 4 (fun _ -> rnd.Next())
let sortedSeq = Seq.sort randomSeq
printfn "Sorted: %A" sortedSeq
printfn "Random: %A" randomSeq
乍一看,这段代码可能会生成一个序列,对相同的序列进行排序,然后打印已排序和未排序的版本。
它没有。它生成两个序列,其中一个已排序,另一个未排序。它们可以而且几乎可以肯定确实具有完全不同的价值观。
这是在没有引用透明度的情况下结合副作用和惰性求值的直接结果。您可以通过使用Seq.cache
which 防止重复评估来重新获得一些控制权,但仍然无法控制效果发生的时间和顺序。
相比之下,当您使用急切评估的数据结构时,其后果通常不那么隐蔽,因此我认为与 Haskell 相比,F# 中对显式效果的要求大大降低。
也就是说,在类型系统中使所有效果显式化的一大优势是它有助于实施良好的设计。Mark Seemann 之类的人会告诉你,设计健壮系统的最佳策略,无论是面向对象的还是功能性的,都包括在系统边缘隔离副作用并依赖于引用透明、高度可单元测试的核心。
如果您正在使用显式效果并IO
在类型系统中工作,并且您的所有功能最终都是用 编写的IO
,那么这是一种强烈而明显的设计气味。
回到最初的问题,即这在 F# 中是否值得,我仍然必须回答“我不知道”。我一直在为 F# 中的引用透明效果开发一个库,以自己探索这种可能性。IO
如果您有兴趣,那里有更多关于这个主题的材料以及更完整的实现。
最后,我认为值得记住的是,排斥中间人的诅咒可能更多地针对编程语言设计人员,而不是典型的开发人员。
如果您使用不纯的语言工作,您将需要找到一种方法来应对和驯服您的副作用,您遵循的精确策略是开放的解释以及最适合您自己和/或您的需求的团队,但我认为 F# 为您提供了很多工具来做到这一点。
最后,我对 F# 的务实和经验丰富的观点告诉我,实际上,“大部分函数式”编程在几乎所有时间里仍然比它的竞争对手有很大的改进。