首页 > 解决方案 > 为什么这个函数应用程序会在 purescript 中生成运行时错误?

问题描述

我有以下 PureScript 片段;注意parseXMLFromString部分应用:

parseXMLFromString ∷ String → DOMParser → Effect Document
parseXMLFromString s d =
  parseFromString "application/xml" s d

parseNoteDoc :: DOMParser -> Effect Document
parseNoteDoc = parseXMLFromString TD.noteXml

note <- parseNoteDoc domParser

生成以下代码:

// Generated by purs version 0.12.4
"use strict";
var Effect_Console = require("../Effect.Console/index.js");
var Test_Data = require("../Test.Data/index.js");
var Web_DOM_DOMParser = require("../Web.DOM.DOMParser/index.js");
var parseNoteDoc = Web_DOM_DOMParser.parseXMLFromString(Test_Data.noteXml);
var main = function __do() {
    var v = Web_DOM_DOMParser.makeDOMParser();
    var v1 = parseNoteDoc(v)();
    return Effect_Console.log("TODO: You should add some tests.")();
};
module.exports = {
    parseNoteDoc: parseNoteDoc,
    main: main
};

该行var v1 = parseNoteDoc(v)();给出了错误TypeError: parseNoteDoc(...) is not a function

我不确定额外()的来自哪里,parseNoteDoc但这就是问题所在。当我()在生成的源中手动删除时,它按预期工作。

更新:添加了在此分支上重现此代码的代码。照常办理手续后,在浏览器中npm run testbrowser打开。dist/index.html

标签: purescript

解决方案


TL;DR:您的 FFI 代码不正确,您需要添加一个额外的function().


更长的解释

多余的空括号来自Effect.

这就是 PureScript 中有效计算的建模方式:有效计算不是一个值,而是一个值的“承诺”,您可以评估并获得该值作为结果。一个值的“承诺”可以建模为一个返回值的函数,这正是它在 PureScript 中的建模方式。

例如,这个:

a :: Effect Unit

编译为 JavaScript 为:

function a() { return {}; }

同样,这个:

f :: String -> Effect Unit

编译为 JavaScript 为:

function f(s) { return function() { return {}; } }

所以它接受一个字符串作为参数,然后返回Effect Unit,这本身就是JS中的无参数函数。

但是,在您的FFI 模块中,您定义parseFromString为:

exports.parseFromString = function (documentType) {
  return function (sourceString) {
    return function (domParser) {
      return domParser.parseFromString(sourceString, documentType);
    };
  };
};

这相当于parseFromString :: String -> String -> DOMParser -> Document- 即它需要三个参数,一个接一个,并返回一个已解析的文档。

但是在 PureScript 方面,您将其定义为parseFromString :: String -> String -> DOMParser -> Effect Document- 这意味着它应该一个接一个地接受三个参数,然后返回一个Effect Document- 如上所述,它应该是一个无参数函数。当您尝试评估时,正是这个额外的无参数调用失败了Effect Unit,实际上它根本不是一个Effect,而是一个Document

所以,为了修复你的 FFI,你只需要插入一个额外的无参数函数,它将模拟返回的Effect

exports.parseFromString = function (documentType) {
  return function (sourceString) {
    return function (domParser) {
      return function() {
        return domParser.parseFromString(sourceString, documentType);
      }
    };
  };
};

(有趣的是,它makeDOMParser :: Effect DOMParser在您的 FFI 模块中正确建模为无参数函数)


但是有更好的方法

JS 中的这些嵌套函数金字塔看起来确实很丑,你必须同意。所以毫不奇怪有一个应用程序可以解决这个问题 - EffectFn1, runEffectFn1, 和朋友。这些是简单的包装器,可以将 JavaScript 风格的函数(即一次获取所有参数)“转换”为 PureScript 风格的柯里化有效函数(即一个一个地获取参数并返回效果)。

你可以将你的 JS 端声明为一个普通的 JS 函数,然后将其导入 PureScript as ,并在需要的地方EffectFnX调用它:runEffectFnX

// JavaScript:
exports.parseFromString = function (documentType, sourceString, domParser) {
  return domParser.parseFromString(sourceString, documentType);
};

-- PureScript:
foreign import parseFromString ∷ EffectFn3 String String DOMParser Document

parseHTMLFromString ∷ String → DOMParser → Effect Document
parseHTMLFromString s d =
  runEffectFn3 parseFromString "text/html" s d

PS购买的人EffectFn1也喜欢Fn1和朋友-同样的事情,但对于纯粹(无效)的功能。


推荐阅读