faylang / fay

A proper subset of Haskell that compiles to JavaScript
https://github.com/faylang/fay/wiki
BSD 3-Clause "New" or "Revised" License
1.29k stars 86 forks source link

Doesn't play well with 'require' and 'new' #433

Closed Thimoteus closed 9 years ago

Thimoteus commented 9 years ago

To preface, I'm not sure if this is my own fault, Fay's, node's babel's or the module I'm using.

The problem is I'm using a module that's usually used in two parts, something like

var R = require('snoocore');
var config = { ... };
var r = new R(config);

and when I try emulating that in Fay with

...
require' = ffi "require(%1)"
...
newWithRequire = ffi "new (%2)(%1)"

node throws an error when I run. On the other hand, if I do it all at once with something like newAndRequire = ffi "new (require(%2))(%1)" it works out fine.

Here's the code I'm using:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE EmptyDataDecls #-}
module Test where

import Prelude
import FFI
import Fay.Text (Text, fromString)
import qualified Fay.Text as T

class Requirable a
class Opts a

data Snoocore
data SnoocoreOpts
data SnoocoreWithOpts

instance Requirable Snoocore
instance Opts SnoocoreOpts

require' :: Requirable r => Text -> Fay r
require' = ffi "require(%1)"

newWithConfig :: (Opts o, Requirable r) => o -> r -> Fay SnoocoreWithOpts
newWithConfig = ffi "new (%2)(%1)"

snoocore :: Fay Snoocore
snoocore = require' "snoocore"

makeOpts :: Text -> Int -> Text -> Text -> Text -> Text -> Text -> [Text] -> SnoocoreOpts
makeOpts = ffi "{'userAgent': %1, 'throttle': %2, 'oauth': { 'type': %3, 'key': %4, 'secret': %5, 'username': %6, 'password': %7, 'scope': %8 }}"

allAtOnce :: Opts o => Text -> o -> Fay SnoocoreWithOpts
allAtOnce = ffi "new (require(%1))(%2)"

main = do
  let opts = makeOpts "/u/username myApp@3.0.0" 300 "script" "abcdefg" "1234567" "redditusername" "redditpassword" ["identity", "read", "vote"]
  --reddit <- newWithConfig opts =<< snoocore
  reddit <- allAtOnce "snoocore" opts
  print reddit

This compiles and prints [Function] but if I comment out the "allAtOnce" line and uncomment the one above, I get TypeError: Cannot call a class as a function, which apparently comes from babel.js, which the author of Snoocore used to write the module.

I'm compiling with fay --package fay-text Test.hs, my fay --version is 0.23.1.5, and node --version is v0.10.36.

bergmark commented 9 years ago

Did you see #283?

Thimoteus commented 9 years ago

Changing the type signature to newWithConfig :: (Opts o, Requirable r) => o -> Ptr r -> Fay SnoocoreWithOpts gives the same runtime error. Also I was also already wrapping the second argument in parentheses, which is a workaround for #283.

For now I'm just passing the config object and instantiating all at once.

bergmark commented 9 years ago

I'll try to take a look later today, I'm guessing some type conversion/thunk is getting in the way. I have considered changing the FFI so Ptr/Automatic isn't needed but I'm not sure if it would cause problems elsewhere.

bergmark commented 9 years ago

Sorry for the delay with this, I forgot about it!

The issue is that there's a polymorphic r that is a function in this case, fay sees that and converts it back and forth to a fay-callable function, snoocore seems to expect it to stay intact and if they didn't do that check it's possible it would just work.

I found two ways to fix it, by using explicit types in ffi calls, or by adding Ptr, both tell the fay runtime to not try to convert the result.

{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RebindableSyntax  #-}
module Test where

import           Data.Text (Text, fromString)
import           FFI
import           Prelude
import qualified Data.Text as T

class Requirable a
class Opts a

data Snoocore
data SnoocoreOpts
data SnoocoreWithOpts

instance Requirable Snoocore
instance Opts SnoocoreOpts

require' :: Requirable r => Text -> Fay (Ptr r)
require' = ffi "require(%1)"

newWithConfig :: (Requirable r, Opts o) => o -> Ptr r -> Fay SnoocoreWithOpts
newWithConfig = ffi "new (%2)(%1)"

snoocore :: Fay (Ptr Snoocore)
snoocore = require' "snoocore"

makeOpts :: Text -> Int -> Text -> Text -> Text -> Text -> Text -> [Text] -> SnoocoreOpts
makeOpts = ffi "{'userAgent': %1, 'throttle': %2, 'oauth': { 'type': %3, 'key': %4, 'secret': %5, 'username': %6, 'password': %7, 'scope': %8 }}"

main = do
  let opts = makeOpts "/u/username myApp@3.0.0" 300 "script" "abcdefg" "1234567" "redditusername" "redditpassword" ["identity", "read", "vote"]
  reddit <- newWithConfig opts =<< snoocore
  print reddit
{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RebindableSyntax  #-}
module Test where

import           Data.Text (Text, fromString)
import           FFI
import           Prelude
import qualified Data.Text as T

class Requirable a
class Opts a

data Snoocore
data SnoocoreOpts
data SnoocoreWithOpts

instance Requirable Snoocore
instance Opts SnoocoreOpts

require' :: Text -> Fay Snoocore
require' = ffi "require(%1)"

newWithConfig :: Opts o => o -> Snoocore -> Fay SnoocoreWithOpts
newWithConfig = ffi "new (%2)(%1)"

snoocore :: Fay Snoocore
snoocore = require' "snoocore"

makeOpts :: Text -> Int -> Text -> Text -> Text -> Text -> Text -> [Text] -> SnoocoreOpts
makeOpts = ffi "{'userAgent': %1, 'throttle': %2, 'oauth': { 'type': %3, 'key': %4, 'secret': %5, 'username': %6, 'password': %7, 'scope': %8 }}"

main = do
  let opts = makeOpts "/u/username myApp@3.0.0" 300 "script" "abcdefg" "1234567" "redditusername" "redditpassword" ["identity", "read", "vote"]
  reddit <- newWithConfig opts =<< snoocore
  print reddit
bergmark commented 9 years ago

Hope this helps, feel free to ask more questions, I'll close this.