javascript - 如何在整个 web 应用程序堆栈中利用 Haskell 的类型安全?
我在想,一个以CRUD为中心的网络应用程序能从Haskell的类型系统中获得多少好处,特别是当前端使用像AngularJS这样的Javascript MVC框架构建,并传递无类型的数据对象时。
在我看来,一旦你将 Haskell 数据类型转换为 JSON 对象并传递给庞大的 JavaScript MVC 框架层,Haskell 类型系统作为 web 堆栈一部分的好处就会显著减弱,因为没有办法让类型检查器确保整个 web 应用程序中数据流的类型完整性。
例如,你可以更改数据库模式和相关的 Haskell 类型,但类型检查器无法告诉你 JavaScript MVC 前端的哪些部分也需要更新。我认为这是一个问题。
我是否正确陈述了问题,如果是的话,Haskell 网络应用开发者在这一点上能提供什么建议?
答案#1
自从我们最近开始一个拥有大量 JavaScript 前端的项目以来,我们一直在反复思考这个完全相同的问题。根据我的个人观察,我们在 JavaScript 应用程序中遇到的错误比以前仅使用 Snap 并通过 Heist 生成 HTML 的应用程序要多得多。我们尚未做出任何决定,但这里是我们一直在考虑的一些可能的解决方案:
轻量级 Javascript 封装
- CoffeeScript
- TypeScript
- 飞镖
对我来说,这些解决方案相当不令人满意。它们比 JavaScript 略有改进,但远不能给我 Haskell 所提供的那些东西。
功能更强大且类型安全的前端语言
- Fay 是 Haskell 的一个真子集,可以编译成 Javascript。它确实有一定的吸引力,但不能让你访问 Haskell 的全部功能。据我所知,它不支持类型类,我想这很快就会成为一个障碍。
- 榆树
- 罗伊
这些解决方案(以及前一组解决方案)的问题在于,如果你的后端是用 Haskell 编写的,你仍然会遇到阻抗不匹配的问题,因为你的前端语言不是 Haskell。这会使你的代码不够 DRY,因为你必须在 Haskell 和前端语言中定义相同的数据结构。而当你在 Haskell 中更改它们时,你不会得到任何错误提示指出前端代码需要修改的位置。应用程序只是崩溃。
将 Haskell 编译为 Javascript
这里的新游戏是 ghcjs。 这是一个非常有前途的项目,但我认为至少在 GHC 7.8 发布之前,它还不适合用于生产环境。 希望那会在下周内发生。 一旦 7.8 发布,你仍然必须考虑到 ghcjs 仍然非常新。 即使在假设它功能已经完全并且第一次发布运行完美的情况下,你仍然必须记住,在 Haskell+ghcjs 能像 Angular、Ember 等高级 JavaScript 框架一样高效之前,还需要建立相当多的基础设施。
更新于2016年9月:现在,距离我最初写下这个答案几乎三年后,GHCJS有了很大改进。虽然仍有进一步改进的空间,但我已经在生产应用中使用过它,而且效果非常好。当它与Reflex FRP库结合使用时尤其强大,这使得构建响应式用户界面变得更加容易。
使用 EDSL 从 Haskell 生成 Javascript
如果你的问题相对受限,那么有可能在生成 JavaScript 的 EDSL 上完成所有应用工作。我们已经有了出色的 jmacro 包来处理生成 JavaScript 的低级问题。你可以利用它生成使用任何适合你应用的其他 JavaScript 库的代码。这可能是 JavaScript + jQuery,D3.js,甚至使用更高级 JavaScript 框架如 Angular 或 Ember 的代码。我倾向于认为,生成 Angular 的代码会比生成 Ember 的代码更容易,因为 Angular 更简单,并且具有更强的封装性。
Greenfield 是一个为浏览器中的函数式语言设计的字节码虚拟机
这只是我一个不切实际的想法。我觉得它实际上不太可行,因为需要大量的工作,而且很难被采纳。但我至少想提一下这个想法,以求完整性。有人指出 asm.js 几乎已经差不多是这样了。可能确实如此,但如果从一开始就在虚拟机层面设计诸如尾调用优化之类的功能,那就更好了。
答案#2
在我看来,简单的解决方案是从 Haskell 数据声明(或其他 API 方案描述)生成 TypeScript 接口,并在前端部分使用 TypeScript。这为那些不懂 Haskell 但懂 JavaScript 的人提供了参与项目的机会。
例如
data RpcResponse = RpcResponse { number :: Int, string :: Maybe String }
编译到
interface RpcResponse {
number : number,
string?: string
}
并且函数 parseRpcResponseJson 有 Typescript 类型
parseRpcResponseJson(response: string): Option<RpcResponse>;
