我不知道如何在函数中重新赋值变量。
例如,
elephant = 0
function x = elephant = x
为什么这不起作用?
答案 0 :(得分:110)
Haskell是一种很棒的imperative语言,编写可以重新分配状态的程序是一个非常有趣的高级主题!这绝对不是你现在想要的方法,但有一天会回到它身上
定义一个为全局可变变量建模的环境需要花费一些精力。但是,一旦掌握了它,类型的精确度最终会变得非常方便。
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Monad.State
我会坚持使用整数作为你的问题,但我们会引入一个类型别名来提醒自己它们被用作elephant
变量的类型。
type Elephant = Integer
你想要一个全局可变状态有大象的程序。所以首先让我们来定义拥抱大象意味着什么。 Lens
很好地捕捉了这个概念。
class HasElephant a
where
elephant :: Lens' a Elephant
现在我们可以定义function
,为elephant
分配一个新值。
function :: (MonadState s m, HasElephant s) => Elephant -> m ()
function x =
elephant .= x
约束MonadState s m
和HasElephant s
表示我们的程序必须能够保持某种类型s
的可变状态,并且类型s
必须有大象。< / p>
让我们定义一个打印大象的程序。
printElephant :: (MonadState s m, HasElephant s, MonadIO m) => m ()
printElephant =
use elephant >>= (liftIO . print)
此程序执行I / O(打印),因此我们有一个额外的约束MonadIO m
,表示我们的程序类型m
必须能够执行I / O.
elephant
变量可能只是某个较大程序状态的一部分。让我们在这里定义一个数据类型来代表整个州(我们将其命名为刚刚,因为刚果盆地是大象居住的地方)。
data Congo = Congo
{ _congoElephant :: Elephant
}
makeLenses ''Congo
(请参阅Control.Lens.TH,了解makeLenses
使用模板Haskell做的一点点。)
我们必须定义Congo
有大象的方式。
instance HasElephant Congo
where
elephant = congoElephant
现在我们可以编写一个示例程序。我们的程序会打印elephant
的值,然后更改elephant
的值,然后重新打印。
main' :: StateT Congo IO ()
main' =
do
printElephant
function 2
printElephant
然后我们可以运行这个程序。
main :: IO ()
main = Congo 0 & runStateT main' & void
输出结果为:
0
2
答案 1 :(得分:43)
我试图重新分配现有变量
你不能在Haskell中做到这一点。您可以使用IORef
来做一些事情,但这很少是问题的正确解决方案 - 当然不是在初学者可能遇到的情况下。
相反,你应该重新设计程序逻辑,这样它就不需要可变变量来运行。
答案 2 :(得分:24)
Haskell是函数式编程领域的领导者,函数式编程通常被称为“无需编程的编程”。不使用赋值几乎是函数式编程的整个点。一旦你使用它,你就不再以“功能”的方式做到这一点了。当然有时间,但FP试图最小化这些时间。
所以,回答你的问题,“为什么这不起作用?”首先,语法不正确。 recyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
并不意味着在Haskell中进行分配。它将名称绑定到表达式。你不能这样做两次(在相同的范围内)。换句话说,“变量”是不可变的(就像在数学中一样)。其次,突变是一种副作用行为,而Haskell将这些行为视为必须在=
世界中完成的不纯行为。
我可以告诉你如何实际在Haskell中改变一个引用,但我认为这不是你现在需要的。
答案 3 :(得分:11)
将变量x
绑定到值v
的最原始方法是编写一个以x
为参数的函数,并将v
传递给该函数。 / p>
这有时可以用来模拟&#34;变量变量的影响。
,例如,命令式代码
// sum 0..100
i = s = 0;
while (i <= 100) {
s = s+i;
i++;
}
return s;
变为
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
上面的代码不是很好的FP代码,但至少它足够接近命令式代码,以便能够发现连接。
最后的题外话:
当第一个注意到这一点时,通常会引诱它落入Blub paradox。人们可以很容易地想到:&#34;什么!? Haskell需要所有这些东西来模拟一个简单的赋值吗?如果在语言Blub任务是微不足道的,并且在Haskell中模拟需要这么多努力,那么显然Blub比Haskell好得多!&#34;。这将是Blub悖论的一个完美案例:当Blub程序员转向另一种语言时,他们会立即感知到Blub不能直接翻译的内容,并且没有注意到新语言的所有其他功能都没有出现在泡壳。 他们的思想现在在&#34; Blub&#34;中思考,并且需要付出巨大努力来适应新模型。
几乎同样矛盾的是,学习两者 FP和命令式编程之所以有用,恰恰是因为在习惯其中一种范式时学习另一种范式并非易事。如果他们之间的步骤很窄,那么就不值得努力学习两个接近同一问题的方法。
答案 4 :(得分:3)
通常,这不起作用,因为您通常会创建不可变的声明,而不是指定一系列操作。你可以这样做:
elephant = 3
main = print elephant
但你也可以这样做:
main = print elephant
elephant = 3
由于代码没有指定执行顺序,因此无法将多个分配解释为错误以外的任何内容。
如果要指定一系列操作,请使用执行表示法:
main = do
let elephant = 0
print elephant
let elephant = 1
print elephant
let elephant = 2
print elephant
执行块中的代码按顺序执行,因此您可以在大多数编程语言中有效地重新分配变量。
请注意,此代码实际上只是为elephant创建了一个新绑定。旧值仍然存在:
main = do
let elephant = 1
print elephant
let printElephant = print elephant
let elephant = 2
print elephant
printElephant
因为我定义的printElephant函数仍然使用elephant的旧值,所以打印:
1
2
1