Navigation Menu

Skip to content

Commit

Permalink
[enhance] doc/book: Partial re-write of all wiki-based chapters.
Browse files Browse the repository at this point in the history
We are now using Markdown instad of Templates. Plus took this
opportunity to do a little clean-up.
  • Loading branch information
akoprow committed Nov 15, 2011
1 parent 76c582d commit 626cab3
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 543 deletions.
42 changes: 40 additions & 2 deletions doc/book/hello_chat/hello_chat.adoc
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
226 changes: 111 additions & 115 deletions doc/book/hello_web_services/hello_web_services.adoc
Expand Up @@ -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)
Expand All @@ -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_):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
})
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand All @@ -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
Expand Down

0 comments on commit 626cab3

Please sign in to comment.