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

Fl4*_*mer 11 haskell

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

例如,

elephant = 0

function x = elephant = x
Run Code Online (Sandbox Code Playgroud)

为什么这不起作用?

Chr*_*tin 110

Haskell是一门伟大的命令式语言,编写可以重新分配状态的程序是一个非常有趣的高级主题!这绝对不是你现在想要的方法,但有一天会回到它

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

我们将使用镜头mtl库.

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

  • 这个答案非常愚蠢,几乎无法解决这个问题.+1. (38认同)
  • @DavidYoung我不希望我的答案能为提问者提供任何即时效用.其他人已经这样做了.这个答案就在这里,因为我一直认为偶尔瞥见一些你无法理解的东西,从你脚下的小路上抬头,并凝视着云层中山的形状是很有价值的. (36认同)
  • 虽然这些都是一般要知道和使用的好东西,但我觉得这可能不是谈论`StateT`,monad变换器,类型类和镜头的最合适的时间......如果有人不知道Haskell的这个方面(没有按照像C这样的语言进行分配)并且以前没有遇到过这种差异,我怀疑他们还没有这方面的背景,甚至可能会吓跑他们(我*可能是错的,当然,但我有一种非常强烈的怀疑......). (25认同)
  • 评论似乎暗示这是一个糟糕/愚蠢/无答案,但这是回答问题的唯一答案(除非你将其解释为"为什么这种语法不能解析").其他答案只说"你不能这样做 - 这里是如何将一个命令式的,可变的程序重写成一个功能性,不可变的程序".这个答案告诉人们如何定义一些东西来模拟可变的全局变量.(但也许这个答案会受益于不太自由的使用深奥的符号) (2认同)

sep*_*p2k 43

我试图重新分配一个现有的变量

你不能在Haskell做到这一点.你可以通过使用IORefs 来做一些事情,但这很少是问题的正确解决方案 - 当然不是在初学者可能遇到的情况下.

相反,您应该重新设计您的程序逻辑,以便它不需要可变变量来运行.

  • 在这一点上,我怀疑有任何程序逻辑需要重新设计.OP只是想知道如何在Haskell中做他们在其他语言中做的事情. (6认同)

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和命令式编程是非常有用的,正是因为它的不平凡仅用于那些之一,当学习其他范式.如果他们之间的步骤狭窄,那么就不值得努力学习两个接近同一问题的方法.