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.28k stars 86 forks source link

Reduce boilerplate #134

Closed cdsmith closed 11 years ago

cdsmith commented 11 years ago

Currently there are a lot of sources of boilerplate in Fay modules. For example:

  1. Need to declare NoImplicitPrelude.
  2. Need to import Languge.Fay.Prelude explicitly.
  3. Need an explicit module statement because the default module name Main fails.

It would be nice to remove all that boilerplate.

Context: I'm hoping to use Fay for an education project with new programmers, and every line of "just copy this, and don't worry about what it means yet" makes Fay less suitable for that purpose. I could copy the user's code into an existing boilerplate module, but this is both unsafe, and limits flexibility (for example, if user code is pasted after a boilerplate module decl, then it's too late for advanced users to add their own LANGUAGE pragmas)

chrisdone commented 11 years ago

That boilerplate is all for vanilla GHC, e.g. in GHCi and such. In the IDE I add the NoImplicitPrelude by default, so this kind of manipulation is not a big deal. I think we can totally add that manipulation to the type checking part so that invocations to fay that use the GHC type checking get the points above that you'd want. It makes sense for stand-alone use, such as in your case.

bergmark commented 11 years ago

For the module declarations, http://stackoverflow.com/questions/12703151/make-ghc-accept-main-module-with-a-main-function-that-isnt-io

cdsmith commented 11 years ago

So I actually wonder if it might not be a good idea to just rename Language.Fay.Prelude to Prelude. This would resolve both of the prelude-related issues, without doing any source code manipulation at all. It may get in the way, though, if some future build work includes using cabal... but those aren't really new problems; using cabal would already require figuring out what to do with packages (e.g., basically all of them) that explicitly depend on base. Cabal currently has no good answer for wanting to change out the implementation of dependencies behind a library's back.

In any case, passing '-XNoImplicitPrelude' to ghc from typecheck in the interim is an easy and obvious step, right?

chrisdone commented 11 years ago

In any case, passing '-XNoImplicitPrelude' to ghc from typecheck in the interim is an easy and obvious step, right?

Sure, passing that and also maybe inserting an import statement so that the user doesn't have to type it.

So I actually wonder if it might not be a good idea to just rename Language.Fay.Prelude to Prelude. This would resolve both of the prelude-related issues, without doing any source code manipulation at all. It may get in the way, though, if some future build work includes using cabal... but those aren't really new problems; using cabal would already require figuring out what to do with packages (e.g., basically all of them) that explicitly depend on base. Cabal currently has no good answer for wanting to change out the implementation of dependencies behind a library's back.

I'm happy to do that, in fact I was considering it to be a natural thing to do. I'm just not sure that GHC can trivially be told “this Prelude is Fay's, not yours”. When using it from GHCi I don't know how this would be done. Of course if you're just using fay for everything, this can be circumvented a bit. I specifically hack in Emacs with GHCi and generate the JS with fay --no-ghc, so I'd still need that workflow to work.

cdsmith commented 11 years ago

You should be able to do, e.g., ghci -hide-package base -package fay. Would that be sufficient? Or do you mean you want one GHCi instance that can be used with both server and client side code? A good example of a package that already works this way is haskell98

chrisdone commented 11 years ago

-hide-package base sounds like it could work… :-)

Or do you mean you want one GHCi instance that can be used with both server and client side code?

I do. But… I suppose I can run separate sessions…

So the benefit of this would be that you can forgoe NoImplicitPrelude and replace base with fay and it will automatically import the Prelude module from fay? Not bad.

cdsmith commented 11 years ago

Yes, that's the idea. Though, on further thought, exporting a Prelude module from fay itself is going to be very unpopular with anyone (like myself, eventually) who wants to use the Fay compiler as a library, since every module in that code will then have to use NoImplicitPrelude and PackageImports to import the correct Prelude. The right way to do it is probably to provide a separate package called something like fay-base with the pieces intended to be imported by Fay code.

chrisdone commented 11 years ago

Hmm. Yeah. Sounds cleaner. We can do that once I add Cabal support.

cdsmith commented 11 years ago

I'm still thinking about this. We have:

  1. Without cabal support:
    • fay-base is just a hack for running ghc and ghci
  2. With cabal support:
    • Depending on fay-base becomes a way of marking whether a package is Fay or Haskell.
    • Advantage: Nice way to distinguish which environment certain code is written for.
    • Disadvantage: Need Cabal flags -- yuck -- to build the same package for both Haskell and Fay?

I suppose that disadvantage could go away, if the approach were something like writing fay-base:Prelude as a module of re-exports from base, and having the fay compiler replace it with whatever Fay needs. So Fay itself would never read fay-base:Prelude; it's only there as an adapter to get GHC to type-check against the right set of symbols? Maybe...

bergmark commented 11 years ago

I may be missing something (regarding the cabal flags), but isn't it enough to have two separate cabal sections for compiling haskell and fay?

bergmark commented 11 years ago

I tried to fix the module issue as well, but I don't understand the error I'm getting. Typechecking with ghc without using the fay executable works as expected, otherwise I get a linker(?) issue.

I put the change on the dummymain branch.

Note that you have to cabal install to test this so that DummyMain is available.

[~/repos/fay/fay (dummymain)] > ./cabal-dev/bin/fay tests/Bool.hs 
"-fno-code -package fay -XNoImplicitPrelude -main-is Language.Fay.DummyMain tests/Bool.hs -i. -i/Users/adam/repos/fay/fay/cabal-dev//share/fay-0.9.2.0/src"
fay: Warning: the following files would be used as linker inputs, but linking is not being done: -main-is Language.Fay.DummyMain

tests/Bool.hs:5:1:
    Couldn't match expected type `GHC.Types.IO t0'
                with actual type `Fay ()'
    In the expression: main
    When checking the type of the function `main'

[~/repos/fay/fay (dummymain)] > ghc -fno-code -package fay -XNoImplicitPrelude -main-is Language.Fay.DummyMain tests/Bool.hs -i. -i/Users/adam/repos/fay/fay/cabal-dev//share/fay-0.9.2.0/src
[1 of 1] Compiling Main             ( tests/Bool.hs, nothing )
jazmit commented 11 years ago

I think that one of Fay's killer apps is that you can write code that can be easily moved between client and server, or run on both. This is what attracted us to Fay, and so I'd like to weigh in on this from a user's standpoint:

IMHO, the ideal goal is to keep the source code identical for server and client where possible, but have different implementations possible. This could apply:

This would compile as normal with GHC, but would have a Fay-specific implementation. Perhaps there would be a module Language.Fay.Data.List, and fay would know to rewrite the imports to have a Language.Fay in front. Once tuple unpacking is in, the implementation could more or less be copied and pasted from Haskell. A partial implementation would also be acceptable. Over time, it could be filled out and optimized by using native JS functions. If a Fay implementation didn't exist, I could supply my own as Fay.Data.List.

MyModule could be pure haskell which also compiles as fay, or it could have two alternate implementations (eg; a 3D graphics library for OpenGL and WebGL). I could supply the Fay implementation as Fay.MyModule, and the fay compiler would automatically rewrite the import if Fay.MyModule existed.

I don't understand the internals of the Fay or GHC compiler - but it seems to me that all this could be accomplished entirely by having the fay compiler rewrite the import statements early in the compilation process...? This would also mean that existing code with explicit Language.Fay.Prelude imports would not be broken.

cdsmith commented 11 years ago

James, the way this would work using fay-base is as follows.

There would be a package fay-base that exports modules called Prelude, Data.List, etc. These packages are also exported by base. In the default GHC package database, base is (of course) exposed by default, and fay-base is hidden. When building for Fay, you'd specify Cabal or GHC options to arrange things the other way around.

My hope would be that Fay would gain support for -XCPP, so that fay-base would just re-export types and classes from base when built with GHC, much like haskell98 does today, and code built against fay-base and code built against base would get along just fine when used in GHC and GHCi. When building with fay, fay-base would implement everything in pure code and Fay's FFI.

As someone building code to work on both Fay and native Haskell, what this means is that you need to either separate your Fay code and your explicitly GHC-targeted Haskell code (built against base) into separate packages, or else use -XPackageImports (and extend Fay to understand that flag). Code built against fay-base would be compatible with BOTH environments, but would also be more limited in the functionality available to it. Code compiled against base is, of course, GHC-only. If you wanted modules to have different implementations depending on whether they are built with Fay or GHC, then I think your options are to use -XCPP (again, if it were implemented), or to use Cabal flags with different source directories.

Compatibility with existing code is easy. It's just a matter of adding a deprecated module to fay-base that's just module Language.Fay.Prelude (module Prelude) where {}

The positive here is that it works with Haskell name resolution and avoids ugly module name rewriting magic. The disadvantage is needing -XPackageImports or separate packages to combine Fay and Haskell code.

chrisdone commented 11 years ago

So I encountered a bug (referenced to this issue) due to this exact problem, I have to conditionally compile the Maybe type when compiling with Fay but GHC won't accept another Maybe definition. It seems the fay-base solution solves this mess.

Let's have fay-base export Prelude and FFI. This could also export Data.List, etc. CPP stuff can come after. Nice work coming up with this, @cdsmith!