让我们在你的程序中说你定义了一个复杂的汽车对象。该对象包含很长的预定义键值对列表(wheels
,engine
,color
,lights
,amountDoors
等),每个都是部件号或部件号列表或特定值。
//** PSEUDO CODE:
var inputCar = {
"engine": "engine-123",
"lights": ["light-type-a", "light-type-b"],
"amountDoors": 6,
etc ... lets assume a lot more properties
}
让我们假设,这个对象已经尽可能简单,不能进一步减少。
此外,我们还有一个设置列表,它们告诉我们有关零件编号的更多信息,并且每种零件都有所不同。对于引擎,它可能看起来像这样:
var settingsEngine = [
{ "id": "engine-123", weight: 400, price: 11000, numberScrews: 120, etc ... },
{ "id": "engine-124" etc ... }
]
所有设置都捆绑在主设置对象中
settings = { settingsEngine, settingsWheel, settingsLight ... }
现在我们有不同的功能,应该采取Car
并返回有关它的某些值,如重量,价格或螺丝数量。
要计算那些必要的值来匹配输入汽车的ID和来自设置的ID,并且还应用一些逻辑来获得复杂零件的确切数据(为了弄清楚车主的样子,我们需要看看有多少门有,车轮有多大等等。
对于汽车的每个部分来说,获得价格也会有所不同并且任意复杂。定价的每个部分都需要访问汽车的不同部分和信息,因此仅仅映射部件列表是不够的。 (对于油漆工作的价格,我们需要具有相同颜色的所有零件的总表面积等)
一个想法是创建一个inbetween对象,它已经解决了价格和重量计算之间共享的汽车的所有细节,然后可以用来计算重量,价格等。
一个实现可能看起来像这样:
var detailedCar = getDetailedCar(inputCar, settings);
var priceCar = getPriceCar(detailedCar);
var weightCar = getWeightCar(detailedCar);
这样,部分工作只需完成一次。但是在这个例子中,detailedCar
将是一个比初始输入对象更复杂的对象,因此getPriceCar
的参数也是如此 - 这使得它也很难测试,因为我们总是需要为每个测试用例提供一个完整的汽车对象。所以我不确定这是不是一个好方法。
对于处理复杂输入数据的程序来说,什么是一个好的设计模式,这些数据无法在函数式编程风格/纯函数/组合中进一步简化?
如果给出复杂,相互依赖的输入,结果如何才能轻松进行单元测试?
您所描述的一般术语是使用预测。投影是一种数据结构,它是其他数据结构的抽象,面向您想要进行的各种计算。
在您的示例中,您需要一个“螺旋投影”,它可以获取描述车辆的数据并提供所需的螺钉。因此,我们定义一个函数:
screwProjection(vehicle, settings) -> [(screwType, screwCount)]
它采用车辆和描述组件的设置,并提出构成车辆的螺钉。如果你不关心screwType
,你还可以进一步投影,简单地总结元组中的第二项。
现在,为了分解screwProjection()
,你将需要一些迭代车辆的每个组件的东西,并根据需要进一步分解。例如,在您的示例中的第一步,获取engine
并找到适合引擎的设置,并根据引擎类型进行过滤,然后根据螺钉字段筛选结果:
partProjection(part, settings) -> [(partType, partCount)]
所以,screwProjection()
看起来像:
vehicle.parts
.flatMap( part -> partProjection( part, settings ) ) // note 1
.filter( (partType, partCount) -> partType == 'screw' )
.map( (partType, partCount) -> partCount )
.sum()
注1)此投影方法不允许嵌套的物料清单查找,您可能希望添加这些查询以获得额外的信用。
这种枚举的一般方法=> projection => filter => reduce是许多函数式编程范例的核心。
我会在这里建议略有不同的方法。
既然你的问题是关于纯粹的函数式编程,我会说你需要一个更高阶的函数来负责减轻复杂数据结构所需的位和阴影不必要的那些:readComplexDataStructure :: (ComplexDataStructure -> a) -> (a -> b) -> ComplexDataStructure -> b
,其中a
代表你需要从一些ComplexDataStructure
实例中提取的数据和b
是一个计算结果。
请注意它与Reader
monad有多接近,但我不建议马上使用它,除非代码复杂性证明这样的决定。
附:它可以扩展。你只需要一个函数来产生由(ComplexDataStructure -> a)
投影组成的n-uple。例如,请考虑以下签名:double :: (ComplextDataStructure -> a) -> (ComplexDataStructure -> b) -> ( (a, b) -> c) -> ComplexDataStructure -> c
。只要你只保留适当的预测,你的代码就不会变得“臃肿”,所有其他代码都非常具有组合性和自我描述性。