|
3 | 3 | <p style="text-align: center"> |
4 | 4 | <img src="docs/static/img/logos/Prolog_as_scalaDSL_BOW.png"> |
5 | 5 | </p> |
| 6 | + |
| 7 | +Prolog-as-scalaDSL is a library providing a DSL for writing Prolog programs in scala. |
| 8 | + |
| 9 | +## How to use |
| 10 | + |
| 11 | +1. Add the following library dependency in your build file. |
| 12 | + * for sbt: |
| 13 | + ```scala |
| 14 | + libraryDependencies += "io.github.kelvindev15" % "prolog-as-scaladsl_3" % "<version>" |
| 15 | + ``` |
| 16 | + * for gradle: |
| 17 | + ```kotlin |
| 18 | + implementation("io.github.kelvindev15:prolog-as-scaladsl_3:<version>") |
| 19 | + ``` |
| 20 | +2. Replace `<version>` with the desired of latest version of the library. |
| 21 | + |
| 22 | +## Demo |
| 23 | +Using the DSL is as simple as extending the `PrologDSL` trait. Let's write a program. |
| 24 | + |
| 25 | +```scala 3 |
| 26 | +object Demo extends PrologDSL: |
| 27 | + def main(args: Array[String]): Unit = |
| 28 | + val program = PrologProgram(Theory( |
| 29 | + factOf(structOf(atomOf("father"), atomOf("abraham"), atomOf("isaac"))), |
| 30 | + factOf(structOf(atomOf("father"), atomOf("terach"), atomOf("abraham"))), |
| 31 | + ruleOf( |
| 32 | + structOf(atomOf("grandfather"), varOf("X"), varOf("Y")), |
| 33 | + structOf(atomOf("father"), varOf("X"), varOf("Z")) and |
| 34 | + structOf(atomOf("father"), varOf("Z"), varOf("Y")))), |
| 35 | + ) |
| 36 | + |
| 37 | + for |
| 38 | + solution <- Solver solve ( |
| 39 | + program withGoal structOf(atomOf("father"), atomOf("abraham"), atomOf("isaac"))) |
| 40 | + do println(solution) |
| 41 | +``` |
| 42 | + |
| 43 | +Here's the output: |
| 44 | + |
| 45 | +```text |
| 46 | +Yes(father(abraham, isaac),Map()) |
| 47 | +``` |
| 48 | + |
| 49 | +As you can tell the writing of the prolog program is a bit difficult since we had to specify what is a struct, what |
| 50 | +is an atom or a variable, etc... Fortunately we are in scala so we can take advantage of that: |
| 51 | + |
| 52 | +```scala 3 |
| 53 | +val father = atomOf("father") |
| 54 | +val grandfather = atomOf("grandfather") |
| 55 | +val abraham = atomOf("abraham") |
| 56 | +val isaac = atomOf("isaac") |
| 57 | +val terach = atomOf("terach") |
| 58 | +val X = varOf("X") |
| 59 | +val Y = varOf("Y") |
| 60 | +val Z = varOf("Z") |
| 61 | + |
| 62 | +val program = PrologProgram(Theory( |
| 63 | + factOf(structOf(father, abraham, isaac)), |
| 64 | + factOf(structOf(father, terach, abraham)), |
| 65 | + ruleOf(structOf(grandfather, X, Y), structOf(father, X, Z) and structOf(father, Z, Y))), |
| 66 | +) |
| 67 | +``` |
| 68 | +Now the program was easier to write, but, still we had to introduce a lot of variables in order to achieve that. |
| 69 | +Luckily the DSL come with some of predefined structures such as variables and moreover strings are automatically converted |
| 70 | +to atoms! |
| 71 | + |
| 72 | +```scala 3 |
| 73 | +val program = PrologProgram(theory( |
| 74 | + factOf(structOf("father", "abraham", "isaac")), |
| 75 | + factOf(structOf("father", "terach", "abraham")), |
| 76 | + ruleOf(structOf("grandfather", X, Y), structOf("father", X, Z) and structOf("father", Z, Y))), |
| 77 | +) |
| 78 | +``` |
| 79 | + |
| 80 | +In order to resemble the prolog syntax, string can be invoked to create structures: |
| 81 | + |
| 82 | +```scala 3 |
| 83 | +val program = PrologProgram(theory( |
| 84 | + factOf("father"("abraham", "isaac")), |
| 85 | + factOf("father"("terach", "abraham")), |
| 86 | + ruleOf("grandfather"(X, Y), "father"(X, Z) and "father"(Z, Y)), |
| 87 | +)) |
| 88 | +``` |
| 89 | + |
| 90 | +The grandfather rule can be written in a "more prolog" way as: |
| 91 | + |
| 92 | +```scala 3 |
| 93 | +"grandfather"(X, Y) :- ("father"(X, Z) &: "father"(Z, Y)) |
| 94 | +``` |
| 95 | + |
| 96 | +Whenever a clause is expected (e.g the arguments of the theory method), structure are automatically converted to fact. |
| 97 | +So here's a cleaner way to write the program. |
| 98 | + |
| 99 | +```scala 3 |
| 100 | +val program = PrologProgram(theory( |
| 101 | + "father"("abraham", "isaac"), |
| 102 | + "father"("terach", "abraham"), |
| 103 | + "grandfather"(X, Y) :- ("father"(X, Z) &: "father"(Z, Y))) |
| 104 | +) |
| 105 | +``` |
| 106 | + |
| 107 | +Let's make some other queries: |
| 108 | + |
| 109 | +```scala 3 |
| 110 | +for |
| 111 | + goal <- Seq( |
| 112 | + "grandfather"("abraham", "isaac"), |
| 113 | + "father"(A, "isaac"), |
| 114 | + "father"(F, S) |
| 115 | + ) |
| 116 | + solution <- Solver solve (program withGoal goal) |
| 117 | +do println(solution) |
| 118 | + |
| 119 | +/* OUTPUT: |
| 120 | + No(grandfather(abraham, isaac)) |
| 121 | + Yes(father(A, isaac),Map(A -> abraham)) |
| 122 | + Yes(father(F, S),Map(F -> abraham, S -> isaac)) |
| 123 | + Yes(father(F, S),Map(F -> terach, S -> abraham)) |
| 124 | +*/ |
| 125 | +``` |
| 126 | + |
| 127 | +As you can see some solutions have a mapping (substitution). In this cases we can access a variable substitution directly |
| 128 | +from the solution: |
| 129 | + |
| 130 | +```scala 3 |
| 131 | +for |
| 132 | + solution <- Solver solve (program withGoal "father"(F, S)) |
| 133 | +do |
| 134 | + for |
| 135 | + father <- solution(F) |
| 136 | + son <- solution(S) |
| 137 | + do println(s"$father is the father of $son") |
| 138 | + |
| 139 | +/* OUTPUT |
| 140 | + abraham is the father of isaac |
| 141 | + terach is the father of abraham |
| 142 | + */ |
| 143 | +``` |
| 144 | + |
| 145 | +Program may be written in a more declarative way. All we need is to mixin the `DeclarativeProlog` trait. |
| 146 | + |
| 147 | +```scala 3 |
| 148 | +object Demo extends PrologDSL with DeclarativeProlog: |
| 149 | + def main(args: Array[String]): Unit = |
| 150 | + val program = prolog: |
| 151 | + programTheory: |
| 152 | + clause { "father"("abraham", "isaac") } |
| 153 | + clause { "father"("terach", "abraham") } |
| 154 | + clause { "grandfather"(X, Y) :- ("father"(X, Z) &: "father"(Z, Y)) } |
| 155 | + goal: |
| 156 | + "father"(F, S) |
| 157 | + |
| 158 | + for solution <- Solver solve program do println(solution) |
| 159 | +``` |
| 160 | +If you want, you may split your theory in a `staticTheory` and a `dynamicTheory` |
| 161 | +(`programTheory` is an alias of `dynamicTheory). |
| 162 | + |
| 163 | +A Solver may be used just to satisfy goals in the following way: |
| 164 | + |
| 165 | +```scala 3 |
| 166 | +for solution <- Solver query member(X, list("a", "b", "c")) do println(solution) |
| 167 | +/* |
| 168 | + Yes(member(X, [a, b, c]),Map(X -> a)) |
| 169 | + Yes(member(X, [a, b, c]),Map(X -> b)) |
| 170 | + Yes(member(X, [a, b, c]),Map(X -> c)) |
| 171 | + No(member(X, [a, b, c])) |
| 172 | + */ |
| 173 | +``` |
| 174 | +Notice that `member` and `list` and many others are facility methods to create their correspondent predicates. |
| 175 | + |
| 176 | +The trait `TermConvertible` gives you the possibility to interpret your object as if they were terms. You just need |
| 177 | +to specify how to convert them to term. Here's a cumbersome but explicative example: |
| 178 | + |
| 179 | +```scala 3 |
| 180 | +def father(f: String, s: String): TermConvertible = new TermConvertible: |
| 181 | + override def toTerm: Struct = "father"(f, s) |
| 182 | +``` |
0 commit comments