简体   繁体   English

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

[英]Thinking Functionally. Building a New Array in Haskell / Purescript

I'm new to functional programming, and I've decided to build an app in Purescript. 我是函数式编程的新手,我决定在Purescript中构建一个应用程序。 I've hit my first hurdle, and I'm not sure how to think about this conceptually. 我遇到了第一个障碍,我不确定如何从概念上思考这个问题。

I'm not looking for code as much as a way to think functionally about this problem. 我不是在寻找代码,而是在功能上思考这个问题。

I have a list of data. 我有一个数据列表。 Specifically, something like 具体来说,就像

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

I would like to create a list of Html (which is a purescript-halogen type) by using the record provided (with a list of the above types). 我想通过使用提供的记录(带有上述类型的列表)创建一个Html列表(这是一个purescript-halogen类型)。

So, I would have a function 所以,我会有一个功能

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

Now, I think I'm going to need to give this function result type a Monad computational context (purescript Eff is like Haskell IO ) 现在,我想我需要给这个函数结果类型一个Monad计算上下文(purescript Eff就像Haskell IO

So something like: 所以类似于:

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

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. 无论如何,这似乎是错误的,因为我的计划涉及改变ARecordObject的状态,这是一个ARecordObject

So then I found this function: 那么我发现了这个功能:

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

which looks almost perfect! 看起来几乎完美! I get an array, I give it a function that somehow assigns the properties in the record to this new array...but no, wait...I'm thinking non-functionally again. 我得到一个数组,我给它一个函数,以某种方式将记录中的属性分配给这个新数组......但不,等等......我正在考虑非功能性。

I'm really at a bit of a loss here. 我真的在这里有点亏。 Basically, I want to create something like a list of <li></li> elements, where I assign properties to each item. 基本上,我想创建类似<li></li>元素的列表,其中我为每个项目分配属性。

Eg 例如

I'm provided a record with: 我提供了一条记录:

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

And I would like a function foo that returns an array: 我想要一个返回数组的函数foo

[ 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 ] ]

where rec is the name of the recordObject (and obviously the two arrays are not identical, but actually mapped over the initial record). 其中rec是recordObject的名称(显然这两个数组不相同,但实际上映射到了初始记录)。

(the dot syntax is a purescript record syntax notation similar to standard getter/setter notation) (点语法是类似于标准getter / setter表示法的purescript记录语法表示法)

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. 无论如何,这似乎是错误的,因为我的计划涉及改变ARecordObject的状态,这是一个ARecordObject

Functional programmers don't just avoid mutation because it's a no-no (indeed, many functional programs make careful use of a controlled dose of mutability) - we do it because it produces safer, simpler code. 功能程序员不仅仅是避免变异,因为它是禁忌(实际上,很多功能程序都会谨慎使用受控剂量的可变性) - 我们这样做是因为它产生更安全,更简单的代码。

To wit: You're thinking in what I call "alloc-init mode", wherein you create some sort of "empty" value and then go about calculating its properties. 也就是说:你正在考虑我称之为“alloc-init模式”,其中你创建了某种“空”值,然后继续计算它的属性。 Forgive my vehemency, but that's a fundamentally broken programming model, left over from the days of manual memory management; 原谅我的愤怒,但这是一个从根本上破坏的编程模型,从手动内存管理的时代遗留下来; code which uses it will never be safe and abstractions relying on it will forever be leaky. 使用它的代码永远不会安全,依赖它的抽象将永远是漏洞。 The idiom doesn't fit into any language that's higher-level than C, and yet, if I had a pound for every time I see code like this... 这个成语不适合任何比C更高级的语言,然而,如果我每次看到这样的代码时都有一英镑......

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

...I would be a rich man (na na na). ......我会成为一个有钱人(娜娜娜)。 The default should be to create objects after you know what they're going to look like: 默认应该是在您知道它们的外观后创建对象:

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

This is simpler - you're just calculating a value , rather than reaching into the memory referenced by a pointer to update its contents - and more importantly it's safer because the type-checker ensures that you haven't forgotten a property and it allows you to make Foo immutable. 这更简单 - 你只是计算一个值 ,而不是到达指针所引用的内存以更新其内容 - 更重要的是它更安全,因为类型检查器确保你没有忘记一个属性它允许你使Foo不可变。 The cleanest imperative code is functional code - you should only be imperative where necessary for performance (or when the language forces your hand). 最干净的命令性代码是功能代码 - 只有在必要的性能(或语言强迫你的手)时,你才应该这样做。


Anyway, rant over. 无论如何,咆哮过来。 The point is that you're making life harder for yourself than necessary by thinking imperatively. 重点在于,通过强制性思考,你使自己的生活变得更加艰难。 Just write a function that calculates a single <li> from a single object... 只需编写一个从单个对象计算单个<li>的函数...

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

... (note that I'm not somehow creating an "empty" li and then populating its values), and then map it over your input list. ...(请注意,我不是以某种方式创建一个“空” li然后填充其值),然后mapmap到输入列表上。

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

This is how I'd do it in JS, too, even though I'm not required to by the language. 这就是我在JS中的表现,即使我不需要语言。 No side-effects, no mutation, no need for Eff - just simple, safe, purely functional code. 没有副作用,没有变异,不需要Eff - 只是简单,安全,纯粹的功能代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM