从功能上思考。在 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
- 只是简单、安全、纯功能代码。
我是函数式编程的新手,我决定用 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
- 只是简单、安全、纯功能代码。