Skip to content

Commit

Permalink
Make ps-0.15.x-v0.33.0 (#619)
Browse files Browse the repository at this point in the history
* Update build history; note spago difference

* Add initial explanation for VTAs

* Fix monad state instantiation

* Fix Effect example

* Link to falsify and other prop test links

* Link to Free Boolean Cube

* fp-ts' migration guide

* Add GADT-related link

* Fix file name

* Update spago/purs to latest
  • Loading branch information
JordanMartinez authored Dec 12, 2023
1 parent c140536 commit 749e370
Show file tree
Hide file tree
Showing 40 changed files with 1,098 additions and 42 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Set up PureScript toolchain
uses: purescript-contrib/setup-purescript@main
with:
purescript: "0.15.7"
purescript: "0.15.13"

- name: Cache PureScript dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
key: ${{ runner.os }}-spago-${{ hashFiles('packages.dhall') }}
path: |
Expand All @@ -38,9 +38,9 @@ jobs:
22-Projects/.spago
- name: Set up Node toolchain
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "lts/*"

- name: Install dependencies
run: |
Expand Down
1 change: 1 addition & 0 deletions 01-Getting-Started/01-Why-Learn-PureScript.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ One of the main issues with JavaScript is a poor type system. Many errors aren't

TypeScript seems to address this type safety issue. Just consider its name! However, a few people who are using PureScript now have said this about TypeScript: "You might as well be writing Javascript." TypeScript does not provide any real guarantees; it only pretends. PureScript does provide such guarantees.

- [`fp-ts`'s Migration guide from PureScript to TypeScript](https://gcanti.github.io/fp-ts/guides/purescript.html). This is helpful for seeing 1) how much more TypeScript code it takes to implement the same feature in PureScript, and 2) how the resulting syntax IMO is of lesser quality and clarity than the corresponding PureScript code is.
- [TypeScript vs PureScript: Not All Compilers Are Created Equal](https://blog.logrocket.com/typescript-vs-purescript-not-all-compilers-are-created-equal-c16dadaa7d3e)
- [JavaScript, TypeScript, and PureScript](https://www.youtube.com/watch?v=JTEfpNtEoSA) or "Why TypeScript only 'pretends' to have types."
- [Various examples comparing PureScript and TypeScript](https://discourse.purescript.org/t/type-system-showdown-purescript-and-typescript/2084)
Expand Down
8 changes: 4 additions & 4 deletions 01-Getting-Started/04-Install-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Unlike the manual install, `nvm` properly handles the npm prefix for you. So, yo
Once you have installed `npm`, we can use it to install everything in one command:

```sh
npm i -g [email protected].7 spago@0.20.9 esbuild@0.15.7 purs-tidy@0.9.2 purs-backend-es@1.3.1 [email protected]
npm i -g [email protected].13 spago@0.21.0 esbuild@0.19.8 purs-tidy@0.10.0 purs-backend-es@1.4.2 [email protected]
```


Expand All @@ -63,9 +63,9 @@ npm i -g purs-backend-es
The following commands should now work:

```sh
purs --version # 0.15.7
spago --version # 0.20.9
esbuild --version # 0.15.7
purs --version # 0.15.13
spago --version # 0.21.0
esbuild --version # 0.19.8
```

### Building This Project
Expand Down
47 changes: 29 additions & 18 deletions 03-Build-Tools/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ This folder accomplishes the following:

## History: How We Got Here

The following explanation does not cover all the tools used in PureScript's ecosystem. However it provides context for later files. In short, `spago` is both the official dependency manager and build tool. `bower` can be thought of as a deprecated dependency manager; the community is in the process of building a registry that will replace the Bower registry since it no longer accepts uploads. `pulp` is a build tool that uses `bower`; its usage will become more common again once the registry is built.
The following explanation does not cover all the tools used in PureScript's ecosystem. However it provides context for later files.

In short, `spago` is both the official dependency manager and build tool. It was originally written in Haskell. It's currently being rewritten in PureScript. The Haskell version is called `spago-legacy` whereas the rewrite is the alpha `spago`. Whenever this repo mentions `spago`, it's always in reference to `spago-legacy`.

There are two other tools that are only around because alpha `spago` hasn't been finished yet. `bower` can be thought of as a deprecated dependency manager; the community used this tool because it provided a registry. `pulp` is a build tool that uses `bower`; its usage has become less frequent because of the migration towards `spago`.

### Phase 1: Initial Tooling

Expand All @@ -24,6 +28,8 @@ Bodil Stokke (with later contributions from Harry Garrood) later wrote a tool ca
- publish libraries and their docs
- easily bump the project's version

This is why most of the "core" libraries (PureScript libraries stored under the `purescript` GitHub organization) still have `bower.json` files as their dependencies.

### Phase 2: The `psc-package` Experiment

`Bower` worked fine, but there were a few user-interface issues that made it difficult to use, especially when a new PureScript release was made that included breaking changes.
Expand All @@ -40,38 +46,29 @@ See the below image to visualize this:

### Phase 3: Improving the `psc-package` Developer Workflow via `Spago`

From the above image, one should infer that using `pulp` and `bower` was overall easier to use and explain. Thus, Justin Woo and Fabrizo Ferrai started a project called `spago`. `spago` evolved out of `spacchetti` and reimplemented parts of `psc-package` into one program with a seamless developer workflow. While `psc-package` can still be used, it's better to use `spago`.
From the above image, one should infer that using `pulp` and `bower` was overall easier to use and explain. Thus, Justin Woo and Fabrizo Ferrai started a project called `spago`. `spago` evolved out of `spacchetti` and reimplemented parts of `psc-package` into one program with a seamless developer workflow. While `psc-package` can still be used, it became better to use `spago`.

The below image summarizes the current state:

![Build Tool Relationships "Build Tool Relationships"](./assets/Build-Tool-Relationships--With-Spago.svg)

### Phase 4: `Spago` becomes mainstream while `psc-package` is less used

Spago dropped support for `psc-package` commands in the `v0.11.0` release. `psc-package` is still usable and is more or less feature-complete. However, no further work on it will be done. Rather, Spago has become the main dependency manager when utilizing package-sets.
Spago dropped support for `psc-package` commands in the `v0.11.0` release. `psc-package` was still usable and was more or less feature-complete. However, no further work was being done on it. Rather, Spago had become the main dependency manager when utilizing package-sets.

The community is now split between `pulp` + `bower` workflows and `spago` workflows. One must still use `pulp` + `bower` if they want to do the following:
At this point, part of the community used `pulp` + `bower` workflows while the rest used `spago` workflows. One must still use `pulp` + `bower` if they want to do the following:
- publish their library's docs to Pursuit
- include their library in a package set, so `spago` users can use it

### Phase 5: The need for a PureScript registry (Bower registry no longer accepts new uploads)

The Bower registry stopped accepting new uploads. The community quickly updated their tooling to workaround how libraries are published and installed. However, it was clear that PureScript now needed to create a registry.

Fabrizio Ferrai led the effort to build this registry with significant input from Harry Garrood. The registry is not yet complete, so the community is in this in-between stage.

Regardless, the following is still true:
- most people are now using `spago`
- the `pulp` + `bower` workflow is still needed to publish a library, but it works differently now.
- See [these instructions for how to use `bower` to publish a library in this in-between context](https://discourse.purescript.org/t/up-to-date-instructions-for-publishing-new-packages/1953)
- See the `Dependency Managers/Bower Explained` file for clarification on how to install packages as dependencies if one is using `bower`
- Thomas has written a [Recommended Tooling for PureScript Applications](https://discourse.purescript.org/t/recommended-tooling-for-purescript-applications-in-2019/948) post.

See [The `bower` registry is no longer accepting package submissions](https://discourse.purescript.org/t/the-bower-registry-is-no-longer-accepting-package-submissions/1103/) for more context.
Fabrizio Ferrai led the effort to build this registry with significant input from Harry Garrood. The registry is not yet complete, so the community is in this in-between stage. See [The `bower` registry is no longer accepting package submissions](https://discourse.purescript.org/t/the-bower-registry-is-no-longer-accepting-package-submissions/1103/) for more context.

### Phase 6: Updating JavaScript output to ES modules and delegating bundling to 3rd-party tools

In PureScript `0.15.0`, we stopped compiling PureScript source code to CommonJS modules and started compiling to ES modules. As a result, we dropped the buggy and broken bundler provided via `purs bundle` and instead directed endusers to use 3rd-party bundlers like `esbuild`, `webpack`, and `parecel`. Such bundlers often produced smaller bundles than `purs bundle`. Moreover, it gave the core team in charge of PureScript one less thing to maintain.
In PureScript `0.15.0`, we stopped compiling PureScript source code to CommonJS modules and started compiling to ES modules. As a result, we dropped the buggy and broken bundler provided via `purs bundle` and instead directed end-users to use 3rd-party bundlers like `esbuild`, `webpack`, and `parcel`. Such bundlers often produced smaller bundles than `purs bundle`. Moreover, it gave the core team in charge of PureScript one less thing to maintain.

See the [0.15.0 Migration Guide](https://github.com/purescript/documentation/blob/master/migration-guides/0.15-Migration-Guide.md) for more details.

Expand All @@ -81,15 +78,29 @@ While the Purescript compiler produces correct JavaScript code, there were a num

Soon after the time that PureScript `0.15.4` was released, a new project called `purs-backend-es` was released. This project works on the `CoreFn` representation and transforms it to JavaScript. However, it also optimizes the code significantly during this tranformation. For a few example, see [the `purs` and `purs-backend-es` comparison table in its README](https://github.com/aristanetworks/purescript-backend-optimizer#overview).

While this tool's main purpose is to produce optimized JavaScript code, it enables others to produce new backends. A backend is a target language to which PureScript can be compiled. Before this tool, every backend had to reinvent a lot of code to make it work for that language. With the underlying library, `purescript-backend-optimizer`, one can more easily produce a new backend.
While this tool's main purpose is to produce optimized JavaScript code, it enables others to produce new backends more easily. A backend is a target language to which PureScript can be compiled. Before this tool, every backend had to reinvent a lot of code to make it work for that language. With the underlying library, `purescript-backend-optimizer`, one can more easily produce a new backend.

### Phase 8: The Registry and the Spago Rewrite

The Registry's speed of development was lackluster for quite some time. Fortunately, Thomas Honeyman made it a personal goal to see the Registry implemented. Since then, the Registry's development picked up and eventually became useable, although it's still not yet finished.

More recently, Fabrizio decided to rewrite Spago in PureScript. The main advantage of doing this was the ability to leverage the Registry codebase within Spago, allowing for a more seamless publishing workflow among other things. Such work is still on-going as of this writing (Sept 2023). But, the version of Spago written in Haskell is now known as "Spago Legacy" and the version written in PureScript is "Spago Next" because one install spago next via `npm i spago@next`.

## Spago: Haskell Legacy codebase or PureScript rewrite codebase?

| Type | NPM Package | Versions | Install via | Alternative |
| - | - | - | - | - |
| Legacy Spago | `spago` | `0.0.1` - `0.21.0` | `npm i spago` | `npm i spago-legacy` (installs `[email protected]` under binary name `spago-legacy`) |
| Rewrite Spago | `spago` | `0.92.0` - `0.93.x` | `npm i spago@next` | - |

## Overview of Tools

| Name | Type/Usage | Comments | URL |
| - | - | - | - |
| purs | PureScript Compiler | Used to be called `psc` | -- |
| spago | Build Tool | Front-end to `purs` and `package-set`-based projects | https://github.com/purescript/spago
| pulp | Build Tool | Front-end to `purs`. Builds & publishes projects | https://github.com/purescript-contrib/pulp |
| spago (rewrite) | Build Tool | Front-end to `purs`; `package-set`-based or dependency-range -based projects | https://github.com/purescript/spago |
| spago (legacy) | Build Tool | Front-end to `purs` and `package-set`-based projects | https://github.com/purescript/spago-legacy
| pulp | Build Tool | Front-end to `purs`. Builds & publishes projects (being deprecated) | https://github.com/purescript-contrib/pulp |
| bower | Dependency Manager (being deprecated) | -- | https://bower.io/ |
| purs-tidy | PureScript Formatter | -- | https://github.com/natefaubion/purescript-tidy
| purs-backend-es | Produces optimized JavaScript from PureScript | Only intended for production-level usage | https://github.com/aristanetworks/purescript-backend-optimizer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
module Syntax.Basic.VisibleTypeApplications.Intro where
{-
Visible Type Applications is a feature that only works completely
as of PureScript 0.15.13. While it was supported earlier than that,
there were a few bugs that hindered its usage. The content in this file
and those that follow in this folder assume one is using PureScript 0.15.13.
Sometimes, using polymorphic code can be annoying
because the compiler cannot infer what type you want to use. -}

polymorphicCode :: forall pleaseInferThisType. pleaseInferThisType -> Int
polymorphicCode _someValue = 1
{-
problematicUsage :: Int
problematicUsage = polymorphicCode []
The above code is commented out because it will produce a compiler error.
The empty array is inferred by the compiler to have the type,
`forall a. Array a`. Because there are no elements within the
array, the compiler has no idea what the element type is. So, it infers it
to the most generic type it can be: `forall a. a`.
In this situation, we would need to tell the compiler what that type is
by doing one of two things:
- indicating what the input type of `polymorphicCode` is
- indicating what the element type of the empty array is
There's two ways to tell the compiler what the type should be
when the compiler cannot figure it out.
The first way is to add a type annotation (usually after wrapping
the expression in parenthesis). This is annoying to do because
of the added parenthesis, two colons, and in some cases the need to
fully specify all the types of the function or value. -}

usage_inputType :: Int
usage_inputType = (polymorphicCode :: Array String -> Int) []

usage_elemType :: Int
usage_elemType = polymorphicCode ([] :: Array String)
{-
The second way is using "visible type applications".
Using `forall someType. someType -> Int` as an example, the
`forall someType.` part is really a function. We could read the above as
"Given an argument that is a type (e.g. `String`) rather than a value
(e.g. "foo"), I will return to you a function that takes a value of that type
and produce a value of type `Int`."
"Visible Type Applications" are called thus because these type arguments
are applied to these kinds of functions, but these applications that were
previously invisible to the user are now made visible. Put differently,
the compiler would automatically apply these type arguments but without the
user's knowledge or input. Now, however, the user can also apply these type arguments.
Visible Type Applications (or VTAs for short) are opt-in syntax.
They only work if one writes their `forall` part a specific way
by adding a `@` character in front of the type variable name
(e.g. `someType` becomes `@someType`).
Rewriting `polymorphicCode` to use this opt-in syntax, we get: -}

polymorphicCode2 :: forall @pleaseInferThisType. pleaseInferThisType -> Int
polymorphicCode2 _someValue = 1

-- And now we can use it by applying the type `String` to that type variable.
-- We apply the type by putting a `@` character in front of the type name.
usage2_example :: Int
usage2_example = polymorphicCode2 @String "bar"

-- In our original case of using a higher-kinded type (e.g. `Array Int`),
-- we need to wrap the type in parenthesis.

usage2_inputType :: Int
usage2_inputType = polymorphicCode2 @(Array String) []

-- Since `[]`'s inferred type is `forall a. Array a` rather than `forall @a. Array a`,
-- we cannot use VTAs to determine the element type.
-- This code is commented out because we'll get a compiler error.
-- usage2_elemType :: Int
-- usage2_elemType = polymorphicCode2 ([] @String))

-- Lastly, if we opt-in to this VTA syntax, we must either use it or not use it doesn't mean we have to use VTAs
-- to make the function work. The below code is valid

usage3 :: Int
usage3 = polymorphicCode2 true
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
module Syntax.Basic.VisibleTypeApplications.OrderMatters where

-- When we write the following function...
basicFunction :: Int -> String -> Boolean -> String
basicFunction _i _s _b = "returned value"

-- ... we know that the order of the arguments matters.
-- The below usage is valid
usage :: String
usage = basicFunction 1 "foo" false

-- whereas this one is not because the first argument must be an `Int`
-- usage2 :: String
-- usage2 = basicFunction "foo" 1 false

-- Similarly, because functions are curried,
-- when we apply just one argument, we get back a function
-- that takes the remaining arguments

basicFunction' :: String -> Boolean -> String
basicFunction' = basicFunction 1

-- These same ideas apply to VTAs. Notice below that VTA support is only added
-- to the second and fourth type variable.
vtaFunction
:: forall first @second third @fourth
. first
-> second
-> third
-> fourth
-> String
vtaFunction _first _second _third _fourth = "returned value"

-- Type-level arguments (e.g. VTAs) are always applied before value-level arguments.
-- Why? Because those arguments appear earlier in the function.
-- If we want to use VTAs to specify which types `second` and `fourth` are,
-- we must apply those type arguments BEFORE applying any value arguments. In other words,
-- the below code is correct:

usage3 :: String
usage3 = vtaFunction @Int @Int "first" 2 "third" 4

-- whereas this code would be incorrect:
-- usage3 :: String
-- usage3 = vtaFunction "first" @Int 2 "third" @Int 4

-- Put differently, we can define a new function by only applying a single type argument.

vtaFunction'
:: forall first third fourth
. first
-> Int
-> third
-> fourth
-> String
vtaFunction' = vtaFunction @Int -- force `second` to be `Int`

-- Or by type-applying multiple arguments
vtaFunction''
:: forall first third
. first
-> Int
-> third
-> Int
-> String
vtaFunction'' = vtaFunction @Int @Int -- force `second` and `fourth` to be `Int`

-- Note: the astute reader will have noticed that the `@` characters don't appear in
-- `vtaFunction'` and `vtaFunction''`. Since this is opt-in syntax, one must
-- opt-in every time a new function is defined, even if that function
-- is derived from applying one argument to a multi-argument curried function.
Loading

0 comments on commit 749e370

Please sign in to comment.