# News aggregator

### Polymorphic case for this lens transformer?

### Haskell companies hiring interns?

Since it's internship application season for lots of college students right now (including me), this is a post for any Haskell-using startups/companies to let students know they may *possibly* be hiring interns for Summer 2015. I say possibly since that's a fair bit away and lots of companies don't know what they'll be hiring at that point. But yeah, let us know if you possibly have openings! I know I'm not the only one interested in working with Haskell professionally this summer.

[link] [11 comments]

### Announce: hasql: A minimalistic general high level API for relational databases

### SML# - (9-1)[ML] 補足：再帰的関数の考え方

### SML# - (9-1)[ML] 補足：再帰的関数の考え方

### Conservation laws for free!

In this year's POPL, Bob Atkey made a splash by showing how to get from parametricity to conservation laws, via Noether's theorem:

Invariance is of paramount importance in programming languages and in physics. In programming languages, John Reynolds’ theory of relational parametricity demonstrates that parametric polymorphic programs are invariant under change of data representation, a property that yields “free” theorems about programs just from their types. In physics, Emmy Noether showed that if the action of a physical system is invariant under change of coordinates, then the physical system has a conserved quantity: a quantity that remains constant for all time. Knowledge of conserved quantities can reveal deep properties of physical systems. For example, the conservation of energy, which by Noether’s theorem is a consequence of a system’s invariance under time-shifting.

In this paper, we link Reynolds’ relational parametricity with Noether’s theorem for deriving conserved quantities. We propose an extension of System Fω with new kinds, types and term constants for writing programs that describe classical mechanical systems in terms of their Lagrangians. We show, by constructing a relationally parametric model of our extension of Fω, that relational parametricity is enough to satisfy the hypotheses of Noether’s theorem, and so to derive conserved quantities for free, directly from the polymorphic types of Lagrangians expressed in our system.

### Gabriel Gonzalez: How to desugar Haskell code

Haskell's core language is very small, and most Haskell code desugars to either:

- lambdas / function application,
- algebraic data types / case expressions,
- recursive let bindings,
- type classes and specialization, or:
- Foreign function calls

Once you understand those concepts you have a foundation for understanding everything else within the language. As a result, the language feels very small and consistent.

I'll illustrate how many higher-level features desugar to the same set of lower-level primitives.

ifif is equivalent to a case statement:

if b then e1 else e2-- ... is equivalent to:

case b of

True -> e1

False -> e2

This works because Bools are defined within the language:

data Bool = False | TrueMulti-argument lambdasLambdas of multiple arguments are equivalent to nested lambdas of single arguments:

\x y z -> e-- ... is equivalent to:

\x -> \y -> \z -> eFunctions

Functions are equivalent to lambdas:

f x y z = e-- ... is equivalent to:

f = \x y z -> e

-- ... which in turn desugars to:

f = \x -> \y -> \z -> e

As a result, all functions of multiple arguments are really just nested functions of one argument each. This trick is known as "currying".

Infix functionsYou can write functions of at least two arguments in infix form using backticks:

x `f` y-- ... desugars to:

f x yOperators

Operators are just infix functions of two arguments that don't need backticks. You can write them in prefix form by surrounding them with parentheses:

x + y-- ... desugars to:

(+) x y

The compiler distinguishes operators from functions by reserving a special set of punctuation characters exclusively for operators.

Operator parametersThe parentheses trick for operators works in other contexts, too. You can bind parameters using operator-like names if you surround them with parentheses:

let f (%) x y = x % yin f (*) 1 2

-- ... desugars to:

(\(%) x y -> x % y) (*) 1 2

-- ... reduces to:

1 * 2Operator sections

You can partially apply operators to just one argument using a section:

(1 +)-- desugars to:

\x -> 1 + x

This works the other way, too:

(+ 1)-- desugars to:

\x -> x + 1

This also works with infix functions surrounded by backticks:

(`f` 1)-- desugars to:

\x -> x `f` 1

-- desugars to:

\x -> f x 1Pattern matching

Pattern matching on constructors desugars to case statements:

f (Left l) = eLf (Right r) = eR

-- ... desugars to:

f x = case x of

Left l -> eL

Right r -> eR

Pattern matching on numeric or string literals desugars to equality tests:

f 0 = e0f _ = e1

-- ... desugars to:

f x = if x == 0 then e0 else e1

-- ... desugars to:

f x = case x == 0 of

True -> e0

False -> e1Non-recursive let / where

Non-recursive lets are equivalent to lambdas:

let x = y in z-- ... is equivalent to:

(\x -> z) y

Same thing for where, which is identical in purpose to let:

z where x = y-- ... is equivalent to:

(\x -> z) yActually, that's not quite true, because of let generalization, but it's close to the truth.

Recursive let / where cannot be desugared like this and should be treated as a primitive.

Top-level functionsMultiple top-level functions can be thought of as one big recursive let binding:

f0 x0 = e0f1 x1 = e1

main = e2

-- ... is equivalent to:

main = let f0 x0 = e0

f1 x1 = e1

in e2

In practice, Haskell does not desugar them like this, but it's a useful mental model.

ImportsImporting modules just adds more top-level functions. Importing modules has no side effects (unlike some languages), unless you use Template Haskell.

Type-classesType classes desugar to records of functions under the hood where the compiler implicitly threads the records throughout the code for you.

class Monoid m wheremappend :: m -> m -> m

mempty :: m

instance Monoid Int where

mappend = (+)

mempty = 0

f :: Monoid m => m -> m

f x = mappend x x

-- ... desugars to:

data Monoid m = Monoid

{ mappend :: m -> m -> m

, mempty :: m

}

intMonoid :: Monoid Int

intMonoid = Monoid

{ mappend = (+)

, mempty = 0

}

f :: Monoid m -> m -> m

f (Monoid p z) x = p x x

... and specializing a function to a particular type class just supplies the function with the appropriate record:

g :: Int -> Intg = f

-- ... desugars to:

g = f intMonoidTwo-line do notation

A two-line do block desugars to the infix (>>=) operator:

do x <- me

-- ... desugars to:

m >>= (\x ->

e )One-line do notation

For a one-line do block, you can just remove the do:

main = do putStrLn "Hello, world!"-- ... desugars to:

main = putStrLn "Hello, world!"Multi-line do notation

do notation of more than two lines is equivalent to multiple nested dos:

do x <- mxy <- my

z

-- ... is equivalent to:

do x <- mx

do y <- my

z

-- ... desugars to:

mx >>= (\x ->

my >>= (\y ->

z ))let in do notation

Non-recursive let in a do block desugars to a lambda:

do let x = yz

-- ... desugars to:

(\x -> z) yghci

The ghci interactive REPL is analogous to one big do block (with lots and lots of caveats):

$ ghci>>> str <- getLine

>>> let str' = str ++ "!"

>>> putStrLn str'

-- ... is equivalent to the following Haskell program:

main = do

str <- getLine

let str' = str ++ "!"

putStrLn str'

-- ... desugars to:

main = do

str <- getLine

do let str' = str ++ "!"

putStrLn str'

-- ... desugars to:

main =

getLine >>= (\str ->

do let str' = str ++ "!"

putStrLn str' )

-- ... desugars to:

main =

getLine >>= (\str ->

(\str' -> putStrLn str') (str ++ "!") )

-- ... reduces to:

main =

getLine >>= (\str ->

putStrLn (str ++ "!") )List comprehensions

List comprehensions are equivalent to do notation:

[ (x, y) | x <- mx, y <- my ]-- ... is equivalent to:

do x <- mx

y <- my

return (x, y)

-- ... desugars to:

mx >>= (\x -> my >>= \y -> return (x, y))

-- ... specialization to lists:

concatMap (\x -> concatMap (\y -> [(x, y)]) my) mx

The real desugared code is actually more efficient, but still equivalent.

The MonadComprehensions language extension generalizes list comprehension syntax to work with any Monad. For example, you can write an IO comprehension:

>>> :set -XMonadComprehensions>>> [ (str1, str2) | str1 <- getLine, str2 <- getLine ]

Line1<Enter>

Line2<Enter>

("Line1", "Line2")Numeric literals

Integer literals are polymorphic by default and desugar to a call to fromIntegral on a concrete Integer:

1 :: Num a => a-- desugars to:

fromInteger (1 :: Integer)

Floating point literals behave the same way, except they desugar to fromRational:

1.2 :: Fractional a => a-- desugars to:

fromRational (1.2 :: Rational)IO

You can think of IO and all foreign function calls as analogous to building up a syntax tree describing all planned side effects:

main = dostr <- getLine

putStrLn str

return 1

-- ... is analogous to:

data IO r

= PutStrLn String (IO r)

| GetLine (String -> IO r)

| Return r

instance Monad IO where

(PutStrLn str io) >>= f = PutStrLn str (io >>= f)

(GetLine k ) >>= f = GetLine (\str -> k str >>= f)

Return r >>= f = f r

main = do

str <- getLine

putStrLn str

return 1

-- ... desugars and reduces to:

main =

GetLine (\str ->

PutStrLn str (

Return 1 ))

This mental model is actually very different from how IO is implemented under the hood, but it works well for building an initial intuition for IO.

For example, one intuition you can immediately draw from this mental model is that order of evaluation in Haskell has no effect on order of IO effects, since evaluating the syntax tree does not actually interpret or run the tree. The only way you can actually animate the syntax tree is to define it to be equal to main.

ConclusionI haven't covered everything, but hopefully that gives some idea of how Haskell translates higher level abstractions into lower-level abstractions. By keeping the core language small, Haskell can ensure that language features play nicely with each other.

Note that Haskell also has a rich ecosystem of experimental extensions, too. Many of these are experimental because each new extension must be vetted to understand how it interacts with existing language features.

### Hackage inconsistent? (pandoc.cabal in 1.13.1)

### Danny Gratzer: The Guts of a Spineless Machine

It’s fairly well known that Haskell is a bit um.. different then how stock hardware sees the world. I’m not aware of too many processors that have decided that immutability and higher order functions are the right way to go.

Compiling Haskell and its ilk, however, does have one interesting wrinkle on top of the normal problem: laziness. Laziness stands completely at odds with how most everything else works. More over, whether or not you think it’s the right default it’s an interesting question of how to efficiently compile some evaluation strategy other than call by value or name.

To this end, people have built a lot of abstract machines that lazy languages could target. The idea being that these machines can be mapped easily to what we hardware want and transitively, we can get our compiler. Most of these work by “graph reduction” (that’s the G in STG) and the latest incarnation of these graph machines is the spineless tagless graph machine which lies at the heart of GHC and a few other compilers.

In this post, I’d like to go over how exactly the STG machine actually works. Turns out it’s pretty cool!

Core ConceptsThe basic idea behind a compiler intent on going the STG route is something like

- .. front end stuff ..
- Translate IL to STG language
- Compile STG language to C/ASM/LLVM/Javascript

In GHC case I understand the pipeline is something like

- Parsing
- Type checking
- Desugaring + a few bobs and bits
- Translation to core
- Lion share of optimization
- Translation to STG language
- STG language to C–
- C– to assembly or llvm

We’re really concerned with parts 6 and 7 here. First things first, let’s lay out what’s exactly in the STG language. It’s a tiny functional language that looks a bit like Haskell or Core, with a few restrictions. A program is simply a series of bindings, much like Haskell. The top levels look something like

f = {x y z} flag {a b c} -> ...You should read this for now as f = \a b c -> .... The first set of variables and the flag correspond to some stuff we’ll discuss later.

Inside the ... we can write most of what you would expect form Haskell. We have let[rec] bindings, case expressions, application, constructors, literals, and primitives. There is a caveat though, first off all constructor applications must be fully saturated. This isn’t unlike OCaml or something where you can’t just treat a constructor as a function with an arbitrary name. We would write

\a -> Just ainstead of just Just. Another bit of trickiness, our language has no lambdas! So we can’t even write the above. Instead if we had something like

map Just [1, 2, 3]We’d have to write

let f = \a -> Just a l'' = 3 : nil l' = 2 : l'' l = 1 : l' in map f lThe reason for the awkward l'' series is that we’re only allowed to apply constructors and functions to atoms (literals and variables).

One other noteworthy feature of STG is that we have primitive operations. They need to be fully saturated, just like constructors, but they work across unboxed things. For example there would probably be something like +# which adds to unboxed integers. To work with these we also have unboxed literals, 1#, 2#, so on and so on.

No despite all these limitations placed on STG, it’s still a pretty stinking high level language. There’s letrec, higher order functions, a lot of the normal stuff we’d expect in a functional language. This means it’s not actually to hard to compile something like Haskell or Core to STG (I didn’t say “compile efficiently”).

As an example, let’s look at translating factorial into STG language. We start with

f :: Int -> Int f i = case i of 0 -> 1 i -> i * (f (i - 1))Now the first step is we change the binding form

f = {} n {i} -> ...The case expressions clause can remain the same, we’re already casing on an atom

case i of (MkInt# i#) -> ...Now comes the first big change, our boxed integers are going to get in the way here, so the case expression strips away the constructor leaving us with an unboxed integer. We can similarly refactor the body to make evaluation order explicit

case i of MkInt i# -> case i# -# 1# of dec# -> let dec = \{dec#} u {} -> MkInt dec# in case fact dec of MkInt rest# -> case i# * rest# of result# -> MkInt result#Notice how the case expressions here are used to make the evaluation of various expressions explicit and let was used to create a new thing to evaluate.

Now we can see what those extra {}’s were for. They notate the free variables for a thunk. Remember how we can have all sorts of closures and it can make for some really nice code? Well the machine doesn’t exactly support those naively. What we need to do and note the variables that we close over explicitly and then generate code that will store these free variables with the value that closes over them. This pair is more or less what is called a “closure” for the rest of this post.

dec for example has a free variable dec# and it exists to box that result for the recursive call to factorial. We use case expressions to get evaluation. Most programs thus become chains of case’s and let alternating between creating thunks and actually doing work.

That u in between the {}’s in dec was also important. It’s the update flag. Remember how in Haskell we don’t want to force the same thunk twice. If I say

let i = 1 + 1 in i + iWe should only evaluate 1 + 1 once. That means that the thunk i will have to be mutated to not evaluate 1 + 1 twice. The update flag signifies the difference between thunks that we want to update and thunks that we don’t. For example, if we replaced the thunk for + with the first result it returned, we’d be mighty surprised. Suddenly 1 + 1 + 1 is just 2!

The u flag says “yes, I’m just a normal expression that should be updated” and the n flag says the opposite.

That about wraps up our discussion of the STG language, let’s talk about how to implement it now.

SemanticsThis language wouldn’t be much good if it didn’t lend itself to an easy implementation, indeed we find that the restrictions we placed upon the language prove to be invaluable for its compilation (almost like they were designed that way!).

In order to decide how best to implement it, we first let at the formal semantics for our language. We give these semantics a tuple of 6 things.

- The code - the instruction we’re currently executing
- The argument stack - A stack of integers or pointers to closures
- The return stack - A stack of continuations
- The update stack - A stack of update frames and sadness
- The heap - A map from addresses to closures
- The environment - A map from names to addresses of toplevel closures

A code is more or less the current thing we’re attempting to do. It’s either

- Eval e p - evaluate an expression in an environment (p)
- Enter a - Enter a closure
- ReturnCon c ws - Return a constructor applied to some arguments
- ReturnInt - Return an integer

Now the idea is we’re going to “unroll” our computations into pushing things onto the continuation stack and entering closures. We start with the code Eval main {}. That is to say, we start by running main. Then if we’re looking at a case we do something really clever

EVAL(case expr of {pat1 -> expr1; ...}, p) as rs us h obecomes

EVAL (expr, p) as ({pat1 -> expr1; ...} : rs) us h oThat is to say, we just push the pattern matching on to the continuation stack and evaluate the expression.

At some point we’ll get to a “leaf” in our expression. That is random literal (a number) or constructor. At this point we make use of our continuation stack

EVAL (C ws, p) as ((...; c vs -> expr; ...) : rs) us h o ReturnCon (C ws) as ((...; c vs -> expr; ...) : rs) us h o EVAL (expr, p[vs -> ws]) as rs us h oSo our pattern matching is rolled into ReturnCon. ReturnCon will just look on top of the return stack looking for a continuation which wants its constructor and evaluate its expression, mapping the constructor’s variables to the pattern’s variables.

The story is similar for literals

EVAL (Int i, p) as ((...; c vs -> expr; ...) : rs) us h o ReturnInt i as ((...; i -> expr; ...) : rs) us h o EVAL (expr, p) as rs us h oAnother phase is how we handle let’s and letrec’s. In this phase instead of dealing with continuations, we allocate more thunks onto the heap.

EVAL ((let x = {fs} f {xs} -> e; ... in expr), p) as rs us h o EVAL e p' as us h' oSo as we’d expect, evaluating a let expression does indeed go and evaluate the body of the let expression, but changes up the environment in which we evaluate them. We have

p' = p[x -> Addr a, ...] h' = h[a -> ({fs} f {xs} -> e) p fs, ...]In words “the new environment contains a binding for x to some address a. The heap is extended with an address a with a closure {fs} f {xs} -> ... where the free variables come from p”. The definition for letrec is identical except the free variables come from p' allowing for recursion.

So the STG machine allocates things in lets, adds continuations with case, and jumps to continuation on values.

Now we also have to figure out applications.

EVAL (f xs, p) as rs us h o ENTER a (values of xs ++ as) rs us h owhere the value of f is Addr a. So we push all the arguments (remember they’re atoms and therefore trivial to evaluate) on to the argument stack and enter the closure of the function.

How do we actually enter a closure? Well we know that our closures are of the form

({fs} f {vs} -> expr) freesIf we have enough arguments to run the closure (length vs > length of argument stack), then we can just EVAL expr [vs -> take (length vs) as, fs -> frees]. This might not be the case in something like Haskell though, we have partial application. So what do we do in this case?

What we want is to somehow get something that’s our closure but also knows about however many arguments we actually supplied it. Something like

({fs ++ supplied} f {notSupplied} -> expr) frees ++ aswhere supplied ++ notSupplied = vs. This updating of a closure is half of what our update stack us is for. The other case is when we *do* actually enter the closure, but f = u so we’re going to want to update it. If this is the case we add an update from to the stack (as, rs, a) where as is the argument stack, rs is the return stack, and a is the closure which should be updated. Once we’ve pushed this frame, we promptly empty the argument stack and return stack.

We then add the following rules to the definition of ReturnCon

ReturnCon c ws {} {} (as, rs, a) : us h o ReturnCon c ws as rs us h' owhere h' is the new heap that’s replaced our old closure at a with our new, spiffy, updated closure

h' = h[a -> ({vs} n {} -> c vs) ws]So that’s what happens when we go to update a closure. But what about partial application?

Enter a as {} (asU, rs, aU) : us h o Enter a (as ++ asU) rs us h' owhere

h a = ({vs} n {xs} -> expr) frees h' = h [aU -> ((vs ++ bound) n xs -> e) (frees ++ as)]This is a simplified rule from what’s actually used, but gives some intuition to what’s happening: we’re minting a new closure in which we use the arguments we’ve just bound and that’s what the result of our update is.

Compiling ThisNow that we have some idea of how this is going to work, what does this actually become on the machine?

The original paper by SPJ suggests an “interpreter” approach to compilation. In other words, we actually almost directly map the semantics to C and call it compiled. There’s a catch though, we’d like to represent the body of closures as C functions since they’re well.. functions. However, since all we do is enter closures and jump around to things till the cows come home, it had damn well better be fast. C function calls aren’t built to be that fast. Instead the paper advocates a tiny trampolining-esque approach.

When something wants to enter a closure, it merely returns it and our main loop becomes

while(1){cont = (*cont)();}Which won’t stackoverflow. In reality, more underhanded tricks are applied to make the performance suck less, but for we’ll ignore such things.

In our compiled results there will be 2 stacks, not the 3 found in our abstract machine. In the first stack (A-stack) there are pointer things and the B-stack has non-pointers. This are monitored by two variables/registers SpA and SpBwhich keep track of the heights of the two stacks. Then compilation becomes reasonably straightforward.

An application pushes the arguments onto the appropriate stacks, adjusts Sp*, and enters the function A let block allocates each of the bound variables, then the body. Entering a closure simply jumps to the closures code pointer. This is actually quite nifty. We delegate all the work of figuring out exactly what Enter will do (updates, continuation jiggering) is left to the closure itself.

A case expression is a bit more complicated since a continuation’s representation involves boxing up the local environment for each branch. Once that’s bundled away, we represent a continuation as a simple code pointer. It is in charge of scrutinizing the argument stack and selecting an alternative and then running the appropriate code. This is a lot of work, and unless I’m crazy will need to types of bound variables for each branch (really just ptr/non-ptr). The selection of an alternative would be represented as a C switch, letting all sorts of trickery with jump tables be done by the C compiler.

In order to return a value, we do something clever. We take a constructor and point a global variable at its constructor closure, containing its values and jump to the continuation. The continuation can then peek and poke at this global variable to bind things as needed for the alternatives. There is potentially a massive speedup by returning through registers, but this is dangerously close to work.

From here, primitive operations can be compiled to statements/instructions in whatever environment we’re targeting. In C for example we’d just use the normal + to add our unboxed integers.

The last beast to slay is updates. We represent update frames by pointers to argument stacks and a pointer to a closure. That means that the act of updating is merely saving Sp* in an update from, clobbering them, and then jumping into the appropriate closure. We push the update from onto stack B and keep on going.

I realize that this is a glancing overview and I’m eliding a lot of the tricky details, but hopefully this is sufficient to understand a bit about what going on at an intuitive level.

Wrap UpSo now that you’ve put all the effort to get through this post, I get to tell you it’s all lies! In reality GHC has applied all manner of tricks and hacks to get fast performance out of an STG model. To be honest I’m not sure where I should point to that explains these tricks because well… I have no idea what they are.

I can point to

If you have any suggestions for other links I’d love to add them!

*Thanks Chris Ganas for proof reading*

### Christopher Done: Fast pagination on PostgreSQL

During the implementation of IRCBrowse I discovered that Postgres’s built-in offset is not very fast.

Here are the characteristics of my data:

ircbrowse=> \d event Table "public.event" Column | Type | -----------+--------------------------+ timestamp | timestamp with time zone | type | text | nick | text | text | text | network | integer | channel | integer | id | bigint | Indexes: "event_unique" UNIQUE CONSTRAINT, btree (network, channel, "timestamp", nick, type, text) "event_unique_id" UNIQUE CONSTRAINT, btree (id) "event_channel_idx" btree (channel) "event_nick_idx" btree (nick) "event_timestamp_idx" btree ("timestamp") "event_type_idx" btree (type)And the size:

ircbrowse=> select count(*) from event; count ---------- 28673917Channel 1 is the biggest:

ircbrowse=> select count(*) from event where channel = 1; count ---------- 19340467When you’re working with data on this scale (large, but not “big data”), PostgreSQL handles it beautifully. But the speed of OFFSET/LIMIT is not great:

ircbrowse=> explain analyze select * from event where channel = 1 order by id offset 500000 limit 30; QUERY PLAN Limit (cost=5648.81..5818.28 rows=30 width=85) (actual time=0.301..0.309 rows=30 loops=1) -> Index Scan using event_unique_id on event (cost=0.00..81914674.39 rows=14501220 width=85) (actual time=0.020..0.288 rows=1030 loops=1) Filter: (channel = 1)I think that this index scan is simply too expensive. Notice that I’m ordering by id which has a unique btree index on it. Check out the speed:

ircbrowse=> select * from event where channel = 1 order by id offset 1000 limit 30; Time: 0.721 ms ircbrowse=> select * from event where channel = 1 order by id offset 500000 limit 30; Time: 191.926 msYou might think less than a second to sift through 500,000 rows of a 28million row table is pretty good, but I think it sucks. It’s also deceptive. Let’s increase it to 1,000,000 rows (of 19,000,00):

ircbrowse=> select * from event where channel = 1 order by id offset 1000000 limit 30; Time: 35022.464 msThis is getting worse and worse! It’s probably linear in its poor performance.

However, there’s a solution. Use an index table. A separate table which contains foreign keys pointing to this table:

ircbrowse=> \d event_order_index Table "public.event_order_index" Column | Type | Modifiers --------+---------+----------- id | integer | not null origin | integer | not null idx | integer | not null Indexes: "event_order_id_origin" UNIQUE CONSTRAINT, btree (id, origin) "event_order_idx" btree (id) "event_order_idx_idx" btree (idx) "event_order_origin_dx" btree (origin)Now you can have a pagination index for channel 1:

ircbrowse=> select * from event_order_index where idx = 1000 limit 1; id | origin | idx ----+--------+------ 1 | 1 | 1000(I used idx=1000 for channel 1, 2000 for channel 2, etc. so that I would have space for other numerical indexes for the same channel.)

Now you can make a very efficient query for the same data as above:

ircbrowse=> SELECT idx.id,e.timestamp,e.network,e.channel, ircbrowse=> e.type,e.nick,e.text FROM event e, ircbrowse-> event_order_index idx ircbrowse-> WHERE e.id = idx.origin and idx.idx = 1000 and ircbrowse=> idx.id > 1000000 and idx.id < 1000030 ircbrowse-> ORDER BY e.id asc ircbrowse-> LIMIT 30; Time: 1.001 msThis is more or less constant time.

And you can see this in action on the site. Takes about 30ms to load and render the page if I run this on the server:

$ time curl 'http://ircbrowse.net/browse/haskell?events_page=234' real 0m0.031s user 0m0.000s sys 0m0.004sOf course, sending a request in your browser will take longer due to the connection overhead and assets, but generally the goal was for it to be very snappy. The old ircbrowse.com (by another individual, who kindly let me have the name) was very slow indeed. You’d see the page loading the data incrementally from the database.

Anyhoo, thought that was a decent, practical PostgreSQL-specific optimization regarding pagination. Hope it was worth writing up.