【发布时间】:2018-02-08 20:47:25
【问题描述】:
我在构建一个可以在许多情况下用作电子表格替代品的应用程序方面还处于早期阶段。它为用户提供了一种非表格的方法来创建和组织分析。用户的指令被转译为 Javascript 并由 Javascript 执行(仅使用纯函数)。 (界面介于 Google Blockly 和文本编辑器中的直接编码之间。)代替您通常在电子表格中使用的单元格区域,该产品使用 Javascript 数组。我使用这种方法面临的一个问题是(准)循环计算,例如在简单的摊销计划中发现的计算。
到目前为止,我(未成功)尝试解决该问题涉及:
- 惰性列表评估(使用https://github.com/dankogai/js-list-lazy)
- 将所有转译的 JS 封装在函数中以延迟 eval,这样用户的内容就不需要进行拓扑排序(目前是这样)。
下面我将提供足够的上下文来说明这个问题,这可以很广泛地推断出来。以抵押贷款的摊销时间表为例:
基本上,BOP(“期初”)余额取决于上一期的 EOP(“期末”)余额。 EOP 余额取决于同期的 BOP 余额。在使用连续单元格范围的电子表格中,这不是循环的,因为每个 BOP 余额和 EOP 余额都是一个离散单元格。但是,如果 BOP Balance 和 EOP Balance(以及所有其他基于时间序列的计算)是数组,则在尝试检索元素时存在循环引用。该示例的电子表格屏幕截图在文章末尾提供作为补充。
尝试在我的应用中构建此分析会生成以下 JS(为清楚起见,我已对其进行了编辑和重新组织)。这段代码,如果翻译成电子表格,就可以正常工作(请参阅帖子末尾的补充屏幕截图):
// Credit: Apache OpenOffice
function payment (rate, periods, present_value, future_value, type) {
var fv = future_value || 0
var t = type || 0
if (rate === 0) {
return -1 * ((present_value + fv) / periods)
} else {
var term = (1 + rate) ** periods // Transpiling with Babel; otherwise, use Math.pow(1 + rate, periods)
if (t === 1) {
return -1 * ((fv * rate / (term - 1) + present_value * rate / (1 - 1 / term)) / (1 + rate))
} else {
return -1 * (fv * rate / (term - 1) + present_value * rate / (1 - 1 / term))
}
}
}
var loan_principal = 1000000
var annual_interest_rate = 0.06
var interest_rate_monthly = annual_interest_rate / 12
var amortization_period_years = 25
var amortization_period_months = amortization_period_years * 12
var months_range = _.range(1, amortization_period_months + 1, 1) // See: http://underscorejs.org/#range [1, 2, 3, ... 298, 299, 300]
var bop_balance = months_range.map(function (current_element, current_list_position) {
if (current_list_position === 0) {
return loan_principal
} else {
// Along with eop_balance, this causes a graph cycle
return eop_balance[current_list_position - 1]
}
})
var monthly_payment = months_range.map(function (current_element, current_list_position) {
return payment(interest_rate_monthly, amortization_period_months, loan_principal)
})
var principal_payment = months_range.map(function (current_element, current_list_position) {
var current_mthly_pmt = monthly_payment[current_list_position]
var current_int_pmt = interest_payment[current_list_position]
return current_mthly_pmt - current_int_pmt
})
var interest_payment = months_range.map(function (current_element, current_list_position) {
if (current_list_position === 0) {
return loan_principal * interest_rate_monthly * -1
} else {
var previous_bal = eop_balance[current_list_position - 1]
return previous_bal * interest_rate_monthly * -1
}
})
var eop_balance = months_range.map(function (current_element, current_list_position) {
// This causes a graph cycle
var cur_bal = bop_balance[current_list_position]
var cur_prin_pmt = principal_payment[current_list_position]
return cur_bal + cur_prin_pmt
})
由于 bop_balance 和 eop_balance 之间的循环,此代码不会进行拓扑排序。由于循环引用,它不会完全评估。
关于如何解决这种一般情况的任何建议?谢谢。
补充信息:
对应用中纯函数的依赖是为了尽量减少来自电子表格的用户的困惑。
如果查看实际应用有助于提供上下文,请随时访问https://www.getpinevale.com。我把它放在最后,以免分散问题的注意力。
【问题讨论】:
标签: javascript arrays spreadsheet lazy-evaluation circular-reference