Skip to content

Instantly share code, notes, and snippets.

@tfausak
Last active April 26, 2016 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tfausak/96411fd7400aa06478675c7e6c75ebd4 to your computer and use it in GitHub Desktop.
Save tfausak/96411fd7400aa06478675c7e6c75ebd4 to your computer and use it in GitHub Desktop.
These are supposed to be rendered with remark.js. This will have to do in the meantime.

class: center, middle

Neon

Experimental PureScript
standard library

by Taylor Fausak
on 25 April 2016

???

Presented at the Dallas Functional Programmers meetup on 25 April 2016. http://www.meetup.com/Dallas-Functional-Programmers/events/229367392/

Abstract:

I recently spent some time building a standard library for PureScript, which is a relatively new, strongly typed, pure functional language that compiles to readable JavaScript. PureScript is heavily inspired by Haskell, so I will compare it to that, as well as other languages such as ClojureScript and its host language JavaScript.

My standard library is called Neon and it aims to address some of the problems I have seen working with Haskell’s standard library (called the Prelude) over the past couple years. I’ll talk about some of the design decisions, such as lawless type classes, as well as how it stacks up to other projects like SubHask.

I cribbed some of this from Snoyman's "Why I prefer typeclass-based libraries". http://www.yesodweb.com/blog/2016/03/why-i-prefer-typeclass-based-libraries


class: center, middle

PureScript

???

Before getting into Neon, everyone should at least be familiar with the language it's written in.


PureScript

http://www.purescript.org:

PureScript is a small strongly typed programming language that compiles to JavaScript.

https://hackage.haskell.org/package/purescript:

A small strongly, statically typed programming language with expressive types, inspired by Haskell and compiling to Javascript.

???

These are the official definitions.


PureScript

Colloquially, you can think of PureScript as Haskell for JavaScript. There are many other similar projects, like GHCJS and Elm. PureScript is different because it has no runtime and adds extensible records to Haskell.

If you want to know more, watch my "Better know a language: PureScript" talk: http://taylor.fausak.me/2015/10/22/better-know-a-language-purescript/

???

This is how I think of PureScript in my head.


class: center, middle

Neon


Motivating example

https://projecteuler.net/problem=1:

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

???

I like this example because it includes many of the core elements in programming: looping (or filtering), conditionals, and output.

That being said, it's obviously simplistic.

We will explore how to solve this problem in a variety of languages and paradigms.

The goal here is to give you an idea of what Neon looks like before jumping into the deep end.


Motivating example

JavaScript

sum = 0
for (x = 1; x < 1000; ++x) {
  if (x % 3 == 0 || x % 5 == 0) {
    sum += x
  }
}
console.log(sum)

???

This is the traditional imperative approach. With libraries like Lodash, this could be made more functional. But with vanilla JavaScript, this is almost the best we can do.


Motivating example

Clojure

(->> 1000
     range
     (filter (fn [x]
               (or (zero? (mod x 3))
                   (zero? (mod x 5)))))
     (reduce +)
     println)

???

Clojure is functional, so we can solve this problem with the functional approach using filter and reduce.

I won't get into the pros and cons of Clojure's syntax, but I think we can all agree ->> isn't immediately obvious. Plus it can be annoying to choose between that and -> and as-> and some->.


Motivating example

Haskell

main
    = print
    . sum
    . filter (\ x -> x `mod` 3 == 0 || x `mod` 5 == 0)
    $ [1 .. 999]

???

Another functional approach. There is a lot of weird syntax here with the backslash, arrow, backticks, and double dot. Plus there are some weird operators with dot and dollar. Not to mention the whole thing reads backwards.


Motivating example

PureScript

main
  = print
  <<< sum
  <<< filter (\ x -> x `mod` 3 == 0 || x `mod` 5 == 0)
  $ 1 .. 999
main
  = 1 .. 999
  # filter (\ x -> x `mod` 3 == 0 || x `mod` 5 == 0)
  >>> sum
  >>> print

???

Two options here: bottom up or top down. I personally prefer the second one because it's easier to follow.

This has the same problems as the Haskell example. Weird syntax and weird operators. The triple arrows are better than the dot because they suggest which direction things are moving in. But the dollar and hash are useless.


Motivating example

PureScript with Neon

main = 1
  :upTo 999
  :filter (divisibleBy 3 || divisibleBy 5)
  :sum
  :print

???

Obviously my favorite choice. No weird syntax and only two operators.

You may think this is unfair because Neon provides the divisibleBy helper. You could also do \ x -> x % 3 == 0 || x % 5 == 0 if you wanted to.


class: center, middle

Type classes

???

Now that we know what Neon looks like, let's dive into why it looks like that.

Type classes are the primary means of abstraction in PureScript. If you want one function to work on two data types, you need a type class.


Type classes

addInt :: Int -> Int -> Int
addInt x y = {- ... -}

addInt 2 3 -- 5
addNumber :: Number -> Number -> Number
addNumber x y = {- ... -}

addNumber 2.0 3.0 -- 5.0
addInt    2.0 3.0 -- ERROR
addNumber 2   3   -- ERROR

???

It's easy to define functions that work on one data type. (I've left out the definitions here because they would use the FFI.) But how can you make one function that works with both integers and numbers?

This isn't Go, we should be able to do this.

A note on syntax: In PureScript, a literal without a decimal point is an Int. If it has a decimal point, it is a Number.


Type classes

module Int where
  add :: Int -> Int -> Int
  add x y = {- ... -}
module Number where
  add :: Number -> Number -> Number
  add x y = {- ... -}
import qualified Int
import qualified Number

Int.add    2   3   -- 5
Number.add 2.0 3.0 -- 5.0

Int.add    2.0 3.0 -- ERROR
Number.add 2   3   -- ERROR

???

One way around this problem is to define each function in its own module and import them qualified. This technically works, but using modules like this is a huge pain.


Type classes

class HasAdd a where
  add :: a -> a -> a

instance intHasAdd :: HasAdd Int where
  add = addInt

instance numberHasAdd :: HasAdd Number where
  add = addNumber

add 2   3   -- 5
add 2.0 3.0 -- 5.0

???

With a type class we can use the same function on both integers and numbers.


Type classes

infixl 5 add as +

2   + 3   -- 5
2.0 + 3.0 -- 5.0

???

By defining an operator that uses the type class function, we can get operator overloading. Now if you want to define + for your data type you just need to add an instance for the HasAdd type class.


class: center, middle

Laws

???

Now for one of the contentious parts of type classes: Laws. Laws are suggestions about how to implement a type class. Every other language solves this problem with documentation.

The word "law" implies more gravitas than they actually have. Even in the standard library for Haskell there are instances that don't follow the law!


Laws

class Eq a where
  eq :: a -> a -> Bool
infix 4 eq as ==

Laws:

  • Reflexive: x == x is true
  • Symmetric: if x == y then y == x
  • Transitive: if x == y and y == z then x == z

???

Laws tell you how to implement your instances. They also tell you how to use the type class. But I'll bet you could've guessed these laws based on your experience in other languages.


Laws

class Semigroup a where
  append :: a -> a -> a
infixr 5 append as <>

Laws:

  • Associative: (x <> y) <> z is the same as x <> (y <> z)

???

This is one of the simplest type classes. It has one law that says you can group terms up however you like. When you write an instance, you should make sure that you follow the laws. That way other people can use your data type just like any other, with respect to semigroup-ness.

A good way to make sure you actually follow the laws is to use Quick Check. It will turn the law definition into a test case that randomly generates values and sees if they follow the laws.


class: center, middle

Laws

???

I could give an entire talk about laws. Suffice to say, Neon doesn't have laws per se. It does suggest how to implement and use type classes. And it uses Quick Check to test its own instances. But it doesn't (and won't!) mention laws in the documentation.


class: center, middle

Composition > inheritance

???

In Neon, none of the type classes require any of the others. If you want to implement subtraction but not addition, you're free to do that. If you're writing a function and you want both, ask for both.


Composition > inheritance

absoluteValue :: (HasLess a, HasSubtract a, HasZero a) => a -> a
absoluteValue x =
  if x < zero
    then zero - x
    else x

???

This is like duck-typing applied to a static language. As long as your data type implements these type classes, it can use this function.

Also, you should be able to guess how this function was written without actually seeing the definition.


Composition > inheritance

class (HasMap f) <= Functor f

class (Functor f, HasApply f) <= Apply f

class (Apply f, Functor f, HasPure f) <= Applicative f

class (Applicative m, HasChain m) <= Monad m

???

This doesn't prevent you from defining type classes that compose others together. This way you can get back your Monad hierarchy without needing any support from me.


class: center, middle

Naming

???

I think the traditional type class names (given in the last slide, for example) are confusing. They are named for mathematicians, not programmers.


Naming

import Prelude
newtype MyList a = MyList (Array a)
map (_ + 1) (MyList [1, 2, 3])
Error found:
  No type class instance was found for
    Prelude.Functor MyList

???

What the heck is a functor? I was trying to map over my list.


Naming

import Neon
newtype MyList a = MyList (Array a)
map (_ + 1) (MyList [1, 2, 3])
Error found:
  No type class instance was found for
    Neon.Class.HasMap.HasMap MyList

???

Ah, that's better. I didn't implement map for MyList.

(The fully-qualified class name is a bit unfortunate.)


Naming

Edward Kmett

[Functor] gives me access to 70 years worth of documentation

???

https://www.reddit.com/r/haskell/comments/3pc5p9/question_is_there_a_good_reason_why_fmap_map/cw65o1t

I think that's great, but why does it have to be the actual class name? I think it's sufficient to mention in the docs for HasMap that is is a functor. Then if you know what that is you'll be enlightened. If you don't and you're curious, you can go look it up.


Naming

https://en.wikipedia.org/wiki/Functor:

In mathematics, a functor is a type of mapping between categories which is applied in category theory. Functors can be thought of as homomorphisms between categories. In the category of small categories, functors can be thought of more generally as morphisms.

???

Oh... ok.


class: center, middle

Argument order


Argument order

add "ab" "cd"
-- "cdab"
-- ... what?

???

The order of arguments in Neon is "backwards". This is to make function application with the : operator more useful.


Argument order

"cd" :add "ab"
-- "cdab"

???

By putting the "subject" last and creating an operator that applies values to functions, we can write code that looks object oriented. I think this is a huge win because it allows you to work left-to-right. You can start with a simple value and keep working with it until you have what you want.

This also supports easy REPL-driven development. You never have to go back to the front of the line. You can keep adding to the end.

Also this could allow for better IDE integration. Once you type :, the IDE could look at the type of the previous thing and suggest functions that take that type.

This works even without an IDE by doing x :_ to get a typed hole for that function. Then you can take that type and plug it into Pursuit to find a function that you need. (Note that this requires you to have a type signature.)


class: center, middle

Operators

???

Operators are contentious in Haskell and PureScript. Anyone can freely define them. Neon makes a point to not have many.


Operators

infixl 8 call           as :
infixr 7 power          as ^
infixl 6 multiply       as *
infixl 6 divide         as /
infixl 6 remainder      as %
infixl 5 add            as +
infixl 5 subtract       as -
infix  4 equal          as ==
infix  4 notEqual       as !=
infix  4 greater        as >
infix  4 greaterOrEqual as >=
infix  4 less           as <
infix  4 lessOrEqual    as <=
infixr 3 and            as &&
infixr 2 or             as ||

???

This is every operator that Neon provides. You'll find that there's a lot of overlap with JavaScript. That's intentional. The only addition is :. The only obvious things missing are bitwise operators like |. Unfortunately those are part of PureScript's syntax.


class: center, middle

Questions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment