我正在努力学习Haskell。我正在尝试编写一个包含“全局状态”的程序:{# file1.twig #}
{% extends "main.twig" %}
{% block content %}content text{% endblock %}
{# main.twig #}
{% block content %}{% endblock %}
{% for widget in widgets %}
{% embed widget %}{% endembed %}
{% endfor %}
{% block js %}{% endblock %}
{# widget1.twig #}
{% block js %}
script1
{% endblock %}
{# widget2.twig #}
{% block js %}
script1
{% endblock %}
。我想在每次调用函数时更改状态的组件(例如content text
script1
script2
)。改变可以是组件上的简单功能(例如+4)。此外,它打印出更改的组件。这是我到目前为止所做的事情(但我被困住了)。编辑:运行代码后,我想看到最新版本的全局状态。
boxplot(train$rate)
答案 0 :(得分:2)
让我们尝试从更简单的小部件开始逐步解决您的问题。它在编程和FP方面的重要技能以很好的方式教你技能。此外,使用State
monad,尤其是monad-transformer中的多种效果,可以帮助您推理效果并更好地理解事物。
您希望更新不可变数据类型中的var1
。这只能通过创建新对象来完成。所以让我们写下这样的功能:
plusFour :: Vars -> Vars
plusFour (Vars v1 v2) = Vars (v1 + 4) v2
Haskell中有很多方法可以将这个功能写得更短,虽然不太容易理解,但我们现在不关心这些功能。
现在,您希望在State
monad中使用此函数来更新不可变状态,并通过此模拟可变性。只有通过查看类型签名才能告诉我这个函数:change :: StateT Vars IO a
?我们可以说这个函数有几个效果:它可以访问Vars
状态,它可以执行任意IO
个动作。此函数还返回类型a
的值。嗯,这最后一个很奇怪。什么是a
?这个函数应该返回什么?在命令式编程中,此函数将具有类型void
或Unit
。只是做事情,它不会归还所有东西。仅更新上下文。因此,它的结果类型应为()
。它可以是不同的。例如,我们可能希望在更改后返回新的Vars
。但这通常是编程中不好的方法。它使这个功能更加复杂。
在我们理解了应该具有什么类型的函数之后(尝试始终从定义类型开始),我们可以实现它。我们想要改变我们的状态。具有与我们的上下文的有状态部分一起操作的功能。基本上,你对这个感兴趣:
modify :: Monad m => (s -> s) -> StateT s m ()
modify
函数采用更新状态的函数。运行此函数后,您可以观察到根据传递的函数修改了状态。现在change
可以这样写:
change :: StateT Vars IO ()
change = modify plusFour
您可以仅使用modify
和change
函数实现put
(以及get
,这对初学者来说非常好。)
现在让我们从其他函数调用change
函数。在这种情况下,呼叫意味着什么?这意味着您执行monadic action change
。此操作会更改您的上下文,您不会关心它的结果,因为它是()
。但是如果你在get
之后运行change
函数(将整个状态绑定到变量),你可以观察到新的变化。如果您只想打印已更改的组件,例如var1
,则可以使用gets
功能。而且,sample
应该具有哪种类型?应该归还什么?如果在来电方面你只对结果状态感兴趣,那么,它应该是()
,如下所示:
sample :: StateT Vars IO ()
sample = do
change
v1 <- gets var1
liftIO $ print v1
change
v1' <- gets var1
liftIO $ print v1' -- this should be v1 + 4
这应该让你对正在发生的事情有所了解。 Monad变形金刚需要一些时间来适应它们,虽然它是一个强大的工具(不完美但非常有用)。
作为旁注,我想补充一点,使用常见的Haskell设计模式可以更好地编写这些函数。但是你现在不需要关心这些,只是试着了解这里发生了什么。