purenix-org / purenix Goto Github PK
View Code? Open in Web Editor NEWNix backend for PureScript. Transpile PureScript code to Nix.
Home Page: https://hackage.haskell.org/package/purenix
License: BSD 3-Clause "New" or "Revised" License
Nix backend for PureScript. Transpile PureScript code to Nix.
Home Page: https://hackage.haskell.org/package/purenix
License: BSD 3-Clause "New" or "Revised" License
purs
and spago
both implement a bundle
command.
This command bundles all the output .js
files for each PureScript module into a single .js
. This single .js
file is easy to deploy or copy somewhere else.
It may make sense for us to also implement a bundle
command in purenix
. This command would output a single .nix
for the entire project. This single .nix
file would be easy to copy to another project (or possibly even use in a larger repo like Nixpkgs).
We need to investigate exactly how the bundle
command works in purs
and spago
. We need to come up with a good way of including all our output Nix code into a single file.
This was mentioned in #22 (comment).
Currently, purenix
will only create new files. Maybe it's a good idea to also delete the json files once we're done with them, to leave the directory in as clean a state as possible? AFAICT that's what the JS backend does too.
Spago assumes a backed has a --run Module.Name.function
flag that will execute the given function. This is also how tests are implemented. I think the following snippet is a reasonable implementation for --run
in purenix.
mkdir tmp-nix
export NIX_STORE_PATH=$(pwd)/tmp-nix/store
export NIX_DATA_DIR=$(pwd)/tmp-nix/share
export NIX_LOG_DIR=$(pwd)/tmp-nix/log/nix
export NIX_STATE_DIR=$(pwd)/tmp-nix/log/nix
nix-instantiate --eval --readonly-mode -E "let module = import ./output/Test.Main; in module.main null"
This would of course be implemented within Haskell and the module to import as well as the function to evaluate is defined by the flag.
The snippet runs evaluation against a temporary nix store and in readonly-mode so it should be fine run inside a nix build. I've only tested that it does indeed run in the simplest of scenarios so far.
Current in purenix
, we copy over an FFI file like Main.nix
to output/Main/foreign.nix
, but only if Main.nix
exists.
We don't have any check for the situation where Main.purs
uses FFI functions, but no Main.nix
FFI file exists. We should probably add this check, since it can be easy to forget to write an FFI file.
purescript-0.15 was released a few days ago. It would be nice if purenix
was compatible with it. I'm not sure how much work would need to go into this:
https://github.com/purescript/purescript/releases/tag/v0.15.2
We might want to also change our module Lib
to be named something more descriptive.
I guess we could also change modules like Nix.Convert
and Nix.Expr
to something like PureNix.Convert
and PureNix.Expr
, but I don't feel as strongly about this.
Originally posted by @cdepillabout in #10 (comment)
Hey, I was trying to follow the Quick Start but couldn't get the example to build. After following the steps, including switching out the package set hash to the latest on master
of the temp-package-set
repo. Running spago build
after that complained that that package set required an older version of purs
(0.14.something), but I chose the option to override it in the packages.dhall
file..
The result of running spago build
was that modules compiled to CoreFn
but purenix
failed:
[info] Build succeeded.
purenix: impossible
CallStack (from HasCallStack):
error, called at src/PureNix/Identifiers.hs:63:30 in purenix-1.1-AKRNnyJSalY6yydb60EpDF:PureNix.Identifiers
[error] Backend "purenix" exited with error:1
Are you able to reproduce this? FWIW I also tried to build a custom nix shell using EasyPurescriptNix with the appropriate version of purs that spago wanted for that package set, and I still got errors, albeit a different one. Something about not finding sourcePos
.
This is my first rodeo trying to use a different PureScript backend, and I'm not a nix power user so maybe I'm just missing something 🤔
It looks like some characters in quotes aren't escaped correctly.
I haven't extensively tested this, but here is one example I've found:
module Main where
example :: String
example = "\""
This compiles to:
let
module = { };
example = """;
in
{inherit example;}
But example
should probably be "\""
.
We need a README.
It would be nice to have a library like purescript-nix-builtins
that provides FFI for all the different Nix builtins (like builtins.deepSeq
, builtins.getEnv
, builtins.mapAttrs
, etc). This library should also provide FFI data types for types from Nix that are not available from PureScript by default. For instance, a Path
type to represent Nix paths.
A couple other points:
I have a small start of what this library would look like in cabal2nixWithoutIFD
: https://github.com/cdepillabout/cabal2nixWithoutIFD/blob/484515bdec2ccf9dfc02b9a442b801bc2d17b9cc/purescript-parser-combinator/src/NixBuiltins.purs
I think @thought2 may also have some parts of a Nix builtins library in https://github.com/thought2/purescript-miraculix or one of the dependencies.
It might make sense to split this into two packages, one that provides the FFI data types (called something like purescript-nix-types
), and one that provides the Nix builtins (called purescript-nix-builtins
as above). It should be possible to use purescript-nix-types
without relying on purescript-nix-buitlins
. This could be used by people who want to write all their own FFI. The main purescript-nix-builtins
module should probably re-export everything from purescript-nix-types
, so most end users never have to directly interact with purescript-nix-types
.
It might make sense to have a Nix.Builtins
module, and a Nix.Builtins.Unsafe
module. Nix.Builtins
would provide mostly type-safe functions, while Nix.Builtins.Unsafe
would provide mostly unsafe, fully-polymorphic functions.
For instance, builtins.toString
is able to take any argument type except a function or a record. It would be tough to give this a completely safe type in PureScript, but the Nix.Builtins.Unsafe
module could give this a type like toString :: forall a. a -> String
. The person using this function would have to take responsibility to never pass it a function or a record.
On the other hand, a function like buitins.length
can be safely typed in PureScript: length :: forall a. List a -> Int
. This type can always be used safely by end users.
One function / data type that will be necessary (but is not completely straight-forward) is to represent a Nix function that pattern matches on a record. So something like the Nix function { hello ? true, bar }: 123
. I took a stab at creating this here (FunctionWithArgs
and mkFunctionWithArgs
):
The FFI part:
We decided to move forward and make purenix
a lazy language.
The big downside of this is that we will have to be careful when copying (JavaScript) PureScript code, since it is strict. Our Prelude
and other standard libraries will have to be built from the ground up around laziness.
One concern is whether there is some sort of compilation step in purs
before the CoreFn output phase that makes use of PureScript being a strict language. This would cause problems for us. At some point we should probably ask the PureScript developers if they know of anywhere this could bite us.
This issue is to track which core libraries have been ported to PureNix.
These are libraries that have already been ported.
These are libraries that still need to be ported.
lists
and transformers
packages being ported. See #40 (comment) for a little more info.)builtins.seq
function.)These are libraries that either can't be ported to PureNix, or don't make sense to port to PureNix.
Nix does have an assert
statement, but I'm not sure it would be able to be used with a similar API to purescript-assert
. Creating a purescript-nix-assert
library would likely be better.
Nix doesn't have any way of writing arbitrary text to the console. There is the builtins.trace
Nix function, but that could be wrapped up in it's own library.
I'm not sure what this is or how it would be related to PureNix.
Nix doesn't have any way of doing arbitrary effects. In theory, we could have an Effect
type for compatibility, but in practice we wouldn't be able to do anything interesting with it.
edit: There was a little discussion in purenix-org/temp-package-set#1 as to whether or not purescript-effect
would make sense for PureNix. There are a couple good points in favor of having purescript-effect
.
Nix doesn't really have exceptions the same way JavaScript does. Nix does have the builtins.abort
, assert
, and builtins.tryEval
functions, which may be able to be wrapped up in a nice library.
See the problem with random
.
I don't think Nix has any way of getting the current time (or at least to any precision that would be necessary for benchmarking).
Nix doesn't have any built-in way of running computations in parallel. It is possible we could support this same API in PureNix for compatibility, but it is likely we would only be able to run the computations sequentially.
See the problem with random
.
As far as I know, Nix doesn't have any sort of random number generator. There is no way to generate random numbers.
edit: Thinking about this a little more, we could have purescript-random
be a pseudorandom number generator (PRNG). All the random functions could just take a seed that has to be passed in. The seed would be the initial seed for the PRNG. This could be useful for people that are willing to pass in a random seed when calling nix-build
. This seems like a reasonable way to use libraries like purescript-random
, purescript-gen
, and purescript-quickcheck
.
Nix doesn't have any way of mutating values, so refs are not possible.
Nix is lazy, so this sort of trampolining isn't necessary.
Hello,
Thanks for this great project! I'm looking to better understand what is possible or not to do with Nix PureScrip backend, and mainly how we might replicate the callPackage
pattern? I looked a bit over the internet, without success, about how to write a Nix package, a NixOS option or configuration using PureNix, before trying to hack on that myself. Are you aware of existing examples or discussions about this? If not, are there any known challenges or obstacles in doing so?
Thanks!
On a side note: Is there a way to structure PureNix's output, output/Main/default.nix
, so that it aligns with the flakes' syntax?
At some point, we'll have to do a release. Tentative checklist:
1.0
(edit: we decided to go for 1.0
. The versioning scheme is described in the changelog.)Right now, we don't check whether PureScript strings have Nix interpolation characters.
For example, this PureScript code:
myTest :: String -> String
myTest hello = "${hello}"
currently becomes:
let
myIdent = hello: "${hello}";
...
We should decide what to do about this.
Our two choices are:
${
sequence when writing literal strings.newtype StringWithInterp = StringWithInterp String
newtype wrapper that purenix
is aware of and doesn't do escaping on.It would be nice to have a test-suite for purenix
.
The most important tests would be for the Nix.Convert
and Nix.Print
modules.
We'd also especially like to test that all our identifiers, strings, and attrset keys get escaped correctly.
Places to pull ideas from:
As of this writing, neither purerl nor purescript-native have a test suite.
In order for PureNix to be widely usable, we need a real package set for people to depend on.
I have a temporary package set that I've been using while doing development on the core libraries: https://github.com/purenix-org/temp-package-set. But this pins all packages to the master
branch instead of a release tag.
We need a real package set where everything is correctly pinned to a release tag, like the upstream normal PureScript Package Sets. It would be nice to also have all of their CI automation.
Also, before deciding to copy the PureScript Package Set approach, we should take a look at the PureScript Registry and see if it would be possible to use that, since that is what the PureScript community will use going forward (not the PureScript Package Sets). We need to ask the PureScript Registry maintainers if the registry is directly usable by alternative backends.
If anyone is interested in putting this together, please leave a comment and I can create any repos necessary for you to get started setting this up.
Just loading the devShell and it needs to build a lot of stuff.
I see there is already a cachix github action but commented out. Maybe consider re-adding it?
On main the commit 72cc3ff introduces a bug that causes escape sequences like \x001b[32m
to be printed as ESC[32m
when logged/traced to the commandline. It should actually turn the following text's color to be green instead.
I imagine we might want to get rid of this line, since purs
doesn't output something like this. Also, for big repos it will sometimes output so many lines that the important Warning
lines will be scrolled off the screen.
Originally posted by @cdepillabout in #16 (comment)
Right now, when you run purenix
, it looks for all the output/*/corefn.json
files, and transpiles each of them to output/*/default.nix
.
It may be possible to instead check if an output/*/default.nix
file is newer than output/*/corefn.json
, and not regenerate the output/*/default.nix
in that case.
We should confirm that purs
has similar functionality, and doesn't touch the corefn.json
file when it is already newer than the input src/*.purs
input file.
There was a little discussion about this in #22 (comment).
I'm new to nix, so I don't know if this is an error or intended, but when I try to run nix flake show
,
I get the error 'allow-import-from-derivation' is disabled
.
$ nix flake show --version
nix (Nix) 2.4
$ nix flake show --show-trace
git+file:///.../github/purenix?ref=main&rev=4dd6ec3913825a66cc2c8a82219428d812c0a203
├───defaultPackage
error: cannot build '/nix/store/vnvd0kq9vphs2b191yh0wk1z7ffffjlq-cabal2nix-purenix.drv' during evaluation because the option 'allow-import-from-derivation' is disabled
… while importing '/nix/store/ybyh4wwhkwg6fpp2acqd87rkrhj4499r-cabal2nix-purenix'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:87:47:
86| # info that callPackage uses to determine the arguments).
87| drv = if lib.isFunction fn then fn else import fn;
| ^
88| auto = builtins.intersectAttrs (lib.functionArgs drv) scope;
… while evaluating 'drvScope'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:91:18:
90| # this wraps the `drv` function to add a `overrideScope` function to the result.
91| drvScope = allArgs: drv allArgs // {
| ^
92| overrideScope = f:
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/lib/customisation.nix:69:16:
68| let
69| result = f origArgs;
| ^
70|
… while evaluating 'makeOverridable'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/lib/customisation.nix:67:24:
66| */
67| makeOverridable = f: origArgs:
| ^
68| let
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:101:8:
100| };
101| in lib.makeOverridable drvScope (auto // manualArgs);
| ^
102|
… while evaluating 'callPackageWithScope'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:78:37:
77| # here `bar` is a manual argument.
78| callPackageWithScope = scope: fn: manualArgs:
| ^
79| let
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:118:28:
117| defaultScope = mkScope self;
118| callPackage = drv: args: callPackageWithScope defaultScope drv args;
| ^
119|
… while evaluating 'callPackage'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:118:22:
117| defaultScope = mkScope self;
118| callPackage = drv: args: callPackageWithScope defaultScope drv args;
| ^
119|
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:174:9:
173| };
174| }) (self.callPackage src args);
| ^
175|
… while evaluating 'overrideCabal'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/lib/compose.nix:38:22:
37| */
38| overrideCabal = f: drv: (drv.override (args: args // {
| ^
39| mkDerivation = drv: (args.mkDerivation drv).override f;
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:162:5:
161| callPackageKeepDeriver = src: args:
162| overrideCabal (orig: {
| ^
163| preConfigure = ''
… while evaluating 'callPackageKeepDeriver'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:161:33:
160| # annoyance.
161| callPackageKeepDeriver = src: args:
| ^
162| overrideCabal (orig: {
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:218:14:
217| inherit src;
218| }) (callPackageKeepDeriver expr args);
| ^
219|
… while evaluating 'overrideCabal'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/lib/compose.nix:38:22:
37| */
38| overrideCabal = f: drv: (drv.override (args: args // {
| ^
39| mkDerivation = drv: (args.mkDerivation drv).override f;
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:216:10:
215| };
216| in overrideCabal (orig: {
| ^
217| inherit src;
… while evaluating 'callCabal2nixWithOptions'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:205:66:
204| # Creates a Haskell package from a source package by calling cabal2nix on the source.
205| callCabal2nixWithOptions = name: src: extraCabal2nixOptions: args:
| ^
206| let
… from call site
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:220:38:
219|
220| callCabal2nix = name: src: args: self.callCabal2nixWithOptions name src "" args;
| ^
221|
… while evaluating 'callCabal2nix'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/make-package-set.nix:220:32:
219|
220| callCabal2nix = name: src: args: self.callCabal2nixWithOptions name src "" args;
| ^
221|
… from call site
at /nix/store/lgkd30fbvyz9ndyqai6c2cckbfp12bfc-source/nix/overlay.nix:33:11:
32| in
33| hfinal.callCabal2nix "purenix" src { };
| ^
34| };
… while evaluating the attribute 'haskellPackages.purenix'
at /nix/store/lgkd30fbvyz9ndyqai6c2cckbfp12bfc-source/nix/overlay.nix:5:9:
4| prev.haskell.packageOverrides hfinal hprev // {
5| purenix =
| ^
6| let
… while evaluating 'overrideCabal'
at /nix/store/r85b90gdvvpfl04rnn2aisr6jggi2pp0-source/pkgs/development/haskell-modules/lib/compose.nix:38:22:
37| */
38| overrideCabal = f: drv: (drv.override (args: args // {
| ^
39| mkDerivation = drv: (args.mkDerivation drv).override f;
… from call site
at /nix/store/lgkd30fbvyz9ndyqai6c2cckbfp12bfc-source/nix/overlay.nix:38:5:
37| purenix =
38| final.haskell.lib.compose.justStaticExecutables final.haskellPackages.purenix;
| ^
39|
… while evaluating the attribute 'purenix'
at /nix/store/lgkd30fbvyz9ndyqai6c2cckbfp12bfc-source/nix/overlay.nix:37:3:
36|
37| purenix =
| ^
38| final.haskell.lib.compose.justStaticExecutables final.haskellPackages.purenix;
… while evaluating the attribute 'defaultPackage'
at /nix/store/lgkd30fbvyz9ndyqai6c2cckbfp12bfc-source/flake.nix:15:11:
14| {
15| defaultPackage = pkgs.purenix;
| ^
16| packages.purenix = pkgs.purenix;
Spago has a test
command that makes it easy to run tests. PureNix probably needs something added to be able to work with spago test
.
This hasn't really been a problem up until now, since none of the PureNix libraries have tests. But now there is a tasty
-like testing framework for PureNix (https://github.com/thought2/purescript-miraculix by @thought2), so it would be really convenient to be able to run tests directly with spago test
.
Someone interested in implementing this may want to do the following:
Investigate the Spago codebase to find out exactly what it does when running spago test
.
I imagine this function is a good place to start: https://github.com/purescript/spago/blob/aa7c0de6d903262f69452663c09fad9c441af8d3/src/Spago/Build.hs#L241-L256
Keep in mind that it is quite possible there are two different code-paths here, one for the normal JS backend and one for alternative backends (like PureNix).
It would be great if you could leave a comment on this issue with exactly what you figured out.
Optionally go on the PureScript Discord and ask in #compiler
or #purerl
how the Erlang backend handles spago test
. They may have some good suggestions.
Decide on how PureNix should handle spago test
, and leave a short comment here with how you see this working.
Send a PR implementing whatever is necessary.
I noticed that the ports of the 'official' PS packages still keep the same names of their origins.
E.g. purescript-nonempty
Would it maybe make sense to name them either purescript-nix-nonempty
or even purenix-nonempty
. I think the purescript-
prefix is a leftover from bower times. It's not mandatory for spago anymore?
As requested by @sternenseemann in 948e539, and also just a good idea in general
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.