如何在Haskell中的函数中重新分配变量?

时间:2017-04-20 16:37:59

标签: haskell

我不知道如何在函数中重新赋值变量。

例如,

elephant = 0

function x = elephant = x

为什么这不起作用?

5 个答案:

答案 0 :(得分:110)

Haskell是一种很棒的imperative语言,编写可以重新分配状态的程序是一个非常有趣的高级主题!这绝对不是你现在想要的方法,但有一天会回到它身上

定义一个为全局可变变量建模的环境需要花费一些精力。但是,一旦掌握了它,类型的精确度最终会变得非常方便。

我们将继续使用lensmtl库。

{-# 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 mHasElephant 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