为什么这个函数应用程序在纯脚本中会产生运行时错误?

Why does this function application generate a runtime error in 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 上额外的 () 是从哪里来的,但这就是问题所在。当我手动删除生成的源中的 () 时,它按预期工作。

更新: 添加了代码以在 this branch 上重现此内容。在通常的手续之后,npm run testbrowser 并在浏览器中打开 dist/index.html

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


更长的解释:

多余的空括号来自Effect

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

例如,这个:

a :: Effect Unit

编译为 JavaScript 为:

function a() { return {}; }

同样,这个:

f :: String -> Effect Unit

编译为 JavaScript 为:

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

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

但是,在您的 FFI module 中,您将 parseFromString 定义为:

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

这等同于 parseFromString :: String -> String -> DOMParser -> Document - 即它接受三个参数,一个接一个,并且 return 是一个已解析的文档。

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

所以,为了修复你的 FFI,你只需要插入一个额外的无参数函数,它将模拟 returned 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, and friends。这些是将 JavaScript 风格的函数(即一次获取所有参数)“翻译”为 PureScript 风格的柯里化有效函数(即一个接一个地获取参数并 returning 效果)的包装器。

您可以将 JS 端声明为普通 JS 函数,然后将其作为 EffectFnX 导入 PureScript,并在需要时使用 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

P.S。购买 EffectFn1 的人也喜欢 Fn1 and friends - 同样的东西,但用于纯(非有效)功能。