forked from adamtornhill/LispForTheWeb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmap_reduce_in_mongo.lisp
93 lines (75 loc) · 3.02 KB
/
map_reduce_in_mongo.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
;;; Copyright (C) 2014 Adam Tornhill
;;;
;;; Distributed under the GNU General Public License v3.0,
;;; see http://www.gnu.org/licenses/gpl.html
;;; The following module contains parts of the source code for
;;; my book Lisp for the Web. You can get a copy of the book here:
;;; https://leanpub.com/lispweb
(defpackage :retro-games
(:use :cl :parenscript :cl-mongo))
(in-package :retro-games)
;; Domain model
;; ============
;; I've extended the model in comparison
;; with earlier versions. Now we're also able to categorize
;; a game. I've removed the CLOS functions here in order to
;; keep this module as minimalistic as possible:
(defclass game ()
((name :reader name
:initarg :name)
(votes :accessor votes
:initarg :votes
:initform 0)
(category :accessor category
:initarg :category)))
;; Backend
;; =======
;; Pre-requisite: a mongod daemon process runs on localhost
;; using the default port.
;; Here we establish a connection to the database games that
;; we'll use for all storage:
(cl-mongo:db.use "games")
;; We store all game documents in the following collection:
(defparameter *game-collection* "game")
;; A stripped-down version, just enough to store some games:
(defun game->doc (game)
(with-slots (name votes category) game
($ ($ "name" name)
($ "votes" votes)
($ "category" category))))
(defun add-game (name category)
"Add a game with the given name to the database.
In this version we don't check for duplicates."
(let ((game (make-instance 'game :name name :category category)))
(db.insert *game-collection* (game->doc game))))
;; MapReduce
;; =========
;; MongoDB supports a mapReduce database command.
;; All we have to do as a client is to supply the map- and
;; reduce-functions to mongo. Mongo will execute the algorithm
;; in its daemon process (mongod).
;;
;; To invoke mapReduce, we define client-side JavaScript functions:
;; First our map-function. This one emits once for each game
;; category. Since we want to sum all games withing a category,
;; our value is a constant one (1).
(defjs map_category()
(emit this.category 1))
;; The reducer is straightforward: just sum up all values for
;; the given category (c). Since map_category emitted 1's, the
;; length of vals should actually be our answer.
(defjs sum_games(c vals)
((@ *array sum) vals))
;; cl-mongo provides a convenience macro for remote execution of
;; mapReduce. If we have defined the JavaScript functions we just
;; need to provide them _without_ any quoting (that's done in the
;; $map-reduce macro):
(defun sum-by-category ()
(pp (mr.p ($map-reduce "game" map_category sum_games))))
;; An alternative is to provide the raw JavaScript functions here.
;; That functions are identical to the ones generated by Parenscript,
;; sans the outer paranthesis.
(defun sum-by-category-1 ()
(pp (mr.p ($map-reduce "game"
"function (x) { return emit(this.category, 1);};"
"function (c, vals) { return Array.sum(vals);};"))))