Permalink
Browse files

[enhance] doc/book: Partial re-write of all wiki-based chapters.

We are now using Markdown instad of Templates. Plus took this
opportunity to do a little clean-up.
  • Loading branch information...
1 parent 76c582d commit 626cab31368b4835f97f926280b7777993179afa @akoprow akoprow committed Nov 15, 2011
@@ -826,9 +826,47 @@ a comparison is a boolean. We write that the type of function
========================
In Opa, booleans are values +{true = void}+ and +{false = void}+, or, more
concisely but equivalently, +\{true\}+ and +\{false\}+.
-You can check whether boolean +b+ is true or false by using +if b then ... else ...+ or,
-equivalently, +match b with \{true\} -> ... | \{false\} -> ...+.
+
+Their type declaration looks as follow: +type bool = \{true\} / \{false\}+.
+Such types, admitting one of a number of variants, are called sum types.
+========================
+
+[TIP]
+.About sum types
========================
+A value has a _sum type_ +t / u+, meaning that the values of this type are either
+of the two variants: either a value of type +t+ or a value of type +u+.
+
+A good example of sum type are the aforementioned boolean values, which are defined
+as +type bool = \{false\}/\{true\}+.
+
+Another good example of sum type is the type +list+ of linked lists; its definition
+can be summed up as +\{nil\} / \{hd: ...; tl: list\}+.
+
+Note that sum types are not limited to two cases. Sum types with tens of cases
+are not uncommon in real applications.
+=======================
+
+Safely determining which variant was used to construct a value of a sum type
+can be accomplished with pattern matching.
+
+[TIP]
+.About pattern-matching
+========================
+The operation used to branch according to the case of a sum type
+is called _pattern-matching_. A good example of pattern-matching
+is +if ... then ... else ...+ . The more general syntax for pattern matching is
++match ... with | case_1 \-> ... | case_2 \-> ... | case_3 \-> ...+
+
+The operation is actually more powerful than just determining which case of a
+sum type is used. Indeed, if we use the vocabulary of languages such as Java or
+C#, pattern-matching combines features of +if+, +switch+, +instanceof+/+is+,
+multiple assignment and dereferenciation, but without the possible safety issues
+of +instanceof+/+is+ and with fewer chances of misuse than +switch+.
+
+As an example, you can check whether boolean +b+ is true or false by using
++if b then ... else ...+ or, equivalently, +match b with \{true\} -> ... | \{false\} -> ...+.
+=======================
Distinguishing messages between users
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -71,9 +71,9 @@ web application, it starts with a +server+:
topic_of_path(path) = String.capitalize(String.to_lower(List.to_string_using("", "", "::", path)))
start =
- | {path = [] ... } -> display("Hello")
- | {path = ["_rest_" | path] ...} -> rest(topic_of_path(path))
- | {~path ...} -> display(topic_of_path(path))
+| {path = [] ... } -> display("Hello")
+| {path = ["_rest_" | path] ...} -> rest(topic_of_path(path))
+| {~path ...} -> display(topic_of_path(path))
server = Server.of_bundle([@static_include_directory("resources")])
server = Server.simple_dispatch(start)
@@ -90,29 +90,23 @@ As you may see, this function is also quite simple:
.Handling rest requests
----------------
rest(topic) =
-(
match HttpServer.get_method() with
- | {some = {post}} ->
- _ = save_source(topic,
- match HttpServer.get_body() with
- | ~{some} -> some
- | {none} -> ""
- )
- Resource.raw_status({success})
- | {some = {delete}}->
- do remove_topic(topic)
- Resource.raw_status({success})
- | {some = {get}} ->
- Resource.raw_response(load_source(topic), "text/plain", {success})
- | _ ->
- Resource.raw_status({method_not_allowed})
-)
+ | {some = {post}} ->
+ _ = save_source(topic,
+ match HttpServer.get_body() with
+ | ~{some} -> some
+ | {none} -> ""
+ )
+ Resource.raw_status({success})
+ | {some = {delete}}->
+ do remove_topic(topic)
+ Resource.raw_status({success})
+ | {some = {get}} ->
+ Resource.raw_response(load_source(topic), "text/plain", {success})
+ | _ ->
+ Resource.raw_status({method_not_allowed})
----------------
-[WARNING]
-.In progress
-(API is currently evolving, update to new API)
-
First, notice that +rest+ is based on pattern-matching. Expect to meet pattern-matching
constantly in Opa. The first three patterns are built from some of to the distinct verbs
of the standard vocabulary of REST (these verbs are called _Http methods_):
@@ -156,19 +150,17 @@ With this expression, we may rewrite our extract as follows:
.Handling rest requests (shorter variant)
----------------
rest(topic) =
-(
match HttpServer.get_method() with
- | {some = {post}} ->
- _ = save_source(topic, HttpServer.get_body()?"")
- Resource.raw_status({success})
- | {some = {delete}}->
- do remove_topic(topic)
- Resource.raw_status({success})
- | {some = {get}} ->
- Resource.raw_response(load_source(topic), "text/plain", {success})
- | _ ->
- Resource.raw_status({method_not_allowed})
-)
+ | {some = {post}} ->
+ _ = save_source(topic, HttpServer.get_body() ? "")
+ Resource.raw_status({success})
+ | {some = {delete}}->
+ do remove_topic(topic)
+ Resource.raw_status({success})
+ | {some = {get}} ->
+ Resource.raw_response(load_source(topic), "text/plain", {success})
+ | _ ->
+ Resource.raw_status({method_not_allowed})
----------------
And with this, we are done! Our wiki can now be scripted by external web applications:
@@ -418,13 +410,14 @@ a distant service:
[source,opa]
---------------
-@publish load_source(topic) =
+@publish load_source(topic) =
match WebClient.Get.try_get(uri_for_topic(topic)) with
- | {failure = _} -> "Error, could not connect"
- | {~success} -> match WebClient.Result.get_class(success) with
- | {success} -> success.content
- | _ -> "Error {success.code}"
- end
+ | {failure = _} -> "Error, could not connect"
+ | {~success} ->
+ match WebClient.Result.get_class(success) with
+ | {success} -> success.content
+ | _ -> "Error {success.code}"
+ end
---------------
As in previous variants of the wiki, this version of +load_source+ attempts to
@@ -479,31 +472,28 @@ we have previously published:
[source,opa]
---------------
@publish load_rendered(topic) =
- source = load_source(topic)
- match Template.try_parse( Template.default, source) with
- | {failure = _} -> <>{source}</>
- | ~{success} -> Template.to_xhtml(Template.default, success)
+ source = load_source(topic)
+ Markdown.xhtml_of_string(Markdown.default_options, source)
---------------
Finally, we can adapt +save_source+, as follows:
[source,opa]
---------------
@publish save_source(topic, source) =
- match Template.try_parse(Template.default, source) with
- | ~{success} -> match WebClient.Post.try_post(uri_for_topic(topic), source) with
- | { failure = _ } -> {failure = "Could not reach distant server"}
- | { success = s } -> match WebClient.Result.get_class(s) with
- | {success} -> {success = Template.to_xhtml(Template.default, success)}
- | _ -> {failure = "Error {s.code}"}
- end
- end
- | {failure = _} -> {failure = "Incorrect syntax"}
+ match WebClient.Post.try_post(uri_for_topic(topic), source) with
+ | { failure = _ } ->
+ {failure = "Could not reach the distant server"}
+ | { success = s } ->
+ match WebClient.Result.get_class(s) with
+ | {success} -> {success = load_rendered(topic)}
+ | _ -> {failure = "Error {s.code}"}
+ end
---------------
This version of +save_source+ differs slightly from the original, not only because
-it uses a +\{post\}+ request to send the information, but also because it returns
-something slightly more precise than the original, for better error reporting.
+it uses a +\{post\}+ request to send the information, but also because it either
+returns the result +\{success=...\}+ or indicates an error with +\{failure=...\}+.
We take this opportunity to tweak our UI with a box meant to report such
errors:
@@ -517,34 +507,31 @@ and we update it in +edit+ and +save+, as follows:
[source,opa]
---------------
display(topic) =
- Resource.styled_page("About {topic}", ["/resources/css.css"],
- <div id=#header><div id=#logo></div>About {topic}</div>
- <div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}>
- {load_rendered(topic)}
- </>
- <div class="show_messages" id=#show_messages />
- <textarea class="edit_content" id=#edit_content style="display:none"
- cols="40" rows="30" onblur={_ -> save(topic)}></>
- )
+ Resource.styled_page("About {topic}", ["/resources/css.css"],
+ <div id=#header><div id=#logo></div>About {topic}</div>
+ <div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}>{load_rendered(topic)}</>
+ <div class="show_messages" id=#show_messages />
+ <textarea class="edit_content" id=#edit_content style="display:none" cols="40" rows="30" onblur={_ -> save(topic)}></>
+ )
edit(topic) =
- do Dom.transform([#show_messages <- <></>]) //Clear messages
- do Dom.set_value(#edit_content, load_source(topic))
- do Dom.hide(#show_content)
- do Dom.show(#edit_content)
- do Dom.give_focus(#edit_content)
- void
+ do Dom.transform([#show_messages <- <></>])
+ do Dom.set_value(#edit_content, load_source(topic))
+ do Dom.hide(#show_content)
+ do Dom.show(#edit_content)
+ do Dom.give_focus(#edit_content)
+ void
save(topic) =
- match save_source(topic, Dom.get_value(#edit_content)) with
- | { ~success } ->
- do Dom.transform([#show_content <- success]);
- do Dom.hide(#edit_content);
- do Dom.show(#show_content);
- void
- | {~failure} ->
- do Dom.transform([#show_messages <- <>{failure}</>])
- void
+ match save_source(topic, Dom.get_value(#edit_content)) with
+ | { ~success } ->
+ do Dom.transform([#show_content <- success]);
+ do Dom.hide(#edit_content);
+ do Dom.show(#show_content);
+ void
+ | {~failure} ->
+ do Dom.transform([#show_messages <- <>{failure}</>])
+ void
---------------
And that is all for the user interface.
@@ -683,14 +670,12 @@ In this case, expanding our horizon starts by rewriting +uri_for_topic+ as follo
[source,opa]
--------------
uri_for_topic = topic ->
-(
Uri.of_absolute({Uri.default_absolute with
schema = {some = "http"}
domain = "localhost"
port = {some = 8080}
path = ["_rest_", topic]
})
-)
--------------
So far, this is absolutely equivalent to what we had written earlier. Note
@@ -752,14 +737,18 @@ both purposes, Opa offers a module +CommandLine+:
--------------
uri_for_topic =
(
- default_uri = {Uri.default_absolute with domain = "localhost"
- schema = {some = "http"}}
- base_uri = CommandLine.filter({
- title = "Wiki arguments"
- init = default_uri
- parsers = []
- anonymous = []
- })
+ default_uri =
+ {Uri.default_absolute with
+ domain = "localhost"
+ schema = {some = "http"}
+ }
+ base_uri =
+ CommandLine.filter({
+ title = "Wiki arguments"
+ init = default_uri
+ parsers = []
+ anonymous = []
+ })
topic -> Uri.of_absolute({base_uri with
path = ["_rest_", topic]
})
@@ -784,10 +773,12 @@ parser_) to our family, as follows:
[source,opa]
.Parsing option +--wiki-server-port+
--------------
- option_port = {CommandLine.default_parser with
- names = ["--wiki-server-port"]
- on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}}
- }
+port_parser =
+ {CommandLine.default_parser with
+ names = ["--wiki-server-port"]
+ description = "The server port of the REST server for this wiki. By default, 8080."
+ on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}}
+ }
--------------
As you can see, a command-line parser is a record (it has type
@@ -849,12 +840,14 @@ the same absolute URI, but with a new content in field +port+.
We can now define in the exact same manner the command-line parser for the host:
[source,opa]
-.Parsing option +--wiki-server-host+
+.Parsing option +--wiki-server-domain+
--------------
- host_parser = {CommandLine.default_parser with
- names = ["--wiki-server-host"]
- on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}}
- }
+domain_parser =
+ {CommandLine.default_parser with
+ names = ["--wiki-server-domain"]
+ description = "The REST server for this wiki. By default, localhost."
+ on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}}
+ }
--------------
The main difference is that we use predefined text parser +Rule.consume+ (which accepts
anything) instead of +Rule.natural+ (which only accepts non-negative integers).
@@ -865,24 +858,27 @@ additional documentation, we obtain:
.Command-line arguments (complete)
--------------
uri_for_topic =
- host_parser = {CommandLine.default_parser with
- names = ["--wiki-server-host"]
- description = "The host of a the server offering a REST API for this wiki. By default, localhost."
- on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}}
+ domain_parser =
+ {CommandLine.default_parser with
+ names = ["--wiki-server-domain"]
+ description = "The REST server for this wiki. By default, localhost."
+ on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}}
}
- port_parser = {CommandLine.default_parser with
- names = ["--wiki-server-port"]
- description = "The port of a the server offering a REST API for this wiki. By default, 8080."
- on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}}
+ port_parser =
+ {CommandLine.default_parser with
+ names = ["--wiki-server-port"]
+ description = "The server port of the REST server for this wiki. By default, 8080."
+ on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}}
}
- base_uri = CommandLine.filter({
- title = "Wiki arguments"
- init = {Uri.default_absolute with domain = "localhost"
- schema = {some = "http"}}
- parsers = [domain_parser, port_parser]
- anonymous = []
- })
- topic -> Uri.of_absolute({base_uri with path = ["_rest_", topic]})
+ base_uri =
+ CommandLine.filter(
+ {title = "Wiki arguments"
+ init = {Uri.default_absolute with domain = "localhost" schema = {some = "http"}}
+ parsers = [domain_parser, port_parser]
+ anonymous = []
+ }
+ )
+ topic -> Uri.of_absolute({base_uri with path = ["_rest_", topic]})
--------------
This completes our REST client. We now have a full-featured REST client that can also act as a server
Oops, something went wrong.

0 comments on commit 626cab3

Please sign in to comment.