Differences

This shows you the differences between two versions of the page.

Link to this comparison view

tutorials:intermediate:json_prolog [2019/04/16 14:39]
hawkin formatting
tutorials:intermediate:json_prolog [2019/04/24 13:54] (current)
gkazhoya [Case-sensitive variable names]
Line 1: Line 1:
-====== Using JSON Prolog to communicate with KnowRob ​(v0.7.0) ​======+====== Using JSON Prolog to communicate with KnowRob ======
  
-The following tutorial will show how to query the knowledge ​base, KnowRob, for information using Prolog queries within LISP and CRAM, using the json-prolog package, and also how to process and extract the data from the gained results. ​+Tested under CRAM version v0.7.0 
 + 
 +The following tutorial will show how to query the knowledge ​processing system[[http://​knowrob.org/​|KnowRob]], for information using Prolog queries within LISP and CRAM, using the json-prolog package, and also how to process and extract the data from the gained results. ​
  
 ==== Prerequisites ==== ==== Prerequisites ====
  
-We will assume that the previous tutorials have already been completed, and therefore ​CRAM, roslisp_repl and ROS have been installed. ​+This tutorial requires ​CRAM, roslisp_repl and ROS to have already ​been installed. Go through the previous tutorials and the installation page if you don't have CRAM installed yet.
 Make sure you have **Java 8** and **Gradle 5.1** already installed. Otherwise you won't be able to build KnowRob. Other versions could work, but have not been tested yet, so use them at your own risk!  Make sure you have **Java 8** and **Gradle 5.1** already installed. Otherwise you won't be able to build KnowRob. Other versions could work, but have not been tested yet, so use them at your own risk! 
  
Line 15: Line 17:
  
 === Installation troubleshooting === === Installation troubleshooting ===
-If it complains about missing ​**swi-prolog**, you can install it via:+If it complains about missing ​''​swi-prolog''​, you can install it via:
 <code bash> <code bash>
     $ sudo apt-get install swi-prolog     $ sudo apt-get install swi-prolog
 </​code>​ </​code>​
  
-Make sure however, that it is **version 7**. There are major problems with locations of libraries with version 8, which is the version suggested by the swi-prolog website. apt-get should install version 7 by default. To make sure, you can check it with: +Make sure however, that it is **version 7**. There are major problems with locations of libraries with version 8, which is the version suggested by the swi-prolog website. apt-get should install version 7 by default ​on Ubuntu 16.04. To make sure, you can check it with: 
 <code bash> ​   ​ <code bash> ​   ​
     $ dpkg -s swi-prolog     $ dpkg -s swi-prolog
Line 32: Line 34:
  
 === CRAM dependencies === === CRAM dependencies ===
-In order for us to be able to communicate with KnowRob from LISP, we need to load the **cram-json-prolog** ​package in our repl, since it implements the necessary interface for the CRAM to KnowRob communication. We also need **cram-utilities** ​package in order to process lazy lists. (//Note:// Lazy lists will be explained later in the tutorial. If you are curious and want to jump right there, you can do so by clicking [[json_prolog#​Lazy lists | here]].) \\+In order for us to be able to communicate with KnowRob from LISP, we need to load the ''​cram_json_prolog'' ​package in our repl, since it implements the necessary interface for the CRAM to KnowRob communication. We also need the ''​cram_utilities'' ​package in order to process lazy lists. (//Note:// Lazy lists will be explained later in the tutorial. If you are curious and want to jump right there, you can do so by clicking [[json_prolog#​Lazy lists | here]].) \\
 In order to load the packages, do the following in your repl: In order to load the packages, do the following in your repl:
  
-Open the Emacs command prompt with **,** and type **ros-load-system**. Hit **enter**. It will ask for you to enter the package you want to load, in which case you type **cram_json_prolog**. Confirm with enter. After a few seconds you should see **compilation finished** at the bottom line of Emacs. Repeat the process for **cram_utilities** as well.+<code lisp> 
 +CL-USER> (ros-load:load-system ​"cram_json_prolog" :​cram-json-prolog) 
 +CL-USER> (ros-load:​load-system "cram_utilities" :​cram-utilities) 
 +</​code>​
  
 If you want to add these dependencies directly into your package already, you can add them to your .asd file, as ''​cram-json-prolog''​ and ''​cram-utilities''​. ​ If you want to add these dependencies directly into your package already, you can add them to your .asd file, as ''​cram-json-prolog''​ and ''​cram-utilities''​. ​
  
-If you don't have a robot description currently published (and get thrown an error for that by Emacs)use the following instead+To be able to communicated with KnowRob from CRAM over ROSwe need to start a ROS node in the REPL:
 <code lisp> <code lisp>
-    ​CL-USER> (roslisp:​start-ros-node "my-node")+  ​CL-USER> (roslisp:​start-ros-node "my_node")
 </​code>​ </​code>​
  
Line 59: Line 64:
 </​code>​ </​code>​
  
-X being the variable we asked Prolog to fill with a value. Whenever Prolog returns a value for a variable, the variable name gets prefixed with a question mark. "​1"​ is the first value in the list we passed on to the ''​member'' ​function ​of Prolog. The ''​member'' ​function ​is true if the element is a member of the given list, or in this case since it is a variable, it will return the first element of the list. +X being the variable we asked Prolog to fill with a value. Whenever Prolog returns a value for a variable, the variable name gets prefixed with a question mark. "​1"​ is the first value in the list we passed on to the ''​member'' ​predicate ​of Prolog. The ''​member'' ​predicate ​is true if the element is a member of the given list, or in this case since it is a variable, it will return the first element of the list. 
-In a [[json_prolog#​Some more information about Prolog in LISP | later section]] we will look in more detail into the bindings we get as a result from Prolog and explain a bit more in depth why we get these results.+In a [[json_prolog#​Some more information about Prolog in LISP |later section]] we will look in more detail into the bindings we get as a result from Prolog and explain a bit more in depth why we get these results
 + 
 +Why do we get ''?​X''​ sitting in 3 brackets? The inner bracket connects ''?​X''​ with it's value ''​1''​. 
 +What does the second bracket do? 
 +Let's add another variable to the query -- the variable ''?​Y''​. 
 +<code lisp> 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(X,​ [1,2,3]), Y = X.") 
 +</​code>​ 
 +This binds ''?​X''​ to member of the ''​[1,​2,​3]''​ list and ''?​Y''​ gets the same value as ''?​X''​. 
 +The result of the query is 
 +<code lisp> 
 +(((?X . 1) (?Y . 1))) 
 +</​code>​ 
 +Therefore, the second bracket makes a list of bindings for each variable in the query: 
 +<code lisp> 
 +((?X . 1) (?Y . 1)) 
 +</​code>​ 
 +is a list of bindings for variable ''?​X''​ and ''?​Y''​. 
 + 
 +The last outer bracket is explained next.
  
 ==== prolog-simple ==== ==== prolog-simple ====
Line 72: Line 96:
 </​code> ​   ​ </​code> ​   ​
     ​     ​
-The result is basically still the same, but it looks a little bit more complex on the first glance. What we get is a list which contains two elements. The first one is a list in a list with two elements which we already know from the previous call with ''​prolog-simple-1''​ and which were explained previously, which describe ​the value Prolog found for ''​ ?X ''​. The second element of the list is  ''#​S(CRAM-UTILITIES::​LAZY-CONS-ELEM :GENERATOR #<​CLOSURE (LAMBDA () :IN JSON-PROLOG::​PROLOG-RESULT->​BDGS) +The result is basically still the same, but it looks a little bit more complex on the first glance. What we get is a list which contains two elements. The first one is a list in a list with two elements which we already know from the previous call with ''​prolog-simple-1''​ and which describes ​the value Prolog found for ''​ ?X ''​. The second element of the list is  ''#​S(CRAM-UTILITIES::​LAZY-CONS-ELEM :GENERATOR #<​CLOSURE (LAMBDA () :IN JSON-PROLOG::​PROLOG-RESULT->​BDGS) 
-                {1005F52FAB}>​))''​ which is a lazy list element generator. ​(We will explain lazy lists in the next section.) +                {1005F52FAB}>​))''​ which is a lazy list element generator.
 ==== lazy lists ==== ==== lazy lists ====
 The results we obtain from the queries are stored in lazy lists, meaning the information is loaded on demand, resulting in us seeing only one solution of the query. If we want to see all the results, we can expand the list and force LISP to show all of them to us, for example, by using: The results we obtain from the queries are stored in lazy lists, meaning the information is loaded on demand, resulting in us seeing only one solution of the query. If we want to see all the results, we can expand the list and force LISP to show all of them to us, for example, by using:
  
 <code lisp> <code lisp>
-    CL-USER> (setq *our-list* (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​)+    ​CL-USER> (defparameter *our-list* nil) 
 +    ​CL-USER> (setq *our-list* (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​))
       (((?X . 1))       (((?X . 1))
          . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM          . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM
Line 90: Line 114:
 We save our list in a variable for convenience purposes. This allows us also to expand the so stored list, and we can see all the possible values Prolog found for the variable ''?​X'',​ which are ''​1,​2,​3''​. We save our list in a variable for convenience purposes. This allows us also to expand the so stored list, and we can see all the possible values Prolog found for the variable ''?​X'',​ which are ''​1,​2,​3''​.
  
-If we do the same with ''​prolog-simple-1''​ we can see the difference between ''​prolog-simple-1''​ and ''​prolog-simple''​ better:+Now we see why we need the outer bracket: the result of the query is a (lazy) list of all possible bindings for the query variables. Let us add another variable to the query again: 
 <code lisp> <code lisp>
-    ​CL-USER> (setq *our-list* (json-prolog:​prolog-simple-1 "​member(X,​ [1,​2,​3])"​)) +CL-USER> (cut:force-ll (json-prolog:​prolog-simple "​member(X,​ [1,2,3]), Y = X.")) 
-        (((?X . 1))) +(((?X . 1) (?Y . 1)) ((?X . 2) (?Y . 2)) ((?X . 3) (?Y . 3)))
-    CL-USER> ​(cut:​force-ll *our-list*) +
-        ​(((?X . 1)))+
 </​code>​ </​code>​
 +We get a list of all possible bindings of the query variables, where the bindings is itself a list with one element for each variable.
  
-In the first case, meaning ''​prolog-simple'',​ we see that all possible bindings for ''​X''​ are contained within the lazy list, all of them being lists themselves, mapping the variable to a possible value. While for ''​prolog-simple-1''​ even after expanding the list, the result is still the same, since it returns only one possible value for ''?​X''​ to begin with.  +If we evaluate our original ''​member''​ query with ''​prolog-simple-1''​ we can see the difference between ''​prolog-simple-1''​ and ''​prolog-simple''​ better: 
 +<code lisp> 
 +CL-USER> (cut:​force-ll (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​)) 
 +(((?X . 1)) ((?X . 2)) ((?X . 3))) 
 +CL-USER> (cut:​force-ll (json-prolog:​prolog-simple-1 "​member(X,​ [1,​2,​3])"​)) 
 +(((?X . 1))) 
 +</​code>​ 
 + 
 +In the first case, meaning ''​prolog-simple'',​ we see that all possible bindings for ''​X''​ are contained within the lazy list, all of them being lists themselves, mapping the variable to a possible value. While for ''​prolog-simple-1''​ even after expanding the list, the result is still the same, since it returns only one possible value for ''?​X''​ to begin with. 
 + 
 +One peculiarity of Prolog is that it can return an infinite number of bindings for a variable. 
 +In that case, using ''​cut:​force-ll''​ on the lazy list would have to return a list of infinite length, so the program will compute forever. 
 +This is why lazy lists are useful when working with Prolog -- if the number of solutions is infinite or very big, using lazy lists one can work with one value at a time by iterating through the lazy list and getting new solutions on demand.
     ​     ​
 ==== Accessing data from Prolog ==== ==== Accessing data from Prolog ====
 Now that we have a lazy list with interesting information,​ how can we access the elements contained within? Now that we have a lazy list with interesting information,​ how can we access the elements contained within?
     ​     ​
-We can use ''​(cut:​lazy-car *our-list*)''​ to access the elements of the list. It is used the same way the normal ''​car''​ is. Same goes for ''​cdr''​. But there is a better way of doing this. However, from the query, we first need to obtain the lazy list. We can do this in the following way+We can use ''​(cut:​lazy-car *our-list*)''​ to access the elements of the list. It is used the same way the normal ''​car''​ is:
 <code lisp> <code lisp>
-    ​CL-USER> (setq *our-list* +CL-USER> (cut:lazy-car (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​)) 
-                   ​(cut:lazy-car +((?X . 1)) 
-                    ​(json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​))) +</​code>​ 
-</​code> ​   +Same goes for ''​cdr'':​ 
 +<code lisp> 
 +CL-USER> ​(cut:lazy-cdr (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​)
 +(((?X . 2)) 
 + . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM 
 +      :GENERATOR #<​CLOSURE (LAMBDA () :IN JSON-PROLOG::​PROLOG-RESULT->​BDGS) 
 +                   ​{10084ADDCB}>​)) 
 +</​code>​
  
-This gives us the lazy list. Then we can use ''​(cut:​var-value VAR LIST)''​ to access the value of a specific ​item. In our case we asked for possible values of the variable X. Therefore:+When we have one element of the lazy listwe can use ''​(cut:​var-value VAR LIST)''​ to access the value of a specific ​variable. In our case we asked for possible values of the variable X. Therefore:
 <code lisp> <code lisp>
-    ​CL-USER> (cut:​var-value '?​X ​*our-list*+CL-USER> (cut:​var-value '?​X ​(cut:lazy-car (json-prolog:​prolog-simple "​member(X,​ [1,​2,​3])"​))
-         ​1+1
 </​code> ​     </​code> ​    
-Gives us the result ''​1'',​ since this is the value of ''​X''​.+Gives us the result ''​1'',​ since this is the value of ''​X'' ​in the first element of the lazy list.
 That way, one can extract the necessary information,​ adjust it if needed, and pass it on to the plans which need it.  That way, one can extract the necessary information,​ adjust it if needed, and pass it on to the plans which need it. 
  
Line 129: Line 172:
  
 <code lisp> <code lisp>
- 
     CL-USER> (json-prolog:​prolog-simple-1 "​register_ros_package(knowrob_common)"​)     CL-USER> (json-prolog:​prolog-simple-1 "​register_ros_package(knowrob_common)"​)
-       (NIL +       (NIL)
-        . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM +
-             :​GENERATOR #<​CLOSURE (LAMBDA (:IN JSON-PROLOG::​PROLOG-RESULT->​BDGS) +
-                          {1006444B3B}>​)) +
-    ​+
 </​code>​ </​code>​
 +
 +In JSON Prolog ''​(NIL)''​ means success, and ''​NIL''​ means failure. This is explained in detail [[tutorials/​intermediate/​json_prolog?&#​nil_vs_nil|in the next section]].
     ​     ​
-This allows us to use the **knowrob_common** package, which allows us to access many utility functions and also the most commonly used queries of KnowRob.+This allows us to use the ''​knowrob_common'' ​package, which allows us to access many utility functions and also the most commonly used queries of KnowRob.
  
 With this imported package we could now do the following: With this imported package we could now do the following:
  
 <code lisp> <code lisp>
- +    ​CL-USER (json-prolog:​prolog-simple "​lowercase('​HELLOWORLD',​RESULT)") 
-    ​CL-USER (json-prolog:​prolog-simple "​lowercase('​HELLOWORLD',​Result)") +       (((?RESULT ​. |''​helloworld''​|))
-       (((|?Result| ​. |''​helloworld''​|))+
         . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM         . #​S(CRAM-UTILITIES::​LAZY-CONS-ELEM
               :GENERATOR #<​CLOSURE (LAMBDA () :IN JSON-PROLOG::​PROLOG-RESULT->​BDGS)               :GENERATOR #<​CLOSURE (LAMBDA () :IN JSON-PROLOG::​PROLOG-RESULT->​BDGS)
Line 151: Line 190:
 </​code>​ </​code>​
  
-This query converts the first elements upper case letters into lower case, and stores the obtained result in the ''​Result''​ variable.+This query converts the first elements upper case letters into lower case, and stores the obtained result in the ''​RESULT''​ variable.
  
-An overview of the currently available packages and their queries can be found [[http://​www.knowrob.org/​api | here]]. If when loading a package Emacs only returns ''​NIL''​ then it might be that the package does not exist or is not found on your system. In such cases, you can check the terminal in which json-prolog was launched. If it cannot be found, you will see the following error: ​+An overview of the currently available packages and their queries can be found [[http://​www.knowrob.org/​api|here]]. If when loading a package Emacs only returns ''​NIL''​ then it might be that the package does not exist or is not found on your system. In such cases, you can check the terminal in which json-prolog was launched. If it cannot be found, you will see the following error: ​
 <code bash> <code bash>
    ​[rospack] Error: package '​knowrob_maps_tools'​ not found    ​[rospack] Error: package '​knowrob_maps_tools'​ not found
Line 162: Line 201:
     ​     ​
 <code lisp> <code lisp>
- 
     Prolog query failed: PrologException:​ error(existence_error(procedure,​ '/'​(u_load_episodes,​ 1)), context(':'​(system,​ '/'​(call,​ 1)), _1))     Prolog query failed: PrologException:​ error(existence_error(procedure,​ '/'​(u_load_episodes,​ 1)), context(':'​(system,​ '/'​(call,​ 1)), _1))
     [Condition of type SIMPLE-ERROR]     [Condition of type SIMPLE-ERROR]
-    ​ 
 </​code>​ </​code>​
     ​     ​
-and if you check your terminal, in which the **json_prolog** node is running, there will be a huge java print as well, basically explaining the same thing.+and if you check your terminal, in which the ''​json_prolog'' ​node is running, there will be a huge java print as well, basically explaining the same thing.
   ​   ​
-    ​ + 
-==== Some more information about Prolog in LISP ====+==== Case-sensitive variable names ==== 
 + 
 +In the former query we got a weird result from Prolog which looked like this:  
 +<code lisp> 
 +|''​helloworld''​| 
 +</​code>​ 
 +What do the pipes stand for in this case? 
 + 
 +Lisp is case-insensitive,​ that means that variable of function names, or more correctly said, any symbol, can be written in any capitalization and will refer to the same symbol: 
 + 
 +<code lisp> 
 +CL-USER> (eq 'hello '​HELLO) 
 +
 +CL-USER> (eq 'hello '​Hello) 
 +
 +</​code>​ 
 + 
 +Prolog, on the other hand, is case-sensitive. 
 +And in some cases, one would like to have a case-sensitive variable in Lisp as well. 
 +This is where the pipes come into play: 
 +<code lisp> 
 +CL-USER> (intern "​HELLO"​) 
 +HELLO 
 +:INTERNAL 
 +CL-USER> (intern "​Hello"​) 
 +|Hello| 
 +NIL 
 +</​code>​ 
 +The function ''​intern''​ creates a symbol from a string. 
 +If the string is all capital, we get a normal usual symbol, in the other case we get the pipes. 
 + 
 +The same is with Prolog, if your variable or any other symbol is not all caps, you will get a symbol in Lisp which has pipes around it: 
 + 
 +<code lisp> 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(X,​ [1,​2,​3])"​) 
 +(((?X . 1))) 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(MYVAR,​ [1,​2,​3])"​) 
 +(((?MYVAR . 1))) 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(MY_VAR,​ [1,​2,​3])"​) 
 +(((?MY_VAR . 1))) 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(My_var,​ [1,​2,​3])"​) 
 +(((|?​My_var| . 1))) 
 +</​code>​ 
 + 
 +In that case, to get the value of the variable you should also use pipes: 
 + 
 +<code lisp> 
 +CL-USER> (cut:​var-value '​|?​My_var| 
 +                        (car (json-prolog:​prolog-simple-1 "​member(My_var,​ [1,​2,​3])"​))) 
 +
 +</​code>​ 
 + 
 +Please note that in Prolog, all variables have to start with a capital letter and cannot contain dashes: 
 +<code lisp> 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(x,​ [1,​2,​3])"​) 
 +NIL 
 +CL-USER> (json-prolog:​prolog-simple-1 "​member(MY-VAR,​ [1,​2,​3])"​) 
 +NIL 
 +</​code>​ 
 +==== (NIL) vs NIL ====
     ​     ​
 If we want to check, if a certain number is member of the list, we must declare the variable accordingly. e.g. If we want to check, if a certain number is member of the list, we must declare the variable accordingly. e.g.