PureScript FFI 到 mocha。

12

我正试图将 mocha 绑定写入 PureScript 中,但是 Control.Monad.Eff 使我完全不知所措。

describe(function(){
  //do stuff  
});

Describe是一个不需要参数并返回IO、Eff或意味着(发生副作用但没有返回值)的函数。


到目前为止,我的尝试

foreign import describe 
  "function describe(n){         \
  \ return function(){           \
  \   window.describe(n); \
  \ };                           \  
  \}" :: forall eff a. Eff eff a -> Eff eff

foreign import describe "describe" :: forall eff a. Eff eff a -> Eff eff
foreign import describe "describe" :: Eff -> Eff
foreign import describe "describe" :: forall eff a. (a -> Eff eff) -> Eff eff

这里显然缺少了些什么。请帮忙。


1
这个问题是否与Haskell有关(除了Purescript是用它编写的)?如果不是,最好避免使用“haskell”标签,因为这会有点令人困惑。 - GS - Apologise to Monica
1个回答

14
PureScript的外部函数接口实际上非常简单。例如,假设您有以下JavaScript函数:
function si(p) {
    return function (r) {
        return function (t) {
            return p * r * t / 100;
        };
    };
}

您可以按照以下方式导入它:
foreign import si :: Number -> Number -> Number -> Number

您也可以将函数内联,如下所示:

您也可以将函数内联,如下所示:

foreign import si
    "function si(p) {\
    \    return function (r) {\
    \        return function (t) {\
    \            return p * r * t / 100;\
    \        };\
    \    };\
    \}" :: Number -> Number -> Number -> Number

对于副作用,PureScript不使用IO单子。相反,它使用了Eff单子。

据我所知,Eff单子与IO单子相同,只是额外有一个类型参数:一行效果。

例如,在Haskell中,print函数的类型如下:

print :: Show a => a -> IO ()

在 PureScript 中,print 函数的类型如下:
print :: Show a => a -> Eff (trace :: Trace | r) Unit

那么我们从中可以得出什么结论呢?
1. `IO` 类似于 `Eff e`,其中 `e` 是一个效果行。 2. `Unit` 类似于 `()`。 3. `print` 函数具有 `trace` 效果,其类型为 `Trace`。 4. 此外,`print` 函数可以与其他效果组合。这意味着它是可组合的。行多态。
一个单独的 `Eff` 值被称为一个动作。例如,`print "Hello World!"` 的类型为 `Eff (trace :: Trace | r) Unit`,是一个动作。
作为函数参数的 `Eff` 值被称为处理程序。它可以被看作是一个没有参数的高阶效应函数。
没有副作用的 `Eff` 值被称为纯值。
type Pure a = forall e. Eff e a
runPure :: Pure a -> a

由于效果(即e)的行是多态的(换句话说,是空的,一个黑洞),PureScript会认为该函数没有副作用。然而,这也意味着它可以与其他具有影响力的函数组合使用。 Eff monad是程序员与编译器之间的契约,程序员承诺给定的Eff值只有陈述的效果行,没有更多。
来到你的describe函数:
Describe是一个不带参数并返回IO、Eff或其他意义上的函数(发生了副作用但未返回任何值)。
实际上这是错误的,你的describe函数确实需要一个函数作为参数:
describe(function(){
  //do stuff
});

此外,它所接受的函数没有参数,这意味着它是一个有副作用的函数。因此,它必须是类型为 Eff e a 的函数,其中 ea 分别可以是任何效果行和任何返回值。

因此,您的 describe 函数必须是以下类型:

describe :: Eff e a -> Eff (describe :: Describe | e) {}

在 Haskell 中,它将被写成以下形式:
describe :: IO a -> IO ()

PureScript只是比Haskell更加明确。无论如何,Describe是一个新的效应类型,你需要创建它来区别于其他效应类型如Trace:
foreign import data Describe :: !

您需要按照以下方式导入describe
foreign import describe
    "function describe(f) {\
    \    return function () {\
    \        window.describe(f);\
    \    };\
    \}" :: forall e a. Eff e a -> Eff (describe :: Describe | e) {}

最后,您可以按照以下方式使用它:
main = do
    describe $ print "Hello World!"

整个代码如下:
module Main where

import Control.Monad.Eff
import Debug.Trace

foreign import data Describe :: !

foreign import describe
    "function describe(f) {\
    \    return function () {\
    \        window.describe(f);\
    \    };\
    \}" :: forall e a. Eff e a -> Eff (describe :: Describe | e) {}

main = do
    describe $ print "Hello World!"

它将生成以下JavaScript代码:
var PS = PS || {};

PS.Main = (function () {
    "use strict";

    var Prelude = PS.Prelude;
    var Debug_Trace = PS.Debug_Trace;

    function describe(f) {
        return function () {
            window.describe(f);
        };
    }

    var print = Debug_Trace.print(Prelude.showString({})); 

    var main = describe(print("Hello World!"));

    return {
        main: main, 
        describe: describe
    };
}());

希望能对您有所帮助。

1
那真的非常有帮助。但我仍然不太理解一些东西。我理解 Haskell 版本,但是 purescript 仍然让我感到困惑。为什么需要 forall?记录中的 | e) 是什么意思?为什么记录后面跟着 {} - Fresheyeball
你的回答很棒,我无论如何都接受它。谢谢。 - Fresheyeball
3
在Haskell中,forall是隐式的,这意味着你不需要输入它。因此,在Haskell中,如果你键入 foldl :: (a -> b -> a) -> a -> [b] -> a ,编译器会认为它是 foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a。然而在 PureScript 中,forall 是显式的,这意味着你必须始终输入它,否则编译器会产生错误。虽然有点丑陋,但仍然可以接受。 - Aadit M Shah
4
类型forall e. { a :: A, b :: B | e }的意思是“匹配具有属性a :: Ab :: B以及其它任何属性的对象”。这是必要的,因为一个对象可能有额外的属性。如果我们只写了{ a :: A, b :: B },那么编译器会认为这个对象只有属性a :: Ab :: B,没有别的属性。这意味着如果对象有额外的属性,编译器会报错。在结尾添加额外的| e告诉编译器对象可以拥有额外的属性。同时也使得对象可以合并。 - Aadit M Shah
3
Eff单子的类型签名为Eff :: # ! -> * -> *。这意味着第一个参数是一个__效应__(用!表示)的__行__(用#表示)。第二个参数是任何纯值(即非效应值,用*表示)。结果也是纯值。因此,Eff单子需要两个参数:一组效应类型和一个返回类型。你的describe函数的类型是Eff e a -> Eff (describe :: Describe | e) {}。这意味着参数可以具有任意数量的效应并返回任何值。返回类型会添加一个新的效应,并返回一个空对象值。 - Aadit M Shah
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接