惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
小众软件
小众软件
博客园_首页
博客园 - 聂微东
V
V2EX
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
SegmentFault 最新的问题
J
Java Code Geeks
Last Week in AI
Last Week in AI
The Cloudflare Blog
月光博客
月光博客
雷峰网
雷峰网
宝玉的分享
宝玉的分享
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Hugging Face - Blog
Hugging Face - Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
博客园 - Franky
腾讯CDC
Jina AI
Jina AI
博客园 - 叶小钗
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
量子位
爱范儿
爱范儿
美团技术团队
T
Tailwind CSS Blog
博客园 - 【当耐特】
D
Docker
IT之家
IT之家
V
Visual Studio Blog
P
Proofpoint News Feed
L
LangChain Blog
Engineering at Meta
Engineering at Meta
C
Check Point Blog
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog RSS Feed
Recorded Future
Recorded Future

Oskar Wickström

Coding on Paper Catching Typos on My Website with Browser Testing The Bombadil Terminal Experiment There and Back Again: From Quickstrom to Bombadil Computer Says No: Error Reporting for LTL A Year with the Daylight Computer Finding Bugs in a Coding Agent with Lightweight DST Machine: Learning; Human: Unlearning; How I Built “The Monospace Web” A Flexible Minimalist Neovim for 2024 Statically Typed Functional Programming with Python 3.12 Specifying State Machines with Temporal Logic Clearing Weeds and Planting Trees Introducing Quickstrom: High-confidence browser testing The TodoMVC Showdown: Testing with WebCheck Time Travelling and Fixing Bugs with Property-Based Testing Property-Based Testing in a Screencast Editor, Case Study 3: Integration Testing Property-Based Testing in a Screencast Editor, Case Study 2: Video Scene Classification Property-Based Testing in a Screencast Editor, Case Study 1: Timeline Flattening Property-Based Testing in a Screencast Editor: Introduction Why I’m No Longer Taking Donations Writing a Screencast Video Editor in Haskell Declarative GTK+ Programming with Haskell Finite-State Machines, Part 2: Explicit Typed State Transitions Modeling with Haskell Data Types Motor: Finite-State Machines in Haskell Automating the Build of your Technical Presentation Tagless Final Encoding of a Test Language Taking a Step Back from Oden Custom Formatting in HTML and LaTeX Code Listings using Pandoc Paramount Color Scheme for Vim
Hyper: Elegant Weapons for a More Civilized Page
Oskar Wickström · 2017-01-06 · via Oskar Wickström

Since laying Oden aside, I have been getting back into PureScript, and started working on a project called Hyper that I would like to introduce you to.

Other than experimenting with extensible records and row typing, and spamming my followers on Twitter, I have tried to document the design and purpose of the project so that it will be approachable for others. Now, however, I feel that a blog post introducing the project without diving too deep into implementation details would be helpful and interesting.

If you have any feedback, or like me to write more about Hyper here, please let me know in the comments at the bottom of this page.

Background & Motivation

I have been working with NodeJS for web servers, on and off, for the last 3-4 years. As projects grow, and even at the start of a project, programming errors creep in around the middleware setup. Many things can go wrong, and they are often hard to debug. You have no static guarantees that third-party middleware and your handlers are correctly setup — that they cover all cases, execute in the correct order, do not interfere with each other, and handle errors properly.

After an inspiring talk by Edwin Brady on dependent types for state machines, I decided to try to improve this situation for web server middleware using extensible records and row types in PureScript, to see if I could provide similar safety guarantees to those demonstrated in Idris. It turns out that extensible records work really well for this case!

Another thing circling around in my head, to the point of real concern, is the amount of energy that gets directed towards pure functional programming in single-page applications. The ideas are great, and very cool designs emerge, but I am afraid we completely abandon progressive enhancement and “regular” server-side rendered web applications. Thus, I wanted to direct some of the FP love towards good ol’ web server software.

I messed around a bit in Haskell with GADTs and phantom types, but PureScript records really stood out, so I decided to go ahead with PureScript. I think it deserves a web server library like the one Hyper aims to become. With alternative PureScript backends emerging, Hyper could also run on Erlang and C++ servers, not only NodeJS.

Design

The basic building blocks of Hyper are Conn and Middleware. They are heavily inspired by other middleware libraries, like Plug in Elixir and connect in NodeJS. The Conn represents the entirety of an HTTP connection, both the request and response.

type Conn req res components =
  { request :: req
  , response :: res
  , components :: components
  }

A Conn is a record containing some request req, and some response res. What are those values? Well, it depends on the middleware. Usually they are also records, where middleware specify the fields that they require, or provide, in both the request and response records. The structure of request and response records are open, and the compiler guarantees that the composition of middleware is correct.

You might wonder what the purpose of components is. For the sake of this blog post, let us just say that it is a place for user-defined things not directly related to HTTP.

The following type requires the request to have at least a body of type String, and headers of type StrMap String. The request can have more fields, this type does not care. The response and components are not constrained at all by this type.

forall req res c.
Conn { body :: String
     , headers :: StrMap String
     | req
     }
     res
     c

The second building block, middleware, are simply functions transforming connection values. They take a pure connection, and return another connection inside some type m, where m is usually an Applicative or a Monad.

type Middleware m c c' = c -> m c'

As middleware are monadic functions, just as the computations used with Bind, they compose using Kleisli composition.

-- Compose three middleware functions sequentially,
-- from left to right:
authenticateUser >=> parseForm >=> saveTodo

As m is a parameter of Conn, you can customize the middleware chain to use a monad stack for tracking state, providing configuration, gathering metrics, and much more.

Response State Transitions

In Hyper, the state of a response is tracked in its type. This guarantees correctness in response handling, preventing incorrect ordering of headers and body writes, incomplete responses, or other similar mistakes.

The contract of state transitions is encoded in a type class implemented by response writers for specific servers. This makes it possible to write reusable functions on top of the protocol that can run with different server implementations.

To safe a few keystrokes, and your innocent eyes, let us begin by looking at the type alias for middleware transitioning between response states.

type ResponseStateTransition m rw from to =
  forall req res c.
  Middleware
  m
  (Conn req {writer :: rw from | res} c)
  (Conn req {writer :: rw to | res} c)

Now, on to the encoding of state transitions, the ResponseWriter type class.

class ResponseWriter rw m b | rw -> b where
  writeStatus
    :: Status
    -> ResponseStateTransition m rw StatusLineOpen HeadersOpen

  writeHeader
    :: Header
    -> ResponseStateTransition m rw HeadersOpen HeadersOpen

  closeHeaders
    :: ResponseStateTransition m rw HeadersOpen BodyOpen

  send
    :: b
    -> ResponseStateTransition m rw BodyOpen BodyOpen

  end
    :: ResponseStateTransition m rw BodyOpen ResponseEnded

I know, it looks a bit scary with all the types. Stay strong, or have a look at it rendered as a state diagram instead.

State diagram for Hyper responses

We can write a middleware, based on the state transition functions of the type class, that responds friendly to all requests.

writeStatus (Tuple 200 "OK")
>=> writeHeader (Tuple "Content-Type" "text/plain")
>=> closeHeaders
>=> write "Greetings, friend!"
>=> end

Say we forget the line with closeHeaders. What do we get? An informative type error, of course!

Could not match type

  HeadersOpen

with type

  BodyOpen

There are easier-to-use functions written on top of the response API so that you do not have to write out all state transitions explicitly.

writeStatus statusOK
>=> contentType textHTML
>=> closeHeaders
>=> respond (div [] [ h1 [] [ text "Greetings!" ]
                    , p [] [ text "You are most welcome." ]
                    ])

The respond function has the added benefit of decoupling the response type, in this case HTML, from the response type required by the server, which for NodeJS is a Buffer. The transformation between HTML and Buffer is done behind the scenes by a bit of type class machinery.

The bits presented so far constitute the low level API for responding to HTTP requests in Hyper. I have plans for designing a simpler interface, based on Ring response maps in Clojure, where response handlers simply return a data structure describing the response to be written. Such an API can be built on top of the existing low-level API.

Wrapping Up

We have looked at the core design of Hyper, and the motivation behind the project, but merely scratched the surface. The documentation describes in much greater detail the implementation and current components of Hyper. I hope, however, that I have caught your interest. If so, please go ahead and check out some of the provided examples at GitHub.

Also, note that the project is highly experimental, not nearly ready for any production use. But it could be! If you are interested in contributing, do not hesitate to send me a tweet or a PM. I need help writing the library, middleware, servers for different PureScript backends, more examples, documentation, etc. If you want to have a look at the source code, it’s also on GitHub.

Thanks for reading!


Note at Feb 13, 2017: Since this blog post was published, the design of Hyper has changed. It is no longer based on simple monadic functions, but instead indexed monads, to provide safer interaction with response writing side effects. The documentation at hyper.wickstrom.tech should always be up-to-date, so please have a look there as well.