├── README.md ├── articles ├── README.md ├── roles-permissions-1.md └── roles-permissions-2.md └── assets └── prologue-to-prolog.pdf /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to The Clause 2 | 3 | [Join our gitter chat room!](https://gitter.im/TheClause/community) 4 | 5 | The Clause is an initiative to bring Prolog into the modern web development world. Although Prolog is already quite capable, there is work to be done in writing more documentation, resources, and tutorials that are catered towards the web development industry. 6 | 7 | This repo is an accumulation of these efforts. Below you will find resources for learning and using Prolog. 8 | 9 | ### Support 10 | 11 | If you want to support The Clause and help pave the way forward, you can: 12 | 13 | - [Join the discussion](https://gitter.im/TheClause/community) on gitter. 14 | - Write articles that are relevant to mainstream web developers. 15 | - Follow The Clause [on Twitter!](https://twitter.com/ThePrologClause) 16 | 17 | Feel free to [ping me on gitter](https://gitter.im/fogfish) if you have any questions. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ## Introductions to Prolog 26 | 27 | - [Prologue to Prolog](https://twitter.com/ThePrologClause/status/1500642533940023302) (36m) ([slides](assets/prologue-to-prolog.pdf)) 28 | - [Programmation en Logique (Programming in Logic)](https://www.youtube.com/watch?v=VjJQQTfxuP0) (42m) 29 | 30 | ## Articles 31 | 32 | - Building a user permissions system ([part 1](https://dev.to/gilbert/write-a-user-permissions-system-in-5-lines-of-prolog-mof), [part 2](https://dev.to/theclause/write-a-role-permissions-system-in-14-lines-of-prolog-part-2-371n)) 33 | - Setting Up Unit Testing In SWI-Prolog ([link](http://www.paulbrownmagic.com/blog/swi_prolog_unit_testing_env.html)) 34 | - Functional Prolog: Map, Filter, and Reduce ([link](https://pbrown.me/blog/functional-prolog-map-filter-and-reduce/)) 35 | 36 | ## Fundamentals 37 | 38 | - [Defining custom operators](http://www.amzi.com/AdventureInProlog/a12oper.php) 39 | - [Modules](https://www.swi-prolog.org/pldoc/man?section=modules) 40 | - [Autogenerated Tests](https://www.swi-prolog.org/pldoc/man?section=wizard) 41 | 42 | ## In Production 43 | 44 | - [Production Prolog](https://youtu.be/G_eYTctGZw8) (39:57) 45 | - [0:00] Intro: Prolog has many solid implementations, logic programming is ridiculously simple 46 | - [1:54] Demo: Quick intro to Prolog 47 | - [12:17] Demo of SWI Prolog's incredible debugger 48 | - [16:36] Code is data, which makes writing tools for Prolog very easy 49 | - [18:19] Some libraries the speaker uses in production (library(func), library(mavis), library(bencode)) 50 | - [23:01] Constraint logic programming - library(julian) for dates and times. A beauty. 51 | - [26:34] Concurrency - library(spawn). Wow. 52 | - [30:04] Deploying saved states, his favorite production trick. `swipl -o foo -c foo.pl` 53 | - [32:18] Not all sunshine and rainbows 54 | 55 | ## Resources 56 | 57 | - [SWI Prolog forums](https://swi-prolog.discourse.group) - for deeper discussions and troubleshooting 58 | - [Awesome Prolog](https://github.com/klaussinani/awesome-prolog#resources) 59 | - [Full-featured online environment](https://swish.swi-prolog.org) (SWI) 60 | - [JavaScript-based browser environment](http://tau-prolog.org/sandbox/) 61 | - [Prolog cheet sheat](https://github.com/alhassy/PrologCheatSheet) 62 | - The [Association for Logic Programming](http://logicprogramming.org) for those who are interested in the theoretical side of Prolog. If you're looking to do research and/or a PhD, definitely visit that link! 63 | -------------------------------------------------------------------------------- /articles/README.md: -------------------------------------------------------------------------------- 1 | # Articles 2 | 3 | Here you will find a mirror of all articles we post on the web, slightly edited to fit the context. 4 | -------------------------------------------------------------------------------- /articles/roles-permissions-1.md: -------------------------------------------------------------------------------- 1 | ## Building a Roles & Permissions System: Part 1 2 | 3 | Logic programming is something all developers should learn. It isn't actually that hard, and simply knowing **what's possible** will positively influence you in choosing better tools, for now and the future. 4 | 5 | In this post I'm going to show you how easy it is to write a role-based permissions system in Prolog. In our first iteration, you'll only need **5 lines of logic**, and easily extendable to boot. This will give you a sample taste of the full power of Prolog. 6 | 7 | **Note:** This is not a full introduction to Prolog. That said, you should still be able to follow along, because **logic programming is easy**. When you're ready to dive deeper, you can bookmark [this post](https://dev.to/matchilling/introduction-to-logic-programming-with-prolog-cdh) for later. 8 | 9 | ## The Scenario 10 | 11 | Let's say we have users and clubs: 12 | 13 | ```pl 14 | user(alice). 15 | user(bob). 16 | user(carly). 17 | user(dan). 18 | user(elli). 19 | 20 | club(boxing). 21 | club(chess). 22 | ``` 23 | 24 | with a [many-to-many relationship](https://en.wikipedia.org/wiki/Many-to-many_(data_model)) between the two: 25 | 26 | ```pl 27 | member(alice, chess). 28 | member(dan, chess). 29 | member(elli, chess). 30 | 31 | member(alice, boxing). 32 | member(bob, boxing). 33 | member(carly, boxing). 34 | ``` 35 | 36 | And lastly, we want to specify which user is allowed to **moderate** which club: 37 | 38 | ```pl 39 | moderator(dan, chess). 40 | 41 | moderator(alice, boxing). 42 | moderator(bob, boxing). 43 | ``` 44 | 45 | Note that we haven't yet written a single line of logic. All code so far has been **facts** – simple statements of truth. And yet, with only this, Prolog grants us great, expressive power! Allow me to demonstrate. 46 | 47 | Here is how we ask Prolog for all the clubs `alice` is a member of: 48 | 49 | ```pl 50 | ?- member(alice, C). 51 | C = chess ; 52 | C = boxing. 53 | ``` 54 | 55 | Note how Prolog gives us two solutions. 56 | 57 | How about all clubs that `alice` is *also* a moderator of? 58 | 59 | ```pl 60 | ?- member(alice, C), moderator(alice, C). 61 | C = boxing. 62 | ``` 63 | 64 | Or how about all non-moderator members of all clubs? 65 | 66 | ```pl 67 | ?- member(U, C), \+ moderator(U, C). 68 | U = alice, 69 | C = chess ; 70 | U = elli, 71 | C = chess ; 72 | U = carly, 73 | C = boxing. 74 | ``` 75 | 76 | Note how Prolog gives us **three** solutions when there are *six* memberships. Also note that `\+` is the operator for "not". 77 | 78 | ## The Logic 79 | 80 | Given the incredible expressiveness Prolog grants us, it's very easy to begin adding logic for protected moderator actions. 81 | 82 | Let's say we want a moderator to be able to **ban a club member**. A little harsh, I know, but it's a concept we all understand! 83 | 84 | Here's how you can do it – in the advertised 5 lines – to be explained shortly afterwards: 85 | 86 | ```pl 87 | can(User, Club, ban_user, Target) :- 88 | moderator(User, Club), 89 | member(Target, Club), 90 | \+ moderator(Target, Club), 91 | dif(User, Target). 92 | ``` 93 | 94 | and here's how you use it: 95 | 96 | ```pl 97 | % Alice can ban a member 98 | ?- can(alice, boxing, ban_user, carly). 99 | true. 100 | 101 | % But she cannot ban another moderator 102 | ?- can(alice, boxing, ban_user, bob). 103 | false. 104 | 105 | % Nor can she ban herself 106 | ?- can(alice, boxing, ban_user, alice). 107 | false. 108 | 109 | % And non-moderators cannot ban anyone 110 | ?- can(elli, chess, ban_user, alice). 111 | false. 112 | ``` 113 | 114 | That's right, we now have fully working permission logic for banning a user. All moderators now have the ability to ban. To add another moderator, just add a new `moderator` clause. 115 | 116 | Here's an explanation of the `can` rule: 117 | 118 | - `can(User, Club, ban_user, Target)` - For a given user, club, and target, we are defining logic specifically for `ban_user`. 119 | - `moderator(User, Club)` - The given user must be a moderator of the given club. 120 | - `member(Target, Club)` - The given target user must be a member of *that same* club. You can't ban someone who's not even in the club! 121 | - `\+ moderator(Target, Club)` - The target user must not be a moderator. 122 | - `dif(User, Target)` - And lastly, the user performing the action cannot target themselves (`dif` is built into Prolog). 123 | 124 | And that's all it takes. Gaze upon the beauty of Prolog. 125 | 126 | ## Going Further 127 | 128 | - The facts we wrote are hard-coded. In practice, these can be loaded from a database. 129 | - [SWI Prolog](https://www.swi-prolog.org/pldoc/doc_for?object=manual) has HTTP and Docker support, so it's possible to host a "logic server" that answers questions for the other web services in your network. 130 | - The concept of roles and permissions can be decoupled and abstracted further, making our data even more serialization-friendly. This is covered in [Part 2](./permission-system-2.md). 131 | 132 | 133 | ## Full Code 134 | 135 | ```pl 136 | user(alice). 137 | user(bob). 138 | user(carly). 139 | user(dan). 140 | user(elli). 141 | 142 | club(boxing). 143 | club(chess). 144 | 145 | member(alice, chess). 146 | member(dan, chess). 147 | member(elli, chess). 148 | 149 | member(alice, boxing). 150 | member(bob, boxing). 151 | member(carly, boxing). 152 | 153 | moderator(dan, chess). 154 | moderator(alice, boxing). 155 | moderator(bob, boxing). 156 | 157 | can(User, Club, ban_user, Target) :- 158 | moderator(User, Club), 159 | dif(User, Target), 160 | member(Target, Club), 161 | \+ moderator(Target, Club). 162 | ``` 163 | -------------------------------------------------------------------------------- /articles/roles-permissions-2.md: -------------------------------------------------------------------------------- 1 | ## Building a Roles & Permissions System: Part 2 2 | 3 | [Part 1](./permission-system-1.md) showed how to implement a `moderator` role with hardcoded permissions. In this post we will implement a more scalable system where roles & permissions are properly decoupled. 4 | 5 | Fun fact: This will only increase the number of logical lines from 5 to 14 :) 6 | 7 | ## The Scenario 8 | 9 | In [part 1](./permission-system-1.md) we defined `user` and `club` predicates. This isn't actually necessary for our purposes; as long as the ids (e.g. `alice` and `chess`) match across predicates, we don't need to define `user(alice)` and `club(chess)`. 10 | 11 | With that in mind, let's build our program from scratch again, and start with defining club memberships: 12 | 13 | ```pl 14 | member(alice, boxing). 15 | member(bob, boxing). 16 | member(carly, boxing). 17 | member(dan, boxing). 18 | 19 | member(alice, chess). 20 | member(bob, chess). 21 | ``` 22 | 23 | Next, let's define some roles. Last time we defined a `moderator` predicate. This time let's make things more flexible by defining a more general `role` predicate instead: 24 | 25 | ```pl 26 | role(alice, boxing, admin). 27 | role(bob, boxing, moderator). 28 | role(bob, chess, moderator). 29 | ``` 30 | 31 | For every moderator permission, we want admins to **also** have that permission. Let's define this relationship using another predicate: 32 | 33 | ```pl 34 | role_inherits(admin, moderator). 35 | ``` 36 | 37 | Note that this doesn't actually define any inheritance *logic*; it only defines a **relationship** that we will take advantage of later. 38 | 39 | Lastly, let's assign specific permissions to specific roles: 40 | 41 | ```pl 42 | permission(admin, promote_to_mod). 43 | permission(moderator, ban_user). 44 | permission(moderator, ban_protection). 45 | ``` 46 | 47 | **Note 1:** Notice how we *don't* say that admins can ban a user. We are assuming that admins can do everything moderators can do – even though that isn't true yet! The logic for that will come later. 48 | 49 | **Note 2:** Notice how these are all facts. Any facts that you define in Prolog can easily be stored and loaded by an external database. 50 | 51 | ### Query Examples 52 | 53 | With our fresh new list of facts, let's run some queries to get a feel for what's possible. 54 | 55 | First: What roles does `bob` have, and in which clubs? 56 | 57 | ```prolog 58 | ?- role(bob, C, R). 59 | C = boxing, 60 | R = moderator ; 61 | C = chess, 62 | R = moderator. 63 | ``` 64 | 65 | What permitted actions does `bob` have in the boxing club? 66 | 67 | ```prolog 68 | ?- role(bob, boxing, R), permission(R, A). 69 | R = moderator, 70 | A = ban_user ; 71 | R = moderator, 72 | A = ban_protection ; 73 | false. 74 | ``` 75 | 76 | What permitted actions does `alice` have in the boxing club? 77 | 78 | ```prolog 79 | ?- role(alice, boxing, R), permission(R, A). 80 | R = admin, 81 | A = promote_to_mod. 82 | ``` 83 | 84 | **Uh oh.** Notice how `alice` does not have moderator permissions. She only has admin permissions! 85 | 86 | Why is this? Let's look at how the `permission` predicate behaves: 87 | 88 | ```prolog 89 | ?- permission(moderator, A). 90 | A = ban_user ; 91 | A = ban_protection. 92 | 93 | ?- permission(admin, A). 94 | A = promote_to_mod. 95 | ``` 96 | 97 | Aha, the `permission` predicate is only giving us **direct** relationships! This is actually a good thing. However, logically we want `admin` to inherit *all* permissions from `moderator`, so let's do that next. 98 | 99 | ## Role Inheritance 100 | 101 | This problem brings us to our first two lines of logic: a predicate `role_has_permission` that handles permissions **while also considering role inheritance:** 102 | 103 | ```pl 104 | role_has_permission(Role, Action) :- permission(Role, Action). 105 | role_has_permission(Role, Action) :- 106 | role_inherits(Role, Child), 107 | role_has_permission(Child, Action). 108 | ``` 109 | 110 | This is a recursive predicate, and it also happens to be a common Prolog pattern. Here's the explanation: 111 | 112 | - [line 1] A `Role` has permission to do an `Action` if it is specified by the `permission` predicate. 113 | - [line 2] Either that, or a `Role` has permission to do an `Action` if: 114 | - [line 3] The `Role` inherits from some `Child` role, 115 | - [line 4] where that specific `Child` role has permission to do the `Action`. 116 | 117 | Now we can correctly get all the permitted actions for a given role, and also query them regarding Alice and the boxing club: 118 | 119 | ```prolog 120 | ?- role_has_permission(moderator, A). 121 | A = ban_user ; 122 | A = ban_protection ; 123 | false. 124 | 125 | ?- role_has_permission(admin, A). 126 | A = promote_to_mod ; 127 | A = ban_user ; 128 | A = ban_protection ; 129 | false. 130 | 131 | ?- role(alice, boxing, R), role_has_permission(R, A). 132 | R = admin, 133 | A = promote_to_mod ; 134 | R = admin, 135 | A = ban_user ; 136 | R = admin, 137 | A = ban_protection ; 138 | false. 139 | ``` 140 | 141 | Perfect! Now we can correctly ask if a user has a specific permission in a specific club. For example, can Alice and Bob promote other users to moderators? 142 | 143 | ```prolog 144 | ?- role(alice, boxing, R), role_has_permission(R, promote_to_mod). 145 | R = admin ; 146 | false. 147 | 148 | ?- role(bob, boxing, R), role_has_permission(R, promote_to_mod). 149 | false. 150 | ``` 151 | 152 | The first query says "yes, alice *can* promote_to_mod because she has the admin role". The second query says "no, bob cannot" because bob has no role in the boxing club that also has the `promote_to_mod` permission. 153 | 154 | ### A Quick Abstraction 155 | 156 | In Prolog, it's ideal to encode our real-world questions as predicates. The previous query does not conform to this ideal, so let's write a new predicate to keep our code clean: 157 | 158 | ```pl 159 | user_has_permission(User, Club, Action) :- 160 | role(User, Club, Role), 161 | role_has_permission(Role, Action). 162 | ``` 163 | 164 | Note that this is the same logic as the query we just ran. Here's the explanation: 165 | 166 | A `User` has permission to do an `Action` in a `Club` if: 167 | 168 | - [line 2] The `User` has some `Role` in `Club`, 169 | - [line 3] such that `Role` has permission to do `Action`. 170 | 171 | With these three lines of code, we can now get a yes/no answer to the last question we asked in the previous section: 172 | 173 | ```prolog 174 | % Instead of: 175 | % role(alice, boxing, R), role_has_permission(R, promote_to_mod). 176 | % We can now write: 177 | ?- user_has_permission(alice, boxing, promote_to_mod). 178 | true ; 179 | false. 180 | 181 | % Instead of: 182 | % role(bob, boxing, R), role_has_permission(R, promote_to_mod). 183 | % We can now write: 184 | ?- user_has_permission(bob, boxing, promote_to_mod). 185 | false. 186 | ``` 187 | 188 | Much nicer! We will also reuse this predicate shortly. 189 | 190 | ## Adding Ad-hoc Permission Logic 191 | 192 | Ok, now let's update `ban_user` logic [from part 1](./permission-system-1.md#the-logic) to use our new, more scalable system, and achieve the advertised 14 lines of logic. After that, we will also write a new `promote_to_mod` action for good measure. 193 | 194 | First, the new `ban_user`: 195 | 196 | ```pl 197 | ban_user(Actor, Club, Target) :- 198 | user_has_permission(Actor, Club, ban_user), 199 | dif(Actor, Target), 200 | member(Target, Club), 201 | \+ user_has_permission(Target, Club, ban_protection). 202 | ``` 203 | 204 | So easy! If you understood part 1, then no further explanation is needed. 205 | 206 | Now let's do `promote_to_mod`: 207 | 208 | ```pl 209 | promote_to_mod(Actor, Club, Target) :- 210 | user_has_permission(Actor, Club, promote_to_mod), 211 | member(Target, Club), 212 | \+ role(Target, Club, _). 213 | ``` 214 | 215 | The only new-ish part is the last line. Logically speaking, it only passes when `Actor` **does not have a special role**. Programmatically speaking, it gets interesting. 216 | 217 | Because of the `\+` operator (remember it means "not"), Prolog first tries to **prove** `role(Target, Club, _)`. If Prolog can prove it, then `\+` flips it to false. If Prolog *can't* prove it, then `\+` will flip it to true, causing `promote_to_mod` to be true as a whole. 218 | 219 | In other words, `promote_to_mod` will only pass if `role(Target, Club, _)` is not true. The underscore `_` in that code means "something, anything, I don't care what it is, as long as something is there". So again, the code is effectively saying "fail if Target has any special role at all in Club", which is exactly what we want. 220 | 221 | **Note:** Negation is a Prolog fundamental. You have to be careful with what you negate. For example, if you write `\+ something(X)`, and `something(X)` takes a very long time to prove, then your code will be quite inefficient! Don't worry too much though; just like all languages, there are tricks you can do to get around such problems. 222 | 223 | ## DRYing things up 224 | 225 | There's a small amount of [WETness](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself#DRY_vs_WET_solutions) in our action code: each action verifies if the actor has permission to do that action! This is quite redundant, as this behavior is obviously implied in any action we write. 226 | 227 | To remove this redundancy, we will use the built-in [call](https://www.swi-prolog.org/pldoc/doc_for?object=call/2) predicate. Here's an example of using it. The following two queries are equivalent: 228 | 229 | ```prolog 230 | ?- member(alice, C). 231 | C = boxing ; 232 | C = chess. 233 | 234 | ?- call(member, alice, C). 235 | C = boxing ; 236 | C = chess. 237 | ``` 238 | 239 | (For functional programmers, this is similar to a higher-order function. For JavaScripters, this is like Function.prototype.call. For lispers, there's even more to get excited about in Prolog 😉). 240 | 241 | `call` is a very useful tool to learn (and use sparingly). It allows us to use an atom as both a **value** and a **predicate**. Behold: 242 | 243 | ```pl 244 | can(Actor, Club, Action, Target) :- 245 | user_has_permission(Actor, Club, Action), 246 | call(Action, Actor, Club, Target). 247 | 248 | % 249 | % New action code! 250 | % Notice how we removed the first line of each predicate. 251 | % 252 | ban_user(Actor, Club, Target) :- 253 | dif(Actor, Target), 254 | member(Target, Club), 255 | \+ user_has_permission(Target, Club, ban_protection). 256 | 257 | promote_to_mod(_Actor, Club, Target) :- 258 | member(Target, Club), 259 | \+ role(Target, Club, _). 260 | ``` 261 | 262 | and its usage: 263 | 264 | ```prolog 265 | ?- can(alice, boxing, ban_user, carly). 266 | true ; 267 | false. 268 | 269 | ?- can(alice, boxing, ban_user, bob). 270 | false. 271 | ``` 272 | 273 | Wonderful! But how does it work? As long as you understand how `call` works, then the code is straightforward. The key here is realizing `Action` is used as both a value (in `user_has_permission()`) and a predicate (in `call()`). 274 | 275 | **Heavy note:** `call` will run literally anything, and SWI Prolog has the capability to do sensitive things like read and write files. If you're putting this code on the web, BE SURE TO SANITIZE YOUR INPUTS! 276 | 277 | ## Conclusion 278 | 279 | And there you have it! A beautiful and scalable roles & permissions system in less than 20 lines of Prolog. 280 | 281 | In those few lines of code, we were able to: 282 | 283 | - Decoupled roles and permissions 284 | - Implement role inheritance 285 | - Cleanly encoded real-world questions into individual predicates 286 | - Remove redundancy using the higher-order predicate `call` 287 | - Build it in such a way that is super easy to extend! 288 | 289 | The next post in this series will extend our system with **error messages**, allowing the system to know **why** a user's action was rejected. 290 | 291 | ## Full Code 292 | 293 | ```prolog 294 | % 295 | % List of facts (also known as "the database") 296 | % 297 | member(alice, boxing). 298 | member(bob, boxing). 299 | member(carly, boxing). 300 | member(dan, boxing). 301 | 302 | member(alice, chess). 303 | member(bob, chess). 304 | 305 | role(alice, boxing, admin). 306 | role(bob, boxing, moderator). 307 | role(bob, chess, moderator). 308 | 309 | role_inherits(admin, moderator). 310 | 311 | permission(admin, promote_to_mod). 312 | permission(moderator, ban_user). 313 | permission(moderator, ban_protection). 314 | 315 | % 316 | % Role & Permissions logic 317 | % 318 | role_has_permission(Role, Action) :- permission(Role, Action). 319 | role_has_permission(Role, Action) :- 320 | role_inherits(Role, Child), 321 | role_has_permission(Child, Action). 322 | 323 | user_has_permission(User, Club, Action) :- 324 | role(User, Club, Role), 325 | role_has_permission(Role, Action). 326 | 327 | % 328 | % A dash of metaprogramming for good software design 329 | % 330 | can(Actor, Club, Action, Target) :- 331 | user_has_permission(Actor, Club, Action), 332 | call(Action, Actor, Club, Target). 333 | 334 | % 335 | % Action-specific validations 336 | % 337 | ban_user(Actor, Club, Target) :- 338 | dif(Actor, Target), 339 | member(Target, Club), 340 | \+ user_has_permission(Target, Club, ban_protection),. 341 | 342 | promote_to_mod(_Actor, Club, Target) :- 343 | member(Target, Club), 344 | \+ role(Target, Club, _). 345 | ``` 346 | -------------------------------------------------------------------------------- /assets/prologue-to-prolog.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheClause/learn-prolog/e2062875913d7346787e1dfe1c61fb2990664d2b/assets/prologue-to-prolog.pdf --------------------------------------------------------------------------------