The behaviour of the ZIP keyword on LSD is similar to the Python zip built-in function so, if you’re already familiar with their’s, feel free to skip down to the example otherwise read on for the tutorial.
You may have a single page that, ideally, yields generally complex objects which coalesce multiple containers and selectors. To make this convenient from LSD and not require yet another for loop in your application code, we provide a ZIP keyword which takes a list of functions (or, exactly, their symbol identifiers):
ZIP func1 func2 func3
Where the provided functions would result in rows like shown:
func1 func2 func3
┌──────┐ ┌──────┐ ┌──────┐
│ row1 │ │ row1 │ │ row1 │
│ │ │ │ │ │
│ row2 │ │ row2 │ │ row2 │
│ │ │ │ │ │
│ row3 │ │ row3 │ │ row3 │
└──────┘ └──────┘ └──────┘
What the ZIP keyword does is visually stacks the results horizontally to end up with a single table:
ZIP
func1 func2 func3
┌──────────────────────────┐
│ row1──────row1──────row1 │
│ │
│ row2──────row2──────row2 │
│ │
│ row3──────row3──────row3 │
└──────────────────────────┘
Since the results of functions in LSD are lists of objects, this is accomplished by merging the objects together and always taking the length of the shortest list incorporated. If two functions return conflicting keys then, like CSS, the one that applies the value the most recently (right most in a ZIP statement for LSD) is the one that should be expected. For example let’s suppose these were the objects being returned by functions:
┌──────────────────────────────────────────────────┐
│ func1 func2 func3 │
│ │
│ {"foo": "abc"} {"bar": "123"} {"foo": "jkl"} │
│ │
│ {"foo": "def"} {"bar": "456"} {"foo": "mno"} │
│ │
│ {"foo": "ghi"} {"bar": "789"} {"foo": "pqr"} │
└──────────────────────────────────────────────────┘
ZIPing the functions together would yield the following being combined.
ZIP
┌──────────────────────────────────────────────────┐
│ func1 func2 func3 │
│ │
│ {"foo": "abc"}───{"bar": "123"}───{"foo": "jkl"} │
│ │
│ {"foo": "def"}───{"bar": "456"}───{"foo": "mno"} │
│ │
│ {"foo": "ghi"}───{"bar": "789"}───{"foo": "pqr"} │
└──────────────────────────────────────────────────┘
Since we’re applying func3 after func1 the values it effectively sets for the key “foo” will be the ones coming from the former function:
ZIP
┌──────────────────────────────────────────────────┐
│ func1 func2 func3 │
│ │
│ {"foo": "jkl", "bar": "123"} │
│ │
│ {"foo": "mno", "bar": "456"} │
│ │
│ {"foo": "pqr", "bar": "789"} │
└──────────────────────────────────────────────────┘
Shown below is an example of when this is useful for a website that renders two lists of containers each holding information that maps 1-to-1 with each other. Namely, on Hacker News, a list of span tags contains information about the posts and a list of span tags contains information about the conversation plus author.
-- A shorthand for [Hacker News]
hn <| https://news.ycombinator.com |
-- Selectors for fields related to posts' discussions
subtitle_container <| span.subline |
author <| span.subline a.hnuser |
discussion_link <| span.subline span.age a@href |
-- Selectors for fields related to posts' contents
title_container <| span.titleline |
post <| a |
post_link <| a@href |
-- A function defined to obtain post conversations
get_post_conversation <|> <|
FROM hn
|> GROUP BY subtitle_container
|> SELECT author, discussion_link |
-- A function defined to obtain post contents
get_post_meta <|> <|
FROM hn
|> GROUP BY title_container
|> SELECT post, post_link |
-- Should return a table with [discussion_link, post, post_link, author]
ZIP get_post_conversation get_post_meta