Haskell for the Elm Enthusiast
blog.noredink.com
blog.noredink.com
- Twain (like Sinatra) for server and routing: https://hackage.haskell.org/package/twain
- WAI middleware for things like static servers, rate-limiting, body-parsing, etc.: https://hackage.haskell.org/package/wai-extra and https://hackage.haskell.org/package/wai-middleware-static
- Blaze-html for writing HTML views: https://hackage.haskell.org/package/blaze-html
- Selda for a LINQ-style type-safe SQL interface to PostgreSQL and SQLite: https://selda.link
I’d be happy to answer any other questions about developing web apps with Haskell or libraries I use.
- Did you use anything else than Haskell? If so what made you switch to Haskell?
- How do you handle JS? Just write JS? TS? Haskell compiled to JS?
- Are there any challenges specific to Haskell?
- I try to avoid JS. I do progressive enhancement with vanilla JS, and avoid JS webapps unless absolutely necessary. If I need to write a JS webapp, I use a purely functional approach. I found that when writing React, using frameworks, using TypeScript or PureScript, I lost so much time and effort managing my build step and fighting with APIs and package churn. I use preact with the htm library, vanilla JS, and most importantly no build step and no npm. I don’t use a framework. I use a purely functional approach inspired from Haskell/Elm. I have an update function which returns a new state and effects from the current state and event, and a dispatch function which is attached to onevent props. The dispatch function calls the update function, renders, and runs effects. These few functions are 20 lines of code.
- No challenges specific to Haskell so far. Of course I had to learn the ecosystem when I started, which packages to use, language and tooling gotchas, etc. but that is the case for any language.
Here's an example of a working app using my approach I described above: https://jsbin.com/putonutica/edit?js,output
The two functions, render and dispatch, are essentially the only code I share across the projects. That's the whole "framework". It's 19 lines of code. All it requires is a link to Preact, which is 14K. Oh, and I just keep everything in one big file. Simple, but I've built multiple complex single-page JS apps with this approach.
I'm sure someone will tell me I'm doing it all wrong, and I need to use Next.js with webpack, a giant stack of npm packages, code-splitting and bundling, and whatever craziness front-end developers are doing today.
Another question that came to mind: how good is the ecosystem for Haskell? Eg, if you wanted to deal with, say, generating PDFs, would you be more or less confident that you could get something working over a weekend?
E: Completely forgot to mention, sumi.news looks great!
That's something lenses look purpose-built to do. Did they fail you, too?
{
"page": 1,
"token": "eyJ..."
}
But ideally, you'd abstract out the auth stuff and put it in a central location which injects it into all requests that need it, eg. if you're using axios you'd add an interceptor for it. We couldn't find a way to do this without making the request into a Dictionary<string, obj>. It's entirely possible this could work with lenses, but from what I looked at I couldn't make it happen.As an aside, I chuckled at the 'Did they fail you, too?'; it made me wonder how lenses could've failed you.
You would probably have some Request with a ToJSON instance and Token types such as:
data Request = Request
{ url :: URI
, method :: Method
, headers :: [Header]
, body :: ByteString
...
}
instance ToJSON Request where
toJSON = ...
newtype Token = Token ByteString
Then you might write a function to add the token header: applyAuthToken :: Token -> Request -> Request
applyAuthToken (Token tok) req = req { headers = ("token", tok) : req headers }
Or if you really wanted to use a lens you could do: headersL :: Lens' Request [Header]
headersL = lens headers (\req hdrs -> req { headers = hdrs })
applyAuthToken :: Token -> Request -> Request
applyAuthToken (Token tok) req = over headersL ("token", tok) :) req
Then you call applyAuthToken right before serializing your Request type to a JSON ByteString.edit: sorry I see that you wanted the auth token in the body not the headers. Its the exact same concept but you would make the `body` field in `Request` into some other specific type with a ToJSON instance and a slightly different `applyAuthToken` function.
I do this kind of thing in Scala using Shapeless records sometimes. You can work pretty easily with types like "this case class, plus a field called token of type String", though writing those types down is a pain (they're basically recursive hlists of fields tagged with singleton types for their names).
Apparently GHC.Generic is the closest Haskell equivalent?
[1]: https://hackage.haskell.org/package/generic-data-surgery-0.3...
As a point of comparison, ReasonML and ReScript both have great ways to express JS-like objects with types (OCaml, which both are based on, suffers from a slightly more verbose syntax), and TypeScript of course leads the way in making dynamic-ish objects still work. Ideally I'd find a functional language with row polymorphism that also had a great ecosystem, but that's asking for too much.
Your best bet here would be Rescript, OCaml or Purescript and leverage the JS ecosystem. There's also http://www.ocamljava.org/, which seems to be not really active. I'm not sure if I would call any option great though.
Now to your specific question, Aeson comes with a type called `Value` which represents JSON data directly. You can see the definition of `Value` [here](https://hackage.haskell.org/package/aeson-1.5.6.0/docs/src/D...).
`Value` is not particularly ergonomic but what you can do is parse the serialized JSON into `Value`, inject your additional JSON values into the `Object` HashMap, and then finally parse the `Value` term into whatever custom type you created for your application.
Haskell has a mature library for JSON, Aeson. I often use it to interface with APIs. You write your type, then functions to serialize/deserialize. Records using simple types (Text, Int, Maybe, etc.) can automatically derive the serialize/deserialize functions using generics.
Funny you mention PDF, because I’m doing that right now in Haskell for a resume builder project. There’s a few libraries for working with PDFs. HPDF (https://hackage.haskell.org/package/HPDF) supports all the basic functionality you’d expect to create PDFs.
If you have a set of key value pairs where all the values are the same, you can use a Map or HashMap. These are both easily serialisable.
I'm not sure there's a way of having key-value pairs without a schema and with different types of values. If it has a schema you can use a record of course.
If you just want to add a single value, such as pdfUrl for example, you can include this as a nullable (Maybe Text) value in the record. Receive the data without it and return it with it. If you wanted to, you could also use a type parameter to guarantee that a pdfUrl is always included in the response record.
This is ultimately the choice most people suggested back then, but I have two issues with it:
1. It leads to a ton of boilerplate, first in the step that converts the request record to a map, then adding stuff to the map and then serializing it. Instead, consider what just adding a field to what you know is a record is in F#:
let newRecord = {| oldRecord with token = token |}
This is vey close to the spread syntax of JS, and is super intuitive and elegant to write.2. You lose type safety: say your JSON object has a bunch of things of different types within it, like a record with a few records or arrays inside. If you want to convert that to a map, you effectively have to make it a Dictionary<string, obj>, or whatever the Haskell equivalent for obj would be (obj is the "base class" in F#: everything is an obj.) That means you completely lose type information from that point on.
The closest any functional language gets to what I would consider "ideal" in this scenario is ReScript, which allows you to type row polymorphic open objects and use JS-like syntax to work with them. This is in addition to good ol' records, of course. Reason is pretty good, in this aspect, too.
The closest any functional language gets to this
let newRecord = oldRecord & #token .~ token
as seen in https://hackage.haskell.org/package/row-typesThe syntax isn't great, admittedly, but if you're willing to write a template haskell macro you could probably get something like
let newRecord = [json| oldRecord with token = token |]
working.I am thinking to make a new digital product in Rust but I am a bit worried that when time comes to scale up with more devs there will not be as many as there are with Go which are also less than Node devs.
That said, hiring Haskell engineers has its pros and cons. Its true there is a smaller pool of developers then other languages, but you end up with a smaller pool of very excited and committed developers eager to use Haskell in production.
There are plenty of Haskell developers, though not as many as more popular languages. Hiring for Haskell is more competitive, but easier to attract developer attention.
The only thing that I usually can’t find a library for is API clients for various services. Most companies don’t make a haskell client. In these cases I have to make HTTP calls to their API myself.
To be honest, this reads like satire.
Throwing a webpage on a screen shouldn't require four esoteric libraries.
This is a web app that renders different pages depending on database results and user preferences, automates rate limiting and has an ORM for type-safe SQL queries. I don't think four libraries is all that many, certainly not compared to say Ruby or NodeJS.
Requires a static html-file
If zero downtime is required, I write the code to migrate the data to the new schema. Then update the app to write to the new schema while reading from old and new schemas. Then in a subsequent deploy after data is migrated, I write code that removes support for reading from the old schema.
(edit) sumi.news works well on a potato phone, well done!
I am wary of large frameworks, so I have not tried IHP. It looks nice though.
We just started on a project and Servant felt like overkill, but this is the first I’ve heard of Twain.
I'm not sure if the poster is from NoRedInk, but my only feedback is that I'd love to see some sample code that demonstrates the power of the handle pattern in use?
I followed the link that discussed the pattern which then referenced this https://github.com/jaspervdj/fugacious. It would be better tho if there was a concise sample.
I have been Haskell-curious for a while. C++ is my language of choice, but it's just because it's the devil I know (e.g. so many of the bizarre design decisions like how r-value reference came to be, how =auto const&= can bind to an r-value and an l-value and you can't really tell, i.e. the "r-value lifetime disaster" [0], allowing operator& to be overloaded, a lack of ABI versioning leading to the standard library being unable to evolve [1]).
I know Rust is similar to the C++ use case, but the mental model jump is just not appealing enough at the moment.
I have watched some stuff about Haskell where people opine about how certain standard library stuff causes strange behavior (e.g. lazy + head of empty list = exception when the list is eventually evaluated elsewhere, weird performance things I don't understand, etc).
Is the list of "secret language and library disasters and workarounds" long in Haskell? That sort of knowledge that is actually contrary to the language tutorial path (e.g. "no one uses the standard library, we all link this other one to avoid issues X and Y") makes language learning a bit frustrating.
[0] https://quuxplusone.github.io/blog/2020/03/04/rvalue-lifetim... [1] https://www.youtube.com/watch?v=-XjUiLgJE2Y
I got even more interested after I read your blog post on Auto Refresh[1], which sounds like it was heavily inspired by Phoenix's LiveView[2] (even though you don't mention it anywhere). Can you do a comparison between the two, in terms of how ergonomic and performant they are, etc.? Any claims to real-time interactivity?
Another, unrelated question: I'd like to use something like HSX but I hate the verbosity of HTML. I'd much rather write Haskell or something like Pug[3] (formerly Jade). How much friction would there be in switching to one of these instead?
[1]: https://ihp.digitallyinduced.com/ShowPost?postId=a2d56dc7-d2...
[2]: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html
[3]: https://pugjs.org/