从功能上思考。在 Haskell/Purescript 中构建新数组

Thinking Functionally. Building a New Array in Haskell / Purescript

我是函数式编程的新手,我决定用 Purescript 构建一个应用程序。我遇到了第一个障碍,但我不确定如何从概念上考虑这个问题。

I'm not looking for code as much as a way to think functionally about this problem.

我有一个数据列表。具体来说,像

[ {a :: String, b :: String, c :: String} ]

我想使用提供的记录(包含上述类型的列表)创建 Html 的列表(这是 purescript-halogen 类型)。

所以,我会有一个函数

buildElements :: forall p i. MyRecordObject -> Array (HTML p i)

现在,我想我需要给这个函数结果类型一个 Monad 计算上下文(纯脚本 Eff 就像 Haskell IO

所以像这样:

buildElements :: forall p i. MyRecordObject -> Eff (Array (HTML p i))

我的第一个想法是模糊地围绕创建一个类似于

的列表
take $ length xs $ repeat ARecordObject

然后将记录映射到该列表,但我不确定如何将其转换为代码。无论如何,这似乎是错误的,因为我的计划涉及改变 ARecordObject 的状态,这是不行的。

于是我找到了这个函数:

forEach :: forall e a. Array a -> (a -> Eff e Unit) -> Eff Unit

看起来近乎完美!我得到一个数组,我给它一个函数,以某种方式将记录中的属性分配给这个新数组......但是不,等等......我又在非功能性地思考了。

我真的有点不知所措。基本上,我想创建类似于 <li></li> 元素列表的东西,我在其中为每个项目分配属性。

例如

我获得了一条记录:

[ { id: "id1", name: "name1", class: "class1", content: "content1" }
, { id: "id2", name: "name2", class: "class2", content: "content2" } ]

我想要一个 foo 函数 returns 一个数组:

[ li [ id_ rec.id, name_ rec.name, class_ rec.class ] [ text rec.content ]
, li [ id_ rec.id, name_ rec.name, class_ rec.lass ] [ text rec.content ] ]

其中 rec 是 recordObject 的名称(显然这两个数组并不相同,但实际上映射到初始记录)。

(点语法是类似于标准 getter/setter 表示法的纯脚本记录语法表示法)

My first idea was vaguely around creating a list with something like

take $ length xs $ repeat ARecordObject

and then map the record over that list, but I wasn't really sure how to translate that into code. It seemed wrong anyway, since my plan involved mutating the state of ARecordObject, which is a no-no.

函数式程序员不只是因为它是 no-no 而避免突变(事实上,许多函数式程序会谨慎使用可控剂量的可变性)——我们这样做是因为它会产生更安全、更简单的代码。

即:您正在思考我所说的 "alloc-init mode",其中您创建某种 "empty" 值,然后开始计算其属性。请原谅我的激烈,但这是一个 从根本上被破坏的 编程模型,是手动内存管理时代遗留下来的;使用它的代码永远不会安全,依赖它的抽象将永远存在漏洞。这个习语不适合任何 higher-level 除了 C 的语言,然而,如果我每次看到这样的代码都感到震惊...

var foo = new Foo();
foo.Bar = new Bar();
foo.Bar.Baz = new Baz();

...我会成为一个有钱人(na na na)。默认应该是在知道对象的外观后创建对象:

var foo = new Foo(new Bar(new Baz()));

这更简单 - 您只是计算一个值,而不是进入指针引用的内存来更新其内容 - 更重要的是它更安全,因为 type-checker 确保您没有忘记 属性 并且它允许您使 Foo 不可变。最干净的命令式代码是函数式代码——只有在性能需要时(或者当语言迫使你动手时)你才应该是命令式的。


总之,吐槽一下。关键是你通过命令式思考让自己的生活变得比必要的更艰难。只需编写一个函数,从单个对象计算单个 <li>...

toLi :: MyRecord -> HTML
toLi x = li [ id_ x.id, name_ x.name, class_ x.class ] [ text x.content ]

...(请注意,我不是以某种方式创建 "empty" li 然后填充其值),然后 map 它在您的输入列表中。

toLis :: [MyRecord] -> [HTML]
toLis = map toLi

这也是我在 JS 中的做法,即使我不是 语言所要求的。没有 side-effects,没有变异,不需要 Eff - 只是简单、安全、纯功能代码。