Create a gist now

Instantly share code, notes, and snippets.

Clojure vs Haskell

Haskell vs Clojure

The JSON data is in the following format

{
    "Genesis": {
        "1": {
            "1": "In the beginning..." ,
            "2": "..."
        },
        "2": { ... }
    },
    "Exodus": { ... },
    ...
}

In JSON, keys aren't ordered and must be strings.

The goal is to parse the JSON file, order everything correctly (which means parsing the keys into integers), and produce an array that looks like this:

[
    ("Genesis", 1, 1, "In the beginning..")
    ("Genesis", 1, 2, "")
]

The data structure can be a tuple or some new type.

Clojure

(ns versify.core
  (:require [cheshire.core :refer :all])
  (:gen-class))

(def book-order ["Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
    "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
    "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
    "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
    "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
    "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
    "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
    "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
    "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
    "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
    "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"])

(defn intify [m]
  (apply assoc (sorted-map)
         (mapcat 
           (fn [[k v]]
             [(read-string k), v]) m)))

(defn get-books [bible]
  (map (fn [book] [book, (bible book)]) book-order))

(defn get-chapters [books]
  (mapcat (fn [[k v]]
         (let [i (intify v)]
           (map (fn [[n c]] [k, n, c]) i))) books))

(defn get-verses [verses]
  (mapcat (fn [[b c d]]
            (let [i (intify d)]
              (map (fn [[v t]] [b, c, v, t]) i))) verses))

(def parse (comp get-verses get-chapters get-books))

(defn -main
  [& args]
  (let [bible (parse-string (slurp "ESV.json"))]
    (first (parse bible))))

Haskell

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Prelude hiding (lookup)
import Data.Aeson (decode, FromJSON)
import Data.Maybe (fromMaybe)
import Data.Aeson.Types
import Control.Applicative ((<$>))
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.Map (Map, toList, fromList, lookup)

bookOrder = ["Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
    "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
    "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
    "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
    "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
    "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
    "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
    "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
    "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
    "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
    "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"]

type Verse      = String
newtype Chapter = Chapter (Map String Verse) deriving (Show, Eq, Ord)
newtype Book    = Book (Map String Chapter) deriving (Show, Eq, Ord)
newtype Bible   = Bible (Map String Book) deriving (Show, Eq, Ord)

newtype OrderedChapter = OrderedChapter (Map Int Verse) deriving (Show, Eq, Ord)
newtype OrderedBook    = OrderedBook (Map Int OrderedChapter) deriving (Show, Eq, Ord)
newtype OrderedBible   = OrderedBible (Map String OrderedBook) deriving (Show, Eq, Ord)

instance FromJSON Chapter where
    parseJSON val = Chapter <$> parseJSON val

instance FromJSON Book where
    parseJSON val = Book <$> parseJSON val

instance FromJSON Bible where
    parseJSON val = Bible <$> parseJSON val

getBooks :: OrderedBible -> [(String, OrderedBook)]
getBooks (OrderedBible b) = map (f b) bookOrder
    where f b k = (k, (x k b))
          x k b = case (lookup k b) of
                    Just b -> b
                    Nothing -> error "fail"

getChapters :: [(String, OrderedBook)] -> [(String, Int, OrderedChapter)]
getChapters books = concatMap f books
    where f (bookName, (OrderedBook b))              = map (g bookName) (toList b)
          g bookName (chapterNumber, chapter) = (bookName, chapterNumber, chapter)

getVerses :: [(String, Int, OrderedChapter)] -> [FlatVerse]
getVerses chapters = concatMap f chapters
    where f (bookName, chapterNumber, (OrderedChapter chapter)) = map (g bookName chapterNumber) (toList chapter)
          g bookName chapterNumber (verseNumber, verse)  = FlatVerse {
              text = verse,
              book = bookName,
              chapter = chapterNumber,
              verse = verseNumber}

flattenBible :: OrderedBible -> [FlatVerse]
flattenBible = (getVerses . getChapters . getBooks)

data FlatVerse = FlatVerse { text    :: String
                           , book    :: String
                           , chapter :: Int
                           , verse   :: Int
                           } deriving (Show)

formatVerse :: FlatVerse -> String
formatVerse v = "\"" ++ (text v) ++ "\" - " ++ (book v) ++ " "
                ++ show (chapter v) ++ ":" ++ show (book v)

loadBible :: BS.ByteString -> Bible
loadBible x = do
    let y = decode x :: Maybe Bible 
    case y of
        Just b -> b
        Nothing -> error "mle"

getOrderedBible :: Bible -> OrderedBible
getOrderedBible (Bible bible) = OrderedBible (fromList (c bible))
    where c bible     = map (g bible) bookOrder
          g bible key =  (key, (getOrderedBook book)) where
            book = case lookup key bible of
                Just b -> b
                Nothing -> error "fail"

getOrderedBook :: Book -> OrderedBook
getOrderedBook (Book b) = OrderedBook (fromList (map f (toList b)))
    where f (num, chapter) = (read num :: Int, (getOrderedChapter chapter))

getOrderedChapter :: Chapter -> OrderedChapter
getOrderedChapter (Chapter c) = OrderedChapter (fromList (map f (toList c)))
    where f (num, verse) = (read num :: Int, verse)

main = do
    putStrLn "hey"

    bible <- BS.readFile "ESV.json"
    let b = getOrderedBible (loadBible bible)
        f = flattenBible b
    putStrLn $ show $ length f
    putStrLn $ show $ formatVerse (head f)
@Morkrom

These really both do the same thing??

@steveoc64

Either way, there is sackcloth and Ashes, with much gnashing of teeth. Good job.

Let's extend this gist to include more languages please.

@faceyspacey

im not a master at either, but the haskell one being 3X longer isn't a good sign.

@seagreen

The Haskell solution is non-idiomatic, see this post (not by me): https://news.ycombinator.com/item?id=12501966

@pauldraper
pauldraper commented Sep 15, 2016 edited

Oooh, oooh, I love these.


Scala (Ammonite)

import $ivy.`com.typesafe.play::play-json:2.5.8`
import java.nio.file._, play.api.libs.json._

val bookOrder = Seq(
    "Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
    "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
    "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
    "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
    "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
    "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
    "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
    "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
    "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
    "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
    "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"
  ).zipWithIndex.toMap

val bible = Json.parse(Files.readAllBytes(Paths.get("ESV.json")))
  .as[Map[String,Map[String,Map[String,String]]]]

val flattened = for((book, x) <- bible; (chapter, x) <- x; (verse, x) <- x)
  yield (book, chapter.toInt, verse.toInt, x)
val result = flattened.toSeq.sortBy { case (b, c, v, _) => (bookOrder(b), c, v) }
println(result)

Python 3

import json

books = [
    "Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
    "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
    "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
    "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
    "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
    "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
    "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
    "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
    "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
    "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
    "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"
]
books = {book:i for i, book in enumerate(books)}

with open('ESV.json') as f:
    result = sorted(
        ((b, int(c), int(v), x) for b, x in json.load(f).items() for c, x in x.items() for v, x in x.items()),
        key=lambda part: (books[part[0]], part[1:])
    )
print(result)
@adamretter

XQuery 3.1

xquery version "3.1";

declare variable $book-order := ("Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
    "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
    "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
    "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
    "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
    "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
    "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
    "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
    "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
    "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
    "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation");

let $esv := parse-json(unparsed-text("ESV.json"))
return
    for $book-key in $book-order
    return
        for $sub-chapter in $esv($book-key)
        return
            for $sub-key in map:keys($sub-chapter)
            let $sub-sub-chapter := $sub-chapter($sub-key)
            for $sub-sub-key in map:keys($sub-chapter($sub-key))
            order by xs:int($sub-key), xs:int($sub-sub-key)
            return
                <chapter book="{$book-key}" chapter="{$sub-key}" sub-chapter="{$sub-sub-key}">{$sub-sub-chapter($sub-sub-key)}</chapter>

                (: or if you want JSON...
                    array { $book-key, $sub-key, $sub-sub-key, $sub-sub-chapter($sub-sub-key) }
                :)
@douglasrocha
douglasrocha commented Sep 15, 2016 edited

In the comments, both @pauldraper and @adamretter didn't understand that there is a concept that the author didn't violate: The functions for data extraction are the same in the clojure and haskell examples. Of course you can write shorter code, but you sacrifice legibility, maintainability and modularity. Writing code is about building the right levels of abstraction, and the author did it clearly with his example.

@pauldraper

A 5-line program doesn't require abstraction.

"The goal is...."

@theomails
theomails commented Sep 17, 2016 edited

A reference implementation in Java 7..

public class BibleJsonProcessor {
    public static void main(String[] args) {
        new BibleJsonProcessor().process();
    }

    private Gson g = new GsonBuilder().create();
    public void process(){
        try(InputStream is = getClass().getResourceAsStream("BibleJson.txt");
            InputStreamReader ir = new InputStreamReader(is);){ //Auto-close streams

            Type deepType = new TypeToken<Map<String/*Book name*/, Map<String/*Chapter no*/, Map<String/*Verse no*/, String>>>>(){}.getType(); //Specify expected type information
            Map<String, Map<String, Map<String, String>>> jsondata = g.fromJson(ir, deepType); //Parse using Gson library
            List<FlattenedVerse> verses = flatten(jsondata); //Flatten data
            Collections.sort(verses); //Sort

            System.out.println(verses); 
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private List<FlattenedVerse> flatten(Map<String/*Book name*/, Map<String/*Chapter no*/, Map<String/*Verse no*/, String>>> jsondata) {
        List<FlattenedVerse> result = new ArrayList<>();
        Set<String> bookKeys = jsondata.keySet();
        for(String bookName:bookKeys){ //Iterate book names
            Map<String, Map<String, String>> chapters = jsondata.get(bookName);
            Set<String> chapterIdxs = chapters.keySet();
            for(String chapterIdx:chapterIdxs){ //Iterate chapter idxes
                Map<String, String> verses = chapters.get(chapterIdx);
                Set<String> verseIdxs = verses.keySet();
                for(String verseIdx:verseIdxs){ //Iterate verse idxes
                    String verse = verses.get(verseIdx);
                    FlattenedVerse flattenedVerse = new FlattenedVerse(bookName, chapterIdx, verseIdx, verse);
                    result.add(flattenedVerse);
                }
            }
        }
        return result;
    }
}

class FlattenedVerse implements Comparable<FlattenedVerse> {
    private static final List<String> BOOKS = Arrays.asList(new String[]{"Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy",
            "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings",
            "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job",
            "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah",
            "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "Hosea", "Joel", "Amos",
            "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai",
            "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts",
            "Romans", "1 Corinthians", "2 Corinthians", "Galatians", "Ephesians",
            "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians",
            "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James",
            "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation"});
    final String bookName;
    final transient int bookSeqNo; /*transient because it is a calculated field, and not essentially a part of the model*/
    final int chapterNo;
    final int verseNo;
    final String verse;
    public FlattenedVerse(String bookName, String chapterNo, String verseNo, String verse) {
        super();
        this.bookName = bookName;
        this.bookSeqNo = BOOKS.indexOf(bookName);
        this.chapterNo = Integer.parseInt(chapterNo);
        this.verseNo = Integer.parseInt(verseNo);
        this.verse = verse;
    }
    @Override
    public String toString() {
        return "\nFlattenedVerse [bookName=" + bookName + ", chapterNo=" + chapterNo + ", verseNo=" + verseNo + ", verse="
                + verse + "]";
    }
    @Override
    public int compareTo(FlattenedVerse o) {
        int rank1 = bookSeqNo * 10_000_000 + chapterNo * 1_000 + verseNo;
        int rank2 = o.bookSeqNo * 10_000_000 + o.chapterNo * 1_000 + o.verseNo;
        return rank1-rank2;
    }
}
@jcrichton
jcrichton commented Sep 17, 2016 edited

Python

json = ... # read bible json data from file
book_order = ... # as specified

# List comprehension
book_chapter_and_verse = [
  (book, int(chapter_number), int(verse_number), verse)
  for book in book_order
  for chapter_number in sorted(json[book].keys(), key=int)
  for verse_number   in sorted(json[book][chapter_number].keys(), key=int)
  for verse          in [json[book][chapter_number][verse_number]]
]

# Generator function
def book_chapter_and_verse(json, book_order):
    for book in book_order:
        chapters = sorted(json[book].keys(), key=int)
        for chapter_number in chapters:
            verses = sorted(json[book][chapter_number].keys(), key=int)
            for verse_number in verses:
                verse = json[book][chapter_number][verse_number]
                yield book, int(chapter_number), int(verse_number), verse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment