├── LICENSE
├── README.md
├── cloud-database.md
├── clp-pl.md
├── code-transformation.md
├── command-line-options.md
├── currency.md
├── data-structures.md
├── determinism-assertions.md
├── diet.md
├── examples.md
├── fsm.md
├── infer-io.md
├── infer-types.md
├── jquery.md
├── lint.md
├── maps.md
├── mercury-compatibility.md
├── mmap.md
├── mode-indicators.md
├── mode-line.md
├── mode-rewrite.md
├── os-detection.md
├── partial-evaluation.md
├── patches.md
├── protobuf.md
├── semver.md
├── smartmatch.md
├── state-variable.md
├── summary_stats.md
├── switch.md
├── template.md
├── text.md
├── uri.md
├── web-framework.md
├── whitespace-syntax.md
└── yaml.md
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | prolog_library_ideas
2 | ====================
3 |
4 | Random ideas about libraries for Prolog
5 |
6 | See each `.md` file for details. Once an idea has been implemented, I delete the file because it's visible on my [list of packs](http://www.swi-prolog.org/pack/list?author=michael@ndrix.org&sort=downloads).
7 |
8 | There are also many good ideas and implementations at [The public-domain Prolog library](http://www.j-paine.org/prolog/library.html). Many of those would be excellent to release as packs or build on for packs (with permission, of course).
9 |
--------------------------------------------------------------------------------
/cloud-database.md:
--------------------------------------------------------------------------------
1 | # Cloud Storage for Terms
2 |
3 | I want to be able to store 1st-order terms in the cloud (massive scale, shared across apps) as if I were storing them in a local Prolog module. For example, module `foo` is defined somewhere as a cloud module. Then
4 |
5 | ```prolog
6 | foo:assert(things(that,matter,to,michael)),
7 | foo:assert(things(that,matter,to,kinsey)),
8 | foo:things(that,matter,to,Whom).
9 |
10 | % Whom = michael ;
11 | % Whom = kinsey .
12 | ```
13 |
14 | Behind the scenes, each assert stores all necessary [path indexes](https://drive.google.com/file/d/0B25G2sSG-_Mvd2dETlp2VjNyTGM/view?usp=sharing) for the term in [datastore](https://developers.google.com/appengine/docs/go/datastore/reference). The term is an entity. Each path is stored in a list property on that entity. For a query, we can use [datastore's merge join](http://youtu.be/AgaL6NGpkB8?t=23m) to efficiently calculate the set intersections across the necessary path indexes. There may be a way to have it handle set unions too. Otherwise, we could do it in memory.
15 |
16 | # UI
17 |
18 | The key idea is that one should interact with the cloud data as if it were stored locally in a normal Prolog module. Caching is handled transparently (locally and in memcache). When defining a module that operates this way, there's probably a predicate which defines the cache TTL given a term in that module.
19 |
20 | # API
21 |
22 | The cloud term storage should have a very simple API that's just a partially instantiated term like `foo(_,A,hi)` and responds with JSON like `[{"A":"bar"},{"A":"baz"}]`. That way it can be used from languages other than Prolog.
23 |
--------------------------------------------------------------------------------
/clp-pl.md:
--------------------------------------------------------------------------------
1 | # clp(pl)
2 |
3 | A constraint logic programming library in which the constraints are arbitrary Prolog goals. The library accumulates those goals in attributed variables. It uses partial evaluation to simplify and specialize the constraints based on the current evaluation. Constraint goals are reordered or executed to assist in the simplification. Unifications are propagated through the constraints as they become available. Labeling just executes the accumulated, simplified goal to generate solutions.
4 |
5 |
6 | ## Playground
7 |
8 | A place to play around with some examples
9 |
10 | ### Small list
11 |
12 | For example, this code runs out of stack before finding the two valid answers.
13 |
14 | ```prolog
15 | foo(L) :-
16 | length(L,N),
17 | N < 4,
18 | nth1(2,L,b).
19 | ?- findall(L,foo(L),Ls).
20 | ```
21 |
22 | If `foo/1` is called with an unbound variable, we might make the following observations:
23 |
24 | * because `length/2` produces nonneg `N`, add goal `N >= 0` after `length/2` call
25 | * `N >= 0` and `N < 4` goals combine to `between(0,3,N)`
26 | * notice that `length/2` with two variables produces infinite solutions but `between(0,3,N)` produces only 4, move the `between/3` goal upwards (more deterministic goals should be as early as possible)
27 | * because `nth1(2,L,b)` produces a single solution, move it to the very top
28 |
29 | So it's as if the code were originally:
30 |
31 | ```prolog
32 | foo(L) :-
33 | nth1(2,L,b),
34 | between(0,3,N),
35 | length(L,N).
36 | ```
37 |
38 | If we called `foo([A,B])` we'd make different observations:
39 |
40 | * propagate the binding for `L` through the goals
41 | * realize that `length([A,B],N)` can be executed to become `N=2`
42 | * realize that `N=2` is a unification and can be propagated through the goals
43 | * realize that `2<4` can be replaced with `true` which can be removed
44 | * realize that `nth1(2,[A,B],b)` can be executed to become `B=b`
45 |
46 | So it's as if the code were originally:
47 |
48 | ```prolog
49 | foo([_,b]).
50 | ```
51 |
52 | In each case, we get the correct answer and I was able to write the Prolog code without regard to the order in which each goal (constraint) was executed.
53 |
54 | #### Linear logic
55 |
56 | I noticed an interesting use for linear logic in the simplifications above. Assume that the body of a clause is a series of conjunctions of pure (no side effects) goals. Then each goal can be viewed as a resource. We have the following resources (assuming variables share across resources):
57 |
58 | ```prolog
59 | length(L,N).
60 | N < 4.
61 | nth1(2,L,b).
62 | ```
63 |
64 | Then the simplifications in the first example can be written as linear logic rules roughly like this:
65 |
66 | ```prolog
67 | length(_,N) -o length(_,N) * N >= 0.
68 | N>=L * N0} -o {call(nth1(N,L,E))} * L=[...].
77 | ```
78 |
--------------------------------------------------------------------------------
/code-transformation.md:
--------------------------------------------------------------------------------
1 | # Code Beautifier and Transformer
2 |
3 | Similar to `Perl::Tidy` or `gofmt`, accept some Prolog code on standard input and print beautified Prolog in a consistent format to standard output. It should apply popular formatting conventions, indenting conventions and whitespace conventions. Tools like this are helpful on large projects to avoid conflict among developers over formatting style. It also makes automated code transformation tools more effective (automated changes aren’t drowning in code formatting changes).
4 |
5 | An automated code transformation tool could be a natural component of this (see `gofmt -r`). It would essentially be a `term_expansion/2` or `goal_expansion/2` macro expander than operates on files when demanded instead of on code during every compile.
6 |
--------------------------------------------------------------------------------
/command-line-options.md:
--------------------------------------------------------------------------------
1 | # Command line option parsing
2 |
3 | SWI-Prolog has at least two ways to process command line options:
4 |
5 | * [library(optparse)](http://www.swi-prolog.org/pldoc/doc/swi/library/optparse.pl)
6 | * [argv_options/3](http://www.swi-prolog.org/pldoc/doc_for?object=http_unix_daemon%3Aargv_options/3)
7 |
8 | The first library has the user define a schema for the command line options and also generates automated documentation. The second library just converts `--name=value` pairs into `name(value)` terms. Both libraries make the parsed options available as a list. Lists are convenient, but difficult to query and reason with.
9 |
10 | My ideal command-line parsing library would have the following features:
11 |
12 | * convert command-line arguments into predicates in an `argv` module (or whatever module the user prefers)
13 | * allow, but not require, an argument schema
14 | * work with an arbitrary list of atoms (not just the program's argv)
15 |
16 | For example, a command line like this
17 |
18 | ```
19 | -v --size=27 --follow-redirects
20 | ```
21 |
22 | might create a module like this
23 |
24 | ```prolog
25 | :- module(argv, []).
26 |
27 | v.
28 |
29 | size(27).
30 |
31 | follow_redirects.
32 | ```
33 |
34 | which allows one to write code like:
35 |
36 | ```prolog
37 | main :-
38 | warn("starting program"),
39 | ( argv:size(Size), Size<99 -> true; Size=42 ),
40 | format("You asked for size=%d", [Size]).
41 |
42 | warn(String) :-
43 | ( argv:v -> writeln(String); true ).
44 | ```
45 |
--------------------------------------------------------------------------------
/currency.md:
--------------------------------------------------------------------------------
1 | # Currency
2 |
3 | Make [library(currency)](https://gist.github.com/mndrix/2224422adc4913e715809245a6b02b3a) (from PriceCharting) available as open source. Teach it how to support different currencies based on their symbol (leading or trailing), separator (comma or full stop), and the number of decimals (Dollars, Yen and Bitcoin differ). Perhaps these currency details should be configured via a hook. So you'd end up with something like this:
4 |
5 | ```prolog
6 | % define(Iso4217, Symbol, SymbolPosition, Thousands, Fraction, FractionSize)
7 | :- multifile currency:define/6.
8 | currency:define(usd, '$', leading, ',', '.', 2).
9 | currency:define(jpy, '¥', leading, ',', '', 0).
10 | currency:define(btc, 'BTC', trailing, ',', '.', 4).
11 |
12 | ?- atom_currency('$12.34', U).
13 | U = usd(1234).
14 |
15 | ?- atom_currency(Y, jpy(1234)).
16 | Y = '¥1234'.
17 |
18 | ?- atom_currency(B, btc(1234)).
19 | B = '0.1234 BTC'.
20 | ```
21 |
22 | There should be variants for other text types: `string_currency/2` and `codes_currency/2`. There should also be a DCG equivalent: `dcg_currency//1`.
23 |
24 | Should we also specify a pip size? That might be helpful for currency traders.
25 |
--------------------------------------------------------------------------------
/data-structures.md:
--------------------------------------------------------------------------------
1 | # Logical Data Structures
2 |
3 | Create a library with various data structures having nice, logical implementations. Data structures worth including might be:
4 |
5 | * queue
6 | * stack
7 | * dequeue
8 | * priority queue
9 | * set
10 | * map (probably as a tree or HAMT)
11 |
12 | As with my [map](maps.md) idea, these should be defined in terms of generic interfaces on top of multiple (if necessary) implementations.
13 |
14 | See _The Craft of Prolog_ by O’Keefe for an example of a queue. The biggest trick for each structure is finding a nice, logical, reversible interface describing the true relation between the data structure, its inputs and its outputs.
15 |
--------------------------------------------------------------------------------
/determinism-assertions.md:
--------------------------------------------------------------------------------
1 | # Determinism Calling Assertions
2 |
3 | ## By the Caller
4 |
5 | When calling a predicate, one might make certain assumptions about its determinism. Sometimes I’d like to declare those assumptions to help future readers and to get runtime errors if the assumption proves wrong. For example, in the following code `husband_wife(+A,-B) is det`.
6 |
7 | ```prolog
8 | husband_wife(bob, jane).
9 | husband_wife(tom, sue).
10 | ```
11 |
12 | A caller working with that assumption might write one of the following (alternative syntaxes):
13 |
14 | ```prolog
15 | call_det(husband_wife(bob,Wife)).
16 | det husband_wife(bob, Wife).
17 | husband_wife(bob, Wife) is det.
18 | ```
19 |
20 | In each case, he declares his determinism expectation. If the call fails or succeeds with a choicepoint, a helpful error is thrown during development. Mercury’s determinism system is a good model to follow.
21 |
22 | An implementation of `call_semidet/1` from the SWI-Prolog mailing list is:
23 |
24 | ```prolog
25 | call_semidet(Goal) :-
26 | call_cleanup(Goal, Det=true),
27 | ( Det == true ->
28 | true
29 | ; % left choicepoints ->
30 | throw(error(mode_error(det,Goal),_))
31 | ).
32 | ```
33 |
34 | ## By the Developer
35 |
36 | When developing a predicate, I might like to declare a predicate’s determinism and have a tool verify that my declaration is accurate. It probably makes sense to extract determinism declarations from pldoc comments since the data is already there in a machine-readable format. For example, the tool should fail this code because `husband_wife(bob,Wife)` has multiple answers:
37 |
38 | ```prolog
39 | %% husband_wife(+Husband, -Wife) is det
40 | husband_wife(bob, tina).
41 | husband_wife(bob, sue).
42 | husband_wife(tom, pam).
43 | ```
44 |
45 | Using Determinism Calling Assertions (above), this could be implemented as a macro expanding to
46 |
47 | ```prolog
48 | husband_wife(A, B) :-
49 | ground(A),
50 | var(B),
51 | !,
52 | call_det(husband_wife_aux(A,B)).
53 | husband_wife_aux(bob, tina).
54 | husband_wife_aux(bob, sue).
55 | husband_wife_aux(tom, pam).
56 | ```
57 |
--------------------------------------------------------------------------------
/diet.md:
--------------------------------------------------------------------------------
1 | # "Functional Pearls: Diets for Fat Sets" by Martin Ewig
2 |
3 |
4 | This would make a great Prolog library for any data type that can define a relation like `succ/2`. Integers are the most obvious application. Union and intersection can be implemented using the obvious insert, size and traversal predicates.
5 |
6 |
--------------------------------------------------------------------------------
/examples.md:
--------------------------------------------------------------------------------
1 | # Example-driven Development
2 |
3 | Prolog is well-suited to what I think of as example-driven development. A predicate represents a true relation among some variables. By giving examples and counter examples, we get:
4 |
5 | * documentation
6 | * simple automated tests
7 | * simplistic type inference
8 | * simplistic mode inference
9 | * steadfastness tests
10 |
11 | ## Examples
12 |
13 | Let's consider an implementation for `length/2`:
14 |
15 | ```prolog
16 | length(L,N) :-
17 | length(L,0,N).
18 |
19 | length([],N,N).
20 | length([_|L],N0,N) :-
21 | succ(N0,N1),
22 | length(L,N1,N).
23 | ```
24 |
25 | We might declare examples like this:
26 |
27 | ```prolog
28 | :- examples
29 | length([],0),
30 | length([a,b],2),
31 | length([_],1).
32 | :- counter_examples
33 | length([1,2,3],7).
34 | ```
35 |
36 | ## Documentation
37 |
38 | By displaying the examples in HTML documentation, users get a good idea how the predicate should be used. A few accurate, well chosen examples are often more effective at communicating a predicate's purpose than paragraphs of natural language text. Programmers are pretty good at exptrapolating a few examples into a mental model.
39 |
40 | ## Automated Tests
41 |
42 | Each example can be executed as a goal. If it succeeds, consider it a passing test. Counter examples, when executed as goals, should fail. This gives us assurance that our example-based documentation never deviates from the predicate's true behavior.
43 |
44 | ## Simplistic Type Inference
45 |
46 | The predicate arguments shown in the examples allow us to perform primitive type inference. In the examples above, the first argument has values `[]`, `[a,b]`, `[_]` and `[1,2,3]`. By executing code like this, we can infer possible types:
47 |
48 | ```prolog
49 | ?- dif(T,impossible),
50 | foreach( member(V,[[],[_],[a,b],[1,2,3]])
51 | , error:has_type(T,V)
52 | ).
53 | T = any ;
54 | T = acyclic ;
55 | T = nonvar ;
56 | T = proper_list ;
57 | T = list ;
58 | T = list_or_partial_list ...
59 | ```
60 |
61 | We can choose among the possible types with various heuristics. Alternatively, the most precise type among the options is `proper_list`. We can calculate "most precise" with something roughly like this:
62 |
63 | ```prolog
64 | more_precise_than(T1, T2) :-
65 | forall( between(1,1000,_)
66 | , ( quickcheck:arbitrary(T1,Val)
67 | , error:has_type(T2,Val)
68 | )
69 | ),
70 | asserta(more_precise_than(T1,T2) :- !).
71 | ```
72 |
73 | `more_precise_than/2` gives us a partial order. By using a [topological sort](http://en.wikipedia.org/wiki/Topological_sorting) we can pick some types as the most precise. A [handy video demo](http://www.showme.com/sh/?h=qovixPc).
74 |
75 | ### type_value/2
76 |
77 | The predicates `error:has_type/2` and `quickcheck:arbitrary/2` suggest a more general relation: `type_value/2`. It would have the following modes:
78 |
79 | ```prolog
80 | type_value(+Type,+Value) is semidet.
81 | type_value(+Type,-Value) is det. % at random
82 | type_value(-Type,+Value) is multi.
83 | ```
84 |
85 | ## Simplistic Mode Inference
86 |
87 | A predicate's modes communicate three related details about argument instantiation and a predicate's solutions:
88 |
89 | * which arguments must start as `nonvar`
90 | * which initially `var` arguments will finish as `nonvar`
91 | * how many solutions are likely
92 |
93 | By executing each example in every possible combination of var/nonvar arguments, we get a table like this:
94 |
95 | ```
96 | length([],0) 1 step, 0 extra : ++ is det
97 | length(_,_) 1 step, n extra : -- is multi
98 | length([],_) 1 step, 0 extra : +- is det
99 | length(_,0) 1 step, 0 extra : -+ is det.
100 |
101 | length([a,b],2) 1 step, 0 extra : ++ is det
102 | length(_,_) 3 step, n extra : -- is multi
103 | length([a,b],_) 1 step, 0 extra : +- is det
104 | length(_,2) 1 step, 0 extra : -+ is det.
105 |
106 | length([_],1) 1 step, 0 extra : ++ is det
107 | length(_,_) 2 step, n extra : -- is multi
108 | length([_],_) 1 step, 0 extra : +- is det
109 | length(_,1) 1 step, 0 extra : -+ is det.
110 |
111 | % counter example
112 | length([1,2,3],7) 1 step, 0 extra : ++ is semidet
113 | length(_,_) 7 step, n extra : -- is multi
114 | length([1,2,3],_) 1 step, 0 extra : +- is det
115 | length(_,7) 1 step, 0 extra : -+ is det.
116 | ```
117 |
118 | For a predicate of arity N, there are 2^N possible modes. Each mode for which all examples and counterexamples behave as expected is a legitimate mode for this predicate. For each mode, choose the determinism with the most solutions. For length/2, that leads to the following inferred modes:
119 |
120 | ```prolog
121 | length(+,+) is semidet.
122 | length(+,-) is det.
123 | length(-,+) is det.
124 | length(-,-) is multi.
125 | ```
126 |
127 | If one of these modes is incorrect, the user can provide an example or counter example to show the discrepancy. The inference algorithm can then come to the right conclusion.
128 |
129 | ## Steadfastness Tests
130 |
131 | ["Steadfastness basically means that you cannot force a predicate down the wrong path by filling in output arguments wrongly"](http://permalink.gmane.com/gmane.comp.lang.ai.prolog.swi/9785). Based on the inferred modes, fill in some output arguments "wrongly" (by giving them a `nonvar` value) and make sure the example still works.
132 |
--------------------------------------------------------------------------------
/fsm.md:
--------------------------------------------------------------------------------
1 | # Finite State Machines
2 |
3 | Make a small library for describing and working with finite state machines. I want as little syntactic overhead as possible. States are just atoms. Transitions are just atoms. Perhaps it looks something like this, with a single predicate defining a machine.
4 |
5 | ```prolog
6 | %% light_switch(+Event, +CurrentState -NextState) is det.
7 | %
8 | % Define a finite state machine named "light_switch". This predicate defines
9 | % the states, the events and the transitions.
10 | light_switch(flip, on, off).
11 | light_switch(flip, off, on).
12 | ```
13 |
--------------------------------------------------------------------------------
/infer-io.md:
--------------------------------------------------------------------------------
1 | # Infer IO Usage
2 |
3 | Infer whether a predicate does IO (prints to screen, opens a file, sleeps, etc). All foreign predicates are considered to do IO (except for a whitelisted group of exceptions). This whitelist must be hardcoded as facts in the library. For other predicates, they perform IO if one of their subgoals performs IO. This should be an easy inference rule to implement recursively.
4 |
5 | This can be useful for static analysis. It could also be useful for running untrusted Prolog code. It’s not perfect since one can build atoms from arbitrary data and then execute those atoms. To be truly safe, the underlying Prolog engine must provide something like `call_without_io(...)` but this inference could cover many cases.
6 |
7 | `library(sandbox)` does something similar. Investigate there before writing any code.
8 |
--------------------------------------------------------------------------------
/infer-types.md:
--------------------------------------------------------------------------------
1 | # Type Inference
2 |
3 | It can be helpful for documentation and debugging and compile-time error detection to be able to infer the types of variables in goals. Prolog has a great system for describing types based on `error:has_type/2`. This predicate simply recognizes values which belong to the type in question. Types are defined as the conjunction and disjunction of Prolog goals (a boolean algebra) and boolean algebra is a distributive lattice. That means we can learn types and join them with other learned types to obtain a supertype.
4 |
5 | This library has a predicate something like `infer_types(+Goal, -Types:list(pair))`. Running the predicate on a goal gives you a list of variables and their associated types. The types can be named (like `atom`) or compound (like `integer(N), 0 =:= N mod 2`). For example,
6 |
7 | ```prolog
8 | atom_length(Atom, Length) :-
9 | atom_codes(Atom, Codes),
10 | length(Codes, Length).
11 | ?- infer_types(atom_length(A,N), Ts).
12 | Ts = [A=atom, N=integer].
13 | ```
14 |
15 | Calculating the type of a conjunction is calculating the types of variables in the first goal and the types of variables in the second goal. Then join those two type inferences to produce the type inference of the conjunction.
16 |
17 | We could start with simple boolean algebra join rules (assume `v` is join) such that
18 |
19 | ```prolog
20 | nonvar(X) v atom(X)
21 | ```
22 |
23 | is
24 |
25 | ```
26 | nonvar(X),
27 | atom(X).
28 | ```
29 |
30 | but later specialize the join rules so that it’s just `atom(X)`. That’s because an atom is a more specialized case of nonvar.
31 |
--------------------------------------------------------------------------------
/jquery.md:
--------------------------------------------------------------------------------
1 | # jQuery-like API
2 |
3 | The [jQuery](http://api.jquery.com/) library makes it easy to search and modify HTML documents. The API has been [ported to Go](https://github.com/PuerkitoBio/goquery) where it's proven equally helpful. It could be helpful to have the API available in Prolog too.
4 |
5 | ## Start with CSS matching
6 |
7 | A good place to start would be to implement a CSS selector library. That's the foundation of all good jQuery knock offs.
8 |
--------------------------------------------------------------------------------
/lint.md:
--------------------------------------------------------------------------------
1 | # Lint Tool
2 |
3 | Make a lint-like tool for spotting suspicious Prolog constructs. See examples section below.
4 |
5 | According to [this thread](http://computer-programming-forum.com/55-prolog/514e80369d8e92ea.htm), a tool called nit is available as part of [NU-Prolog](http://ww2.cs.mu.oz.au/~lee/src/nuprolog/). Perhaps it could be ported to SWI-Prolog or ideas taken from it.
6 |
7 | There’s an existing SWI-Prolog linting tool called [BLint](http://www.fing.edu.uy/~gbrown/prolog/blint.html). It could probably be packaged up as a pack and made usable.
8 |
9 | This could probably be implemented via `library(prolog_codewalk)`. By loading the target code into memory, walking every clause, reporting on suspicious patterns.
10 |
11 | I’d love to integrate this tool into SWI-Prolog’s `make/0` command. It already does undefined predicate checks and runs tests. It’d be a helpful place to report on other suspicious constructs.
12 |
13 | In an unrelated email thread, Richard A. O’Keefe said,
14 |
15 | > The thing I dream of for Prolog is something like PLT Scheme (now Racket) or clang --analyze, something that helps me write *working* code.
16 |
17 | That really is the emphasis of this lint tool. Help developers get from where they are to working code, without having to write and run a bunch of tests.
18 |
19 | Erlang’s success typing is another great place to look. It only warns of problems when it can prove that you did something wrong. If it can’t prove it, it stays silent.
20 |
21 | It’s worth considering whether the lint tool should work at the syntactic level or at the code level (aka “post-macro” level). SWI-Prolog’s semantic singletons already do some good analysis at the code or compiler level. Maybe there’s space for a syntactic linter. We could allow library authors to define additional lint rules based on correct usage for their library. For example, it’s typically an error to call `delay/1` before `!/0` in a clause. The linter shouldn’t have to hard-code that rule, but because of macro expansion it’s hard to detect it after macros have been expanded. Performing checks at the syntactic level makes both aspects easier, I think.
22 |
23 | ## Examples
24 |
25 | Some small examples:
26 |
27 | * mismatch between `format/2` template and number of arguments
28 | * variables in a term with suspiciously similar names (sans trailing numbers)
29 | * report on undefined predicates
30 | * ! in multifile predicates
31 |
32 | ### `forall/2` loop variables
33 |
34 | The first argument of `forall/2` must have at least one variable that's local to the forall/3. For example, this is almost certainly mistake eventhough the goal succeeds:
35 |
36 | ```prolog
37 | N = 3,
38 | forall( between(1,7,N), writeln(N) )
39 | ```
40 |
41 | The developer probably intended:
42 |
43 | ```prolog
44 | N = 3,
45 | forall( between(1,7,I), writeln(I) )
46 | ```
47 |
48 | ### `bagof/3` and ^ variables
49 |
50 | #### Free variables must exist inside
51 |
52 | If `bagof/3` or `setof/3` has a second argument describing free variables (with `^/2`) each of those free variables must appear somewhere inside the goal:
53 |
54 | ```prolog
55 | X = hi,
56 | bagof(Y, X^foo(Z,Y), Ys)
57 | ```
58 |
59 | The developer probably intended:
60 |
61 | ```prolog
62 | X = hi,
63 | bagof(Y,Z^foo(Z,Y), Ys)
64 | ```
65 |
66 | #### Non-escaping variables must be declared
67 |
68 | If `bagof/3` or `setof/3` have variables that are used in the second argument, not mentioned in the first argument and not mentioned outside the goal, those variables must be declared as free variables using `^/2`:
69 |
70 | ```prolog
71 | Start = 1,
72 | End = 7,
73 | bagof(Ls,(mature_loans_by_day_cache(Dt,Ls),Start= writeln(elway),
7 | 42 -> writeln(answer),
8 | (alpha; beta) -> writeln(greek_letter),
9 | between(1,6) => writeln(bar)
10 | ].
11 | ```
12 |
13 | becomes
14 |
15 | ```prolog
16 | compiled_switch_1234(7) :-
17 | !,
18 | writeln(elway).
19 | compiled_switch_1234(42) :-
20 | !,
21 | writeln(answer).
22 | compiled_switch_1234(A) :-
23 | ( (A=alpha;A=beta) -> writeln(greek_letter)
24 | ; between(1,6,A) -> writeln(bar)
25 | ; fail
26 | ).
27 |
28 | ...,
29 | compiled_switch_1234(X).
30 | ```
31 |
32 | Consider desugaring to something that uses the [smartmatch operator](smartmatch.md) so that one can match against patterns supplied by libraries. Also consider [Python’s switch proposal](http://www.python.org/dev/peps/pep-3103/) for ideas.
33 |
34 | Ideally, switch cases where we’re just doing unification would be optimized by automatically creating an intermediate predicate. That gives us really good performance by leveraging Prolog’s first term index.
35 |
36 | If cuts inside the switch statement are broken, generate a compile time error saying so. That way it doesn’t surprise users.
37 |
--------------------------------------------------------------------------------
/template.md:
--------------------------------------------------------------------------------
1 | # Templating library
2 |
3 | I want a Prolog templating library with the following features:
4 |
5 | * compile templates from quasiquotes into a DCG
6 | * loops over lists or backtracked solutions
7 | * helpful defaults to handle common text generation use cases
8 |
9 | Most templating libraries think of a template as a way to generate text. I believe it's more helpful to think of a template as a way to generate code which generates text. In Prolog, code that generates text is often done the most naturally with DCGs. So a Prolog template should be a way to easily generate DCGs.
10 |
11 | For example, this quasiquoted template
12 |
13 | ```text
14 | {|template(hello)||Hello {{name}}.|}
15 | ```
16 |
17 | might generate code like
18 |
19 | ```prolog
20 | hello(X) -->
21 | "Hello ",
22 | template:value(X, name),
23 | ".".
24 |
25 | % library code
26 | template:value(X,Field) -->
27 | { is_dict(X), get_dict(Field,X,Y) },
28 | template:textual(Y),
29 | !.
30 | template:value(X,Field) -->
31 | call(Field,X),
32 | !.
33 | template:value(X,Field) -->
34 | { call(Field,X,Y) },
35 | template:textual(Y),
36 | !.
37 |
38 | % template:textual//1 is a multifile predicate defining a
39 | % textual representation for atoms, numbers, strings, etc.
40 | ```
41 |
42 | ## Syntax
43 |
44 | One option would be to adopt the syntax and keywords of an existing template language like [mustache](https://mustache.github.io/mustache.5.html) or [Go's template](https://golang.org/pkg/text/template/). Another option would be to make a template library that's designed from the ground up with Prolog in mind. For the first templating package, it might be best to borrow syntax and semantics from an existing package. That would keep me focused on quasiquote and DCG implementation rather than deciding which features are useful for a templating system.
45 |
--------------------------------------------------------------------------------
/text.md:
--------------------------------------------------------------------------------
1 | # Generic string library
2 |
3 | I want a generic, consistent interface to string data. The library should provide an interface across all SWI-Prolog's different string representations:
4 |
5 | * atoms
6 | * lists of codes
7 | * strings
8 |
9 | Start by implementing the interface for these representations. Then implement it for other text data structures.
10 |
11 | Or maybe I just standardize on SWI-Prolog's built in string type and build everything else around that. In that case, this library becomes a massive collection of useful string predicates.
12 |
13 | ## Predicates
14 |
15 | The library should have the following predicates (and many more):
16 |
17 | * `is_text(+Text) is semidet` - true if Text a valid text representation
18 | * `text_atom(Text:text,Atom:atom)`
19 | * `text_codes(Text:text,Codes:list(nonneg))`
20 | * `text_string(Text:text,String:string)`
21 | * `text_length(+Text:text, -Length:nonneg)`
22 | * see [Haskell's Data.Text](http://hackage.haskell.org/package/text-1.2.2.1/docs/Data-Text.html) for ideas
23 | * see [Go's strings](https://golang.org/pkg/strings/) for predicate ideas
24 | * ...
25 |
26 |
27 | ## Typeclasses, interfaces, multifile predicates, oh my!
28 |
29 | The predicates defined in this library are all `multifile` so that third-party libraries can add their own string-like data structures. Many of these predicates can have default implementations that are implemented in terms of others. For example, we might have something hypothetical like:
30 |
31 | ```prolog
32 | text_length(Text,Length) :-
33 | text_codes(Text,Codes),
34 | length(Codes,Length).
35 | ```
36 |
37 | An implementation of this interface should be able to implement `text_length/2` directly or implement the dependencies and rely on the default implementation. This is similar to the way that Haskell typeclasses work and it's really handy.
38 |
39 | Prolog doesn't have a well-defined convention for typeclass-like constructs. The closest thing is `multifile` predicates, but that requires each implementation to be aware of dangling choicepoints, etc. I'd like something more declarative:
40 |
41 | * a typeclass can define which predicates are part of its interface
42 | * a module can declare
43 | * that a specific value meets that interface
44 | * which predicates on that value implement which interface methods
45 | * the typeclass module chooses an implementation based on these declarations and the default implementations
46 |
47 |
48 | ## String representations
49 |
50 | Fill this section with data structures that would make good string implementations in Prolog. [Ropes](https://en.wikipedia.org/wiki/Rope_(data_structure)) come to mind, but I don't know much about them.
51 |
--------------------------------------------------------------------------------
/uri.md:
--------------------------------------------------------------------------------
1 | # URI library
2 |
3 | SWI Prolog comes with [library(uri)](http://www.swi-prolog.org/pldoc/doc/swi/library/uri.pl). It has all the right pieces for a URI library, but has some weaknesses:
4 |
5 | * Pieces missing from a URI are represented as unbound variables. This confounds the idea of "unknown" with "known to be absent".
6 | * Too much code is needed for common relations on URIs (accessing the hostname of an HTTP URI, etc).
7 | * No support for scheme-specific relations. For example, `tag` and `data` URIs have specialized operations.
8 | * No support for adding support for more URI schemes as they come along
9 | * Requires a separate library for URI quasiquotations
10 | * Uses atoms rather than strings internally (minor)
11 |
12 | I'd like a single, high level URI library that addresses all these concerns.
13 |
14 |
15 | # Possible library names
16 |
17 | The obvious name, `uri`, is already taken. Perhaps [urial](https://en.wikipedia.org/wiki/Urial) or [uriel](https://en.wikipedia.org/wiki/Uriel) would be appropriate.
18 |
--------------------------------------------------------------------------------
/web-framework.md:
--------------------------------------------------------------------------------
1 | # Prolog Web Framework
2 |
3 | Writing a web server in SWI Prolog requires a lot of repetitive, low level coding. I'd like something that's more declarative.
4 |
5 | ## Use Cases
6 |
7 | A collection of use cases:
8 |
9 | * dispatch requests to predicates based on unification
10 | * based on request method (`get`, `post`, `head`, etc).
11 | * based on path (`foo/Bar/baz`, notice the `Bar` variable)
12 | * easy access to request details (path, parameters, form, url, etc)
13 | * unambiguous access to request details (request as list creates ambiguity between request components)
14 | * support middleware to factor out common layers
15 | * request logging
16 | * proxy header handling
17 | * memory limits
18 | * request time limits
19 | * converting request body into a term based on `Content-Type` header
20 | * converting request parameters into terms based on the parameter name
21 | * built on a generic web server interface like
22 | * [PEP33](http://www.python.org/dev/peps/pep-0333)
23 | * [Rack](http://rack.rubyforge.org/doc/SPEC.html)
24 | * [JSGI](http://jackjs.org/jsgi-spec.html)
25 | * [PSGI](http://search.cpan.org/~miyagawa/PSGI/PSGI.pod)
26 |
27 | ## HTTP Request library
28 |
29 | At the base of all these ideas is a library for parsing and representing HTTP requests. By providing a single, common representation of an HTTP request, web servers and middleware have a lingua franca allowing them to interoperate.
30 |
31 | ### Atoms or Strings?
32 |
33 | One must choose whether to represent textual content as an atom or as a string. We use an atom if the textual content is drawn from a "small" set of possible values. Otherwise, we use a string.
34 |
35 | So HTTP methods are atoms (there are only a few of them). HTTP paths components are atoms (an application may have 20-30 unique path components). HTTP header names and URL parameter names are also atoms (there may be a couple hundred of them at most).
36 |
37 | If in doubt, use a string.
38 |
39 | ### Parsing raw HTTP requests
40 |
41 | The first step is to parse a raw sequence of bytes (stream, `list(code)`, string, atom, iterator or enumerator) into a barely-structured term. The stream of bytes should probably be represented by extending `library(pure_input)` to work with byte sequences other than streams (perhaps by converting them to in-memory streams at first). That way, we can use DCG rules to handle the parsing.
42 |
43 | The "barely-structured" representation is something like this (`ll` for "low-level"):
44 |
45 | ```prolog
46 | request_ll( Method : atom
47 | , Path : string
48 | , Query : maybe(string)
49 | , Header : string
50 | , Body : string
51 | ).
52 | ```
53 |
54 | A `Body` can nearly always fit into memory. For really huge bodies, we can create a string that's backed by a mmap'd file.
55 |
56 | The lingua franca representation is a term with more structure and greater flexibility on types. Something like:
57 |
58 | ```prolog
59 | request( Method : atom
60 | , Path : list(atom)
61 | , QueryParams : multimap(atom, any)
62 | , Header : multimap(atom, any)
63 | , Body : any
64 | ).
65 | ```
66 |
67 | The speculative `multimap` type maps a single key to 0 or more values. Predicates for accessing a key's value iterates all possible values on backtracking.
68 |
69 | Here are some notes about each of those fields:
70 |
71 | * `Method` - lowercase atom like `get`, `post`, `delete`, etc.
72 | * `Path`
73 | * original path split on `/` characters
74 | * first, empty path component removed
75 | * list structure allows easy unification like `Path = [user, UserId, name]`
76 | * `QueryParams`
77 | * values are of `any` type
78 | * middleware can "inflate" a value from `string` (original type) into a type with more structure
79 | * `Header`
80 | * same rationale as `QueryParams`
81 | * `Body` - middleware may inflate to a type with more structure
82 |
83 | ## Middleware
84 |
85 | I imagine that all middleware acts as if it were one giant DCG:
86 |
87 | ```prolog
88 | apply_middleware(Request0, Request) -->
89 | first_middleware,
90 | second_middleware,
91 | third_middleware,
92 | ...
93 | final_middleware,
94 | !,
95 | unify_current_state_with(Request).
96 | ```
97 |
98 | Each middleware may leave choicepoints and they remain until we finally find a request structure that can satisfy them all. This allows a given middleware to support multiple formats for its results. For example, imagine a middleware which converts a `Date` header into one of: `get_time/1` format, `stamp_date_time/3` format, `library(julian)` format. The `Date` middleware can perform all three conversions on backtracking. A downstream middleware just acts as if the value were in the proper type. If that assumption is wrong, backtracking fixes it.
99 |
100 | For performance, the first middleware could `freeze/2` a bunch of type assertions. That way we get quick failure and avoid backtracking over a bunch of work. I suspect that good conventions will develop and backtracking will happen fairly rarely, but it'll be really valuable when it's needed.
101 |
102 | ## Hooks
103 |
104 | There can be hooks (probably handled by a middleware) for converting unstructured headers and parameters into structured ones. For example,
105 |
106 | ```prolog
107 | header_value(date, Raw, D) :- parse_time(rfc_1123, Raw, D).
108 | header_value(date, Raw, D) :- form_time(rfc_1123(Raw), D).
109 | header_value(content_type, Raw, Mime) :- parse_mimetype(Raw, Mime).
110 | ...
111 | header_value(_, Raw, raw(Raw)).
112 | ```
113 |
114 | Or maybe the backtracking on these hooks generates multiple values for a given key in the `Header` part of the request. A consumer can say `get_header(foo, Val), float(Val)` to indicate that they want a floating point representation of that header.
115 |
116 | ## Routing
117 |
118 | An essential part of web service programming is routing inbound requests to the piece of code that should handle that request. Comments above hint at this idea but I need to specify it in more detail. Fundamentally, this is a predicate mapping a request value to a closure handling that request.
119 |
120 | Consider ideas from `pack(arouter)` which addresses this same problem.
121 |
--------------------------------------------------------------------------------
/whitespace-syntax.md:
--------------------------------------------------------------------------------
1 | # Whitespace Sensitive Syntax
2 |
3 | I personally like Prolog’s syntax. It has a good mix of consistency and flexibility. Unfortunately, people love to hate it. And the comma vs full-stop problem is obnoxious in version control tools. I think it would be fairly easy to implement a whitespace sensitive syntax as a strict superset of Prolog syntax which desugars to normal Prolog.
4 |
5 | This code
6 |
7 | ```prolog
8 | append([], A, A).
9 | append([A|B], C, [A|D]) :-
10 | append(B, C, D).
11 |
12 | % naive reverse
13 | reverse([],[]).
14 | reverse([X|Xs], Zs) :-
15 | reverse(Xs, Ys),
16 | append(Ys, [X], Zs).
17 | ```
18 |
19 | might be written like this
20 |
21 | ```
22 | append [] A A
23 | append [A|B] C [A|D]
24 | append B C D
25 |
26 | % naive reverse
27 | reverse [] []
28 | reverse [X|Xs] Zs
29 | reverse Xs Ys
30 | append Ys [X] Zs
31 | ```
32 |
33 | We’re basically inferring parentheses, commas, full stop and `:-` using rules like this:
34 |
35 | * line starting in column 0 is a clause head
36 | * adjacent lines starting in column 0 imply a full stop ending the first line
37 | * line indented more than the one before implies :- ending the first line
38 | * adjacent lines starting beyond column 0 imply a comma ending first line
39 | * line indented less that the one before implies full stop ending the first line
40 | * first atom in a line is a functor. remaining tokens are arguments
41 |
42 | I'm not sure how disjunctions should look.
43 |
44 |
--------------------------------------------------------------------------------
/yaml.md:
--------------------------------------------------------------------------------
1 | # YAML Library
2 |
3 | Parse and generate YAML text. Lists and scalars can be represented in Prolog natively. Maps are more difficult. I could represent them as lists of pairs or using `library(assoc)` or the speculated [library(map)](map.md). Either way, this would be a fantastic place to use quasi quotation since one could do this:
4 |
5 | ```prolog
6 | calculate_age(today, Birth, Age),
7 | YAML = ,
15 | save_config_file(‘config.yaml’, YAML).
16 | ```
17 |
18 | It’s probably best to write a generic parser and generator with DCG. Then implement a quasi quotation library on top of that.
19 |
--------------------------------------------------------------------------------