Bob*_*law 34 functional-programming scala
我正在努力学习斯卡拉,但我无法理解这个概念.为什么使对象不可变有助于防止函数中的副作用.任何人都可以解释我是五岁吗?
Dan*_*ral 31
有趣的问题,有点难以回答.
函数式编程非常关注使用数学推理程序.要做到这一点,人们需要一种描述程序的形式,以及如何能够证明他们可能具有的属性.
有许多计算模型可以提供这样的形式,例如lambda演算和图灵机.并且它们之间存在一定程度的等效性(请参阅此问题,以供讨论).
在非常真实的意义上,具有可变性和一些其他副作用的程序可以直接映射到功能程序.考虑这个例子:
a = 0
b = 1
a = a + b
Run Code Online (Sandbox Code Playgroud)
以下是将其映射到功能程序的两种方法.第一个,a并且b是"状态"的一部分,每一行是从一个状态到一个新状态的函数:
state1 = (a = 0, b = ?)
state2 = (a = state1.a, b = 1)
state3 = (a = state2.a + state2.b, b = state2.b)
Run Code Online (Sandbox Code Playgroud)
这是另一个,每个变量与时间相关联:
(a, t0) = 0
(b, t1) = 1
(a, t2) = (a, t0) + (b, t1)
Run Code Online (Sandbox Code Playgroud)
那么,鉴于上述情况,为什么不使用可变性呢?
嗯,这是关于数学的有趣的事情:形式主义越不强大,用它来证明就越容易.或者,换句话说,很难推断出具有可变性的程序.
因此,在编程中具有可变性的概念几乎没有进展.着名的设计模式不是通过研究得出的,也没有任何数学支持.相反,它们是经过多年和多年的反复试验的结果,其中一些已被证明是错误的.谁知道到处都能看到其他几十种"设计模式"?
与此同时,Haskell程序员提出了Functors,Monads,Co-monads,Zippers,Applicatives,Lenses ......数十个具有数学支持的概念,最重要的是,编写代码的实际模式来组成程序.您可以使用的东西来推断您的程序,提高可重用性并提高正确性.看一下Typeclassopedia的例子.
难怪不熟悉函数式编程的人会对这些东西感到有点害怕......相比之下,编程世界的其余部分仍在使用几十年前的概念.新概念的概念是陌生的.
不幸的是,所有这些模式,所有这些概念,仅适用于他们使用的代码不包含可变性(或其他副作用).如果确实如此,那么它们的属性就不再有效,你就不能依赖它们.你回到猜测,测试和调试.
Nat*_*C-K 16
简而言之,如果一个函数改变了一个对象,那么它就会产生副作用.突变是一种副作用.根据定义,这是正确的.
事实上,在一个纯函数式语言中,一个对象在技术上是可变的还是不可变的并不重要,因为语言永远不会"尝试"改变一个对象.纯函数式语言不会为您提供任何执行副作用的方法.
然而,Scala不是一种纯函数式语言,它在Java环境中运行,其中副作用非常流行.在这种环境中,使用无法突变的对象会鼓励您使用纯粹的功能样式,因为它使得面向副作用的样式变得不可能.您正在使用数据类型来强制执行纯度,因为该语言不适合您.
现在我要说一些其他的东西,希望它对你有意义.
功能语言中变量概念的基础是参考透明度.
引用透明性意味着值和对该值的引用之间没有区别.在一种真实的语言中,它使得考虑一个程序的工作变得简单得多,因为你永远不必停下来问,这是一个值,还是一个值的引用?任何曾用C语言编程的人都认识到,学习这种范式的挑战很大一部分就是知道哪些是任何时候都是如此.
为了具有引用透明性,引用引用的值永远不会更改.
(警告,我即将打个比方.)
可以这样想:在你的手机中,你已经保存了一些其他人手机的电话号码.您认为无论何时拨打该电话号码,您都会联系到您打算与之通话的人.如果其他人想与您的朋友交谈,您可以给他们电话号码,然后他们就会联系到同一个人.
如果有人更改了他们的手机号码,这个系统会崩溃.突然间,如果你想要接触他们,你需要获得他们的新电话号码.也许你六个月后打电话给同一个号码并联系另一个人.当函数执行副作用时,调用相同的数字并联系到不同的人:你有什么似乎是相同的东西,但是你尝试使用它,现在发现它是不同的.即使你预料到这一点,你给这个号码的所有人怎么样,你会打电话给他们,并告诉他们旧的号码不再到达同一个人了吗?
你依靠与那个人对应的电话号码,但事实并非如此.电话号码系统缺乏参考透明度:这个数字并不总是与人相同.
功能语言避免了这个问题.您可以提供您的电话号码,人们将始终能够在您的余生中与您联系,并且永远不会以该号码与其他人联系.
但是,在Java平台中,事情可能会发生变化.你认为是一回事,一分钟后可能变成另一件事.如果是这种情况,你怎么能阻止它?
Scala通过创建具有引用透明性的类来使用类型的强大功能来防止这种情况.因此,即使整个语言不是引用透明的,只要您使用不可变类型,您的代码将是引用透明的.
实际上,使用不可变类型编码的优点是:
我正在使用这个5岁的解释:
class Account(var myMoney:List[Int] = List(10, 10, 1, 1, 1, 5)) {
def getBalance = println(myMoney.sum + " dollars available")
def myMoneyWithInterest = {
myMoney = myMoney.map(_ * 2)
println(myMoney.sum + " dollars will accru in 1 year")
}
}
Run Code Online (Sandbox Code Playgroud)
假设我们在ATM机上,并且它正在使用此代码向我们提供帐户信息。
您执行以下操作:
scala> val myAccount = new Account()
myAccount: Account = Account@7f4a6c40
scala> myAccount.getBalance
28 dollars available
scala> myAccount.myMoneyWithInterest
56 dollars will accru in 1 year
scala> myAccount.getBalance
56 dollars available
Run Code Online (Sandbox Code Playgroud)
mutated当我们想查询当前余额以及一年的利息时,我们就是账户余额。现在,我们的帐户余额有误。对银行来说是个坏消息!
如果我们使用val而不是var跟踪myMoney类定义,那么我们将无法mutate赚钱并增加余额。
在定义类(在REPL中)时,使用val:
error: reassignment to val
myMoney = myMoney.map(_ * 2
Run Code Online (Sandbox Code Playgroud)
Scala告诉我们我们想要一个immutable值,但正在尝试改变它!
感谢Scala,我们可以切换到val,重新编写myMoneyWithInterest方法,并放心我们的Account类永远不会改变余额。