我不知道如何在函数中重新赋值变量.
例如,
elephant = 0
function x = elephant = x
Run Code Online (Sandbox Code Playgroud)
为什么这不起作用?
Chr*_*tin 110
Haskell是一门伟大的命令式语言,编写可以重新分配状态的程序是一个非常有趣的高级主题!这绝对不是你现在想要的方法,但有一天会回到它
定义一个模拟全局可变变量的环境需要花费一些精力.但是,一旦掌握了它,类型的精确度最终会非常方便.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad.State
Run Code Online (Sandbox Code Playgroud)
我会坚持使用整数作为你的问题,但我们会抛出一个类型别名来提醒自己它们被用作elephant
变量的类型.
type Elephant = Integer
Run Code Online (Sandbox Code Playgroud)
你想要一个全局可变状态有大象的程序.首先让我们来定义一只大象的意义.Lens
很好地捕捉了这个概念.
class HasElephant a
where
elephant :: Lens' a Elephant
Run Code Online (Sandbox Code Playgroud)
现在我们可以定义function
,为其分配一个新值elephant
.
function :: (MonadState s m, HasElephant s) => Elephant -> m ()
function x =
elephant .= x
Run Code Online (Sandbox Code Playgroud)
约束MonadState s m
并且HasElephant s
说我们的程序必须能够保持某种类型的可变状态s
,并且类型s
必须有大象.
我们还定义一个打印大象的程序.
printElephant :: (MonadState s m, HasElephant s, MonadIO m) => m ()
printElephant =
use elephant >>= (liftIO . print)
Run Code Online (Sandbox Code Playgroud)
该程序执行I/O(打印),因此我们有一个额外的约束MonadIO m
,即我们的程序类型m
必须能够执行I/O.
该elephant
变量可能是一些较大的程序状态的只是其中的一部分.让我们在这里定义一个数据类型来代表整个州(我们将其命名为刚果,因为刚果盆地是大象居住的地方).
data Congo = Congo
{ _congoElephant :: Elephant
}
makeLenses ''Congo
Run Code Online (Sandbox Code Playgroud)
(有关使用Template Haskell的信息,请参阅Control.Lens.THmakeLenses
.)
我们必须确定Congo
拥有大象的方式.
instance HasElephant Congo
where
elephant = congoElephant
Run Code Online (Sandbox Code Playgroud)
现在我们可以编写一个示例程序.我们的程序将打印值elephant
,然后更改值elephant
,然后再次打印.
main' :: StateT Congo IO ()
main' =
do
printElephant
function 2
printElephant
Run Code Online (Sandbox Code Playgroud)
然后我们就可以运行这个程序.
main :: IO ()
main = Congo 0 & runStateT main' & void
Run Code Online (Sandbox Code Playgroud)
输出是:
0
2
Run Code Online (Sandbox Code Playgroud)
sep*_*p2k 43
我试图重新分配一个现有的变量
你不能在Haskell做到这一点.你可以通过使用IORef
s 来做一些事情,但这很少是问题的正确解决方案 - 当然不是在初学者可能遇到的情况下.
相反,您应该重新设计您的程序逻辑,以便它不需要可变变量来运行.
Ell*_*ron 24
Haskell是函数式编程领域的领导者,函数式编程通常被称为"无需编程的编程".不使用赋值几乎是函数式编程的全部要点.一旦你使用它,你就不再以"功能"的方式做到这一点了.当然有时间,但FP试图最小化这些时间.
所以,回答你的问题,"为什么这不起作用?" 首先,语法不正确.=
并不意味着在Haskell中进行分配.它将名称绑定到表达式.你不能这样做两次(在相同的范围内).换句话说,"变量"是不可变的(就像在数学中一样).其次,突变是一种副作用,Haskell将这些视为必须在IO
世界上进行的不纯行为.
我可以告诉你如何实际改变Haskell中的引用,但我不认为这就是你需要的东西.
chi*_*chi 12
将变量绑定x
到值的最原始方法v
是编写一个x
作为参数的函数,并传递v
给该函数.
这有时可以用来"模拟"可变变量的效果.
例如,命令式代码
// sum 0..100
i = s = 0;
while (i <= 100) {
s = s+i;
i++;
}
return s;
Run Code Online (Sandbox Code Playgroud)
变
final_s = f 0 0 -- the initial values
where
f i s | i <=100 = f (i+1) (s+i) // increment i, augment s
| otherwise = s // return s at the end
Run Code Online (Sandbox Code Playgroud)
上面的代码不是很好的FP代码,但至少它足够接近命令式代码,以便能够发现连接.
最后的题外话:
当人们第一次注意到这一点时,通常会被引诱陷入Blub悖论.人们可以很容易地想到:"什么!?Haskell需要所有这些东西来模拟一个简单的任务?如果在语言Blub任务是微不足道的,并且在Haskell中模拟需要这么多的努力,那么显然Blub比Haskell好得多!".这将是Blub悖论的一个完美案例:当Blub程序员转向另一种语言时,他们会立即感知到Blub不能直接翻译的内容,并且没有注意到新语言的所有其他功能都没有出现在泡壳.他们的思想现在在"Blub"中思考,需要付出巨大的努力来适应新的模型.
几乎同样矛盾的是,学习都 FP和命令式编程是非常有用的,正是因为它的不平凡仅用于那些之一,当学习其他范式.如果他们之间的步骤狭窄,那么就不值得努力学习两个接近同一问题的方法.