High-level overview of PureScript

Zelenya - Oct 25 '23 - - Dev Community

I want to do a high-level overview of PureScript and its ecosystem. Who uses it and why? What are the trade-offs and shortcomings (especially for newcomers)? Want to sketch out a map of the ecosystem, resources, community, and so on.


📹 Hate reading articles? Check out the complementary video, which covers the same content.

Blitz round

Do people use PureScript in production? Yes.

Is PureScript just a Haskell for the frontend? No. It’s pretty different, and you can also use PureScript on the backend.

Why is PureScript not so popular then? Probably because there is some learning curve to it, and nobody is pouring millions into its marketing.

Is it better than TypeScript or Elm? The languages have different values and design goals — one can’t be purely better than another. You have to see what suits you better.

Intro

Let’s talk about (currently) main pathways to PureScript: after some frontend experience or after some backend.

🤔 What about PureScript as a first language? Probably not. Too many balls to juggle.

  • If you come from the FE, luckily, you can build on top of your knowledge — you can use most of your tools, bundlers, libraries, and even frameworks. You need to learn a new language (probably new syntax), new features, and (unless you’re coming from Elm, ClojureScript, or something along these lines) functional programming.
  • If you come from the backend, you have some catching up to do (everything mentioned above). It’s going to be easier if you have an FP background. And that’s where I came from, and honestly, after years of using PureScript, I still can’t wrap my head around all the bundlers and package managers, but I know what to copy-paste and search for.

Why, Where, and How of using PureScript

PureScript is a general-purpose programming language — you can use it in various domains.

  • I used it on the backend in a typical microservice to shuffle data around.
  • I used it on the frontend in a typical React single-page app/user-interface.

PureScript transpiles to JavaScript, Erlang, (soon) Chez Scheme, and others. And even if you limit yourself to JavaScript, you can put PureScript on top of Node.js or something, boom, new runtime.

I don’t want to talk about random language features. It’s hard to sell a feature without talking about the particular problems to solve. So, I’ll just mention two things I genuinely love about PureScript and miss in other languages.

Row Polymorphism

Whenever I have to manipulate data with other languages, I dream about using PureScript. Especially if you have to integrate with some legacy “REST” service with questionable data, which needs to be validated and transformed, their entities have lots of fields, which are sometimes reused and sometimes the same; some fields are optional here but required there…

When you have a statically typed language, you must write a lot of boilerplate to deal with all the differences and nuances in the data types. Annoying. When you have a dynamically typed one, you have to deal with type of data, optionality, and validations. Annoying.

Row Polymorphism is both — you keep the type-safety and still write pretty flexible/polymorphic/structural/dynamic/whatever-you-wanna-call-it/code. Let the example speak for itself:

parseDate :: forall r. { updatedAt :: String | r } -> { updatedAt :: Maybe Date | r } 
parseDate anything = ... -- implementation doesn't matter
Enter fullscreen mode Exit fullscreen mode

The parseDate function can be used with any entity (data type) with a field updatedAt; it tries to parse the date string as a proper date. And imagine, when you have many functions like this, you can combine them like lego blocks to deal with any data. It’s amazing.

parseDate { message: "Is this october or november?", updatedAt: "2020/10/11" }

... parseCountryCode (parseDate { ..., countryCode: "NULL", updatedAt: "01/01/2023", ... })
Enter fullscreen mode Exit fullscreen mode

🔌 Checkout A joy of working with JSON using PureScript on the topic

Community

My second favorite thing is not a language feature. It’s Community.

Not going to lie; PureScript is a fantastic community. It is welcoming to beginners, hardcore enthusiasts, and everyone in between.

The PureScript Discourse and PureScript Discord are both great places to ask questions (and answer them).

🙏 At this point, I feel the responsibility to ask: If you’re an asshole, please, go somewhere else; there’re a lot of languages that will welcome you

Learning resources, books and docs

The free PureScript By Example book contains several practical projects; it's a great resource for PureScript beginners.

Alternatively, you can start with the Functional Programming Made Easier book, which takes you from zero Functional Programming knowledge to a Full-Stack web app written in PureScript.

You can also visit the PureScript documentation repository on GitHub to find articles, guides, in-depth learning resources for beginners, and more. For example, it links to PureScript Cookbook.

On top of that there are a few incredible personal resources, my personal favorites are Thomas Honeyman Articles, Mark’s alethic.art, and Jordan’s PureScript Reference

Tooling

PureScript comes with excellent tooling and editor support with instant rebuilds.

The recommended build tool for PureScript is Spago.

When it comes to coding, you can use any of your favorite editors via purs ide (an IDE server that comes with compiler) or PureScript language server, which is built on top of it.

💡 For details, see the dedicated documentation section on Editor support.

Pursuit is one of the coolest things. It hosts the API documentation and lets us search it by package, module, function names, and type signatures.

Pursuit

There is also purs-tidy — a formatter for PureScript source code with pretty good defaults; there is no reason not to use it (it comes from someone who worked on a couple of formatters).

💡 For more, see Editor and tool support and Recommended Tooling for PureScript series.

JavaScript-backend specific

If you import libraries from JavaScript, you need a package manager. You can use npm (commonly used), yarn, or whatever.

You also need a bundler to deal with JavaScript imports, smoosh your files, etc. You can get started with esbuild. The most popular alternatives are webpack and parcel, but you can probably make things work with whatever you prefer.

PureScript on the frontend

Speaking of frontend. This is the last big topic we’ll cover because it’s not relevant to everyone.

The most important thing to know is how to use JavaScript code (libraries) and integrate with JavaScript frameworks. tl;dr, it’s simple but boilerplate-y.

First, I want to emphasize that you can mix and match JavaScript, TypeScript, PureScript, and whatever code: you can keep your old code and implement new components using PureScript. Moreover, you can migrate one peace at a time — you don’t have to abandon what you have and do big rewrites.

frontend

That happened in one of my previous companies — JavaScript/TypeScript React code base over a couple of months turned into PureScript code base.

Interoperability with JavaScript

Also called The Foreign Function Interface or FFI for short.

The simplest way to use JavaScript code from PureScript is to give a type to an existing JavaScript value using a foreign import declaration. Let’s take the square function for example:

// JavaScript file
"use strict";

export const square = function (n) {
  return n * n;
};
Enter fullscreen mode Exit fullscreen mode
-- PureScript file
module Test where

foreign import square :: Number -> Number
Enter fullscreen mode Exit fullscreen mode
-- PureScript usage
import Test

square 5.0
Enter fullscreen mode Exit fullscreen mode

There is no magic to it — we bridge the gap between the world by explicitly adding a type signature.

Note that functions in PureScript are curried and don’t tolerate side-effects. Both require learning and a bit of practice — but nothing to worry about. Here is a real-world example (we let the library do the work):

// JavaScript side
export { default as reactPlayerImpl } from 'react-player';
Enter fullscreen mode Exit fullscreen mode
-- PureScript side
foreign import reactPlayerImpl
  :: ReactComponent { url :: String, light :: Maybe Boolean }
Enter fullscreen mode Exit fullscreen mode
-- PureScript usage
player1 :: JSX
player1 = reactPlayer { url: "https://youtu.be/tIUXB0TrlpU", light: Just true }
Enter fullscreen mode Exit fullscreen mode

Note that we are explicit only about the types and things we actually care about — react player accepts a bunch of other props, but we don’t care.

Calling PureScript function from JavaScript is straightforward as well. One thing to keep in mind is that PureScript functions become JavaScript functions of a single argument:

-- PureScript file
module Test where

first :: Int -> Int -> Int
first a _ = a
Enter fullscreen mode Exit fullscreen mode
// JavaScript file
import { gcd } from 'Test';

first(10)(2);
Enter fullscreen mode Exit fullscreen mode

💡 You can read more about interoperability in the book, docs, reference, or anywhere else.

Frameworks

At the moment, the most popular UI Frameworks in PureScript ecosystem are Halogen and React.

💡 Checkout Real World Halogen and Real World PureScript React, which demonstrate the same project using both frameworks.

Halogen is a declarative, component-based UI library for PureScript that emphasizes type safety. You can try the following code here:

module Main where

import Prelude

import Effect (Effect)
import Halogen as H
import Halogen.Aff as HA
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.VDom.Driver (runUI)

main :: Effect Unit
main = HA.runHalogenAff do
  body <- HA.awaitBody
  runUI component unit body

data Action = Increment | Decrement

component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
    }
  where
  initialState _ = 0

  render state =
    HH.div_
      [ HH.button [ HE.onClick \_ -> Decrement ] [ HH.text "-" ]
      , HH.div_ [ HH.text $ show state ]
      , HH.button [ HE.onClick \_ -> Increment ] [ HH.text "+" ]
      ]

  handleAction = case _ of
    Increment -> H.modify_ \state -> state + 1
    Decrement -> H.modify_ \state -> state - 1
Enter fullscreen mode Exit fullscreen mode

And React is React. There is a whole react-basic family of PureScript packages, which include basic components, specific components, starter applications, Hooks API, and so on.


What about other frameworks and libraries? It depends. If you’re lucky and somebody already done the work, you can use a PureScript wrapper-library and don’t worry about JavaScript. If not, then you have to do some prep work (how much depends on how rigid and highly coupled the library is).

💡 Pro-tip: Don’t forget to check with your friends on discord, if you can collaborate with someone.

Where to go from here

Where to go from here?

Last winter, together with a two sets of my ex-colleagues, we shared our experiences of using PureScript in production.

And last but not least, go build some stuff.

References and links


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player