Why Purescript?

Perhaps you could start a ‘Why Purescript?’ thread? :003: (I’d be interested why you are choosing it over others.)

1 Like

The reason is simple. It has a haskellish syntax and it does share a lot more of haskells feature set than elm does. So I do choose it over elm.

And bucklescript is out of question for me, because I never got warm with OCaml. Perhaps because of +. and +?

But currently I am not deep enough into PureScript to say anything more. I just bought the book and wont be able to dive deeper into it before December.

But of course, as far as I have progressed to give a report I might do so (unless I don’t forget it)

2 Likes

Indeed, Purescript is basically Haskell with cruft removed and focused for the web, unlike GHCJS, which transpiles Haskell directly to Javascript. It uses HKT’s instead of HPT’s, which are more convenient (no +/+. distinction for example), but is not as capable of as many things and compiles significantly slower. It is a well designed language, just not my style. ^.^

That is because OCaml uses HPT instead of HKT, which means it is more powerful, however it does necessitate things like that. However, OCaml has a feature coming in 4.05/4.06 called Implicit Modules, it adds a single feature that uses HPT’s to create static lookups based on type in the current scope. It is not whole program like HKT’s are, but it does not compromise the instant-compile-time. You have to decorate your implicit functions with a new argument (implicit), which will generally only be done by library authors, not you, however it means that you could then write (and include over the Kernel’s version of) a new + that can handle any type.

However, things like + and +. are not a big deal, you already know the types that you are dealing with and the compiler tells you if you screw up anywhere. :wink:

I’d definitely be interested in you making a ‘Why Purescript?’ post though! I’m curious what I’ve missed about it that might make it interesting to me (Haskell’y systems, I.E. HKT’s hold little to no appeal for me over HPT’s; HPT’s do more and do not have the monstrous compile-time hit). :slight_smile:

2 Likes

Does HKT == higher kinded types?

Yep, although the implicit functions/modules I mentioned above are a replacement for typeclasses in Haskell (not a huge compile speed hit, but haskell’s typeclasses work with its HKT’s a lot, which are costly). Basically typeclasses can be implemented a few ways, HKT’s are one such way, such in Haskell where you define a new type over an existing type. The current OCaml method of making typeclasses are via Witnesses.

Both HKT’s and Witnesses are static-time dispatch, I.E. perfectly efficient as much as it can be (technically OCaml’s often removes the function indirection in the native compile so its is technically faster than Haskell’s, but that is an implementation detail, consider them the same). Languages that do not have strong types like Erlang/Elixir can only do dynamic dispatch, so it has to look it up on-site, Elixir packages this style up in Protocols for example, which compiles down to decently fast matches, but is still far slower than the O(1) HKT’s/Witnesses. OOP languages, like Java or so, expose this kind of functionality via interfaces, which unfortunately means that you have to implement it on the main class, you cannot add the functionality to, say, a libraries class without subclassing it, and that is not always possible. Even then the OOP method is O(1) as well but it generally always incurs a virtual function lookup, making it more costly than static dispatch methods like HKT and Witnesses, however sometimes compilers like C++ or Java’s Tracing JIT can ‘sometimes’ optimize those out in ‘some’ places ‘some of the time’.

HKT’s at its base means that a type can be a subset/superset of another type, such as Integer and Float both being subtypes of Number, where + could be implemented via (pseudo-code):

(+) :: Number -> Number
(+) l r = darkArtsInternalCompilerMagicOrTypeclasses

The darkArtsInternalCompilerMagicOrTypeclasses is really dark internal compiler magic in Elm since Elm does not have HKT’s, HPT’s, or typeclasses, so it fakes it via javascript, and thanks to that occasionally undefineds sneak in places that should be impossible and you can get NaN’s in integers and weird stuff like that, it does not work perfectly.

The typeclass side of it could be:

class Plusable a where
  (+) :: a -> a -> a

Which we could implement for a few types like:

instance Plusable Integer where
    left + right = Kernel.add_int left right

instance Plusable Float where
    left + right = Kernel.add_float left right

And so forth, which is similar to how Haskell does it (though different names). But that lets you define a valid + operator on any Plusable capable type, so we could then define the global + like:

(+) :: (Plusable num) => num -> num
(+) l r = l + r

Oh now what is that (Plusable num) part? Oh it is a constraint, saying that the passed in type has to implement this typeclass. ^.^

In OCaml you have Witnesses, I.E. you pass a function or module around that defines the operations that work on the given type, so we have this:

let add adder left right = adder left right

And you could use it like:

let 7 = add (+) 3 4
(* or *)
let 3.14 = add (+.) 2.14 1.0

So you made a generic add function that can add anything together, any user type, anything, but you have to pass the thing that knows how to do it in to it, which in this simple case seems fairly retarded as you could just call it straight. And of course you could do the same thing in Haskell. However, OCaml has first class modules and modules that can operate on types (which might be another module since modules are first-class), this makes OCaml’s module system a Higher Polymorphic Type. So let’s try to replicate the above Haskell’s typeclasses and HKT’s in this. First let’s define such a ‘typeclass’ base for Plusable, but via an HPT:

module type Plusable = sig
  type t
  val (+): t -> t -> t
end

That is a pretty direct translation of Haskell’s class call above, so now let’s implement it for a few types:

module Plusable_int = struct
    type t = int
    let (+) l r = l + r
end

module Plusable_float = struct
    type t = float
    let (+) l r = l +. r
end

(* Throwing in zipping and adding together list elements as a complex example *)
module Plusable_list (M : Plusable) = struct
    type t = M.t list
    let (+) l r = List.map2 M.(+) l r
end

And we could define our new global-work-on-anything plus function like this:

let (+) (type a) (module P : Plusable with type t = a) l r = P.(+) l r

And it can be used as such:

let x = (+) (module Plusable_int) 3 4
let y = (+) (module Plusable_float) 2.14 1.0
let z = (+) (module Plusable_list(Plusable_int)) [1; 2] [2; 1]

Uh, wait, that does not let us use it how we want, which would be:

let x = 3 + 4
let y = 2.14 + 1.0
let z = [1; 2] + [2; 1]

Although it does let us pass around a chunk of witnesses that can handle a lot of functionality (you can build up modules after all, as shown in the list example above). It does allow for entirely static dispatch, since the type is selected by the user. However it is verbose, and ugly, and we want the short form.

Interestingly the witness in OCaml looks a lot like the constraint in Haskell, a whole lot, except you have to pass it in yourself… Well, a soon-coming OCaml version is finally bringing OCaml implicit modules! Basically change the above modules to have the word implicit in front, like this:

implicit module Plusable_int = struct
    type t = int
    let (+) l r = l + r
end

implicit module Plusable_float = struct
    type t = float
    let (+) l r = l +. r
end

(* Throwing in zipping and adding together list elements as a complex example *)
implicit module Plusable_list (M : Plusable) = struct
    type t = M.t list
    let (+) l r = List.map2 M.(+) l r
end

And change the + definition to be:

let (+) (implicit P : Plusable) l r = P.(+) l r

And then the example that we want will work:

let x = 3 + 4
let y = 2.14 + 1.0
let z = [1; 2] + [2; 1]

The way it works is that you let implicit open WhateverModules into your scope, and whichever modules are in the implicit scope can be selected for type resolution. This means that the proper HPT/module can be selected based on the passed in types via a single well known scope, thus no harm to compile-time at all, unlike Haskell’s HKT that has to do a whole-program scan. Basically implicit is an utterly brilliant work-around to static typeclass choosing in HPT languages that make it just as succinct as the HKT style, thus removing the point of HKT’s entirely now, even ignoring the fact that HPT’s can do a lot more than HKT’s. :slight_smile:

For note, it was always possible to do dynamic typeclass dispatch (Elixir style) via OCaml’s class system, but no one uses it except in specific situations, it is one of those surgical knives good for specific situations that no one really ever bothers dealing with, although I can demonstrate it if curious. ^.^

4 Likes

ODL, you are super prolific with these great responses across different forums (e.g. Elm Discuss) I feel I should collect them all in one place : ) or uh, perhaps you could create a blog (I much lazier than you are!)

1 Like

I have a blog, it is fairly empty, it is a lot of work to create a well formatted and nice-looking blog post, where it is very easy to just brain-dump like I do above. ^.^

It should be said that type classes and the functions that use them are decidedly non-magic. See this for a brief explanation that should make perfect sense.

The vtable that’s passed into the function can also be used only for lookup and the function it’ll reference can be inlined, so yes, any performance hit should be non-existant.

Just a tad bit late. But OCaml seems like it’s stuck in a perpetual situation of “I hope it’ll be in the next release… Just like we said the last 2-3 releases”. OCaml is great and incredibly elegant in many ways, but for all the crap Haskell gets it should be said that OCaml is way more cumbersome to work with. At least the cumbersome things in Haskell are deliberate and are there as parts of tradeoffs. OCaml is cumbersome mostly because it’s underdeveloped.

I know typeclasses are not, but I’m referencing Elm a bit there, which does not have typeclasses but has a few magical types (like number in Elm is one of them, you can get an ‘undefined’ into an integer if you want!). I then continue on to clarify how darkArtsInternalCompilerMagicOrTypeclasses could be implemented. :wink:

From what I’ve seen Haskell rarely inlined such calls and left them as function pointers, though that may have been improved in the few years since I checked.

Eh you should look at it lately, since version 4.0.0 its been improving and streamlining by leaps and bounds. 4.04 is coming out shortly and they are starting the process of revamping the standard library to, for example, return options instead of throw on things like Array.get and such, by adding new functions with _opt appended, but there is talk about revamping the stdlib for a potential OCaml 5.0 in the far future to remove the old cruft (with converters).

OCaml’s cumbersome parts are also deliberate, they keep the language and compiler designed in such a way that it is fast to parse and generate fast code with fast optimization passes. My native 60+ file project that I last did in it compiles from a cold git clone in 1.3 seconds. My last Haskell project of 8 files took over 40 seconds to compile, and older projects that were larger could be multi-minute compiles, a few even giving my C++ projects a run for the title of “Longest Compile Times”, and that was with just repeated cabal install ... with whatever old information it has (and longer on raw git clones).

Whatever extra verbosity OCaml may have over Haskell is substantially overwhelmed by the quick turn-around of testing and compiling, and that extra verbosity is mostly in terms of what implicit modules solves, which should be out in 4.05 or 4.06 (if any implementation issues are found by 4.05 release). OCaml is quite well developed, not underdeveloped by any stretch. Its stdlib is kept small on purpose, it is not utterly massive like Haskell’s. It is a bit old but they’ve already started updating it, with plans to expand it to include the current community ‘best patterns’ that Batteries and Core has tested over the years (which is why they’ve started with _opt variants of existing throwing functions as those are the most highly requested, even then Batteries/Core fix all that up already for you, just include them in your projects).

1 Like