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
Original file line number Original file line Diff line number Diff line change
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 In Opa, booleans are values +{true = void}+ and +{false = void}+, or, more
concisely but equivalently, +\{true\}+ and +\{false\}+. 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 Distinguishing messages between users
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
226 changes: 111 additions & 115 deletions doc/book/hello_web_services/hello_web_services.adoc
Original file line number Original file line Diff line number Diff line change
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))) topic_of_path(path) = String.capitalize(String.to_lower(List.to_string_using("", "", "::", path)))
start = start =
| {path = [] ... } -> display("Hello") | {path = [] ... } -> display("Hello")
| {path = ["_rest_" | path] ...} -> rest(topic_of_path(path)) | {path = ["_rest_" | path] ...} -> rest(topic_of_path(path))
| {~path ...} -> display(topic_of_path(path)) | {~path ...} -> display(topic_of_path(path))
server = Server.of_bundle([@static_include_directory("resources")]) server = Server.of_bundle([@static_include_directory("resources")])
server = Server.simple_dispatch(start) server = Server.simple_dispatch(start)
Expand All @@ -90,29 +90,23 @@ As you may see, this function is also quite simple:
.Handling rest requests .Handling rest requests
---------------- ----------------
rest(topic) = rest(topic) =
(
match HttpServer.get_method() with match HttpServer.get_method() with
| {some = {post}} -> | {some = {post}} ->
_ = save_source(topic, _ = save_source(topic,
match HttpServer.get_body() with match HttpServer.get_body() with
| ~{some} -> some | ~{some} -> some
| {none} -> "" | {none} -> ""
) )
Resource.raw_status({success}) Resource.raw_status({success})
| {some = {delete}}-> | {some = {delete}}->
do remove_topic(topic) do remove_topic(topic)
Resource.raw_status({success}) Resource.raw_status({success})
| {some = {get}} -> | {some = {get}} ->
Resource.raw_response(load_source(topic), "text/plain", {success}) Resource.raw_response(load_source(topic), "text/plain", {success})
| _ -> | _ ->
Resource.raw_status({method_not_allowed}) 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 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 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_): 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) .Handling rest requests (shorter variant)
---------------- ----------------
rest(topic) = rest(topic) =
(
match HttpServer.get_method() with match HttpServer.get_method() with
| {some = {post}} -> | {some = {post}} ->
_ = save_source(topic, HttpServer.get_body()?"") _ = save_source(topic, HttpServer.get_body() ? "")
Resource.raw_status({success}) Resource.raw_status({success})
| {some = {delete}}-> | {some = {delete}}->
do remove_topic(topic) do remove_topic(topic)
Resource.raw_status({success}) Resource.raw_status({success})
| {some = {get}} -> | {some = {get}} ->
Resource.raw_response(load_source(topic), "text/plain", {success}) Resource.raw_response(load_source(topic), "text/plain", {success})
| _ -> | _ ->
Resource.raw_status({method_not_allowed}) Resource.raw_status({method_not_allowed})
)
---------------- ----------------


And with this, we are done! Our wiki can now be scripted by external web applications: 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] [source,opa]
--------------- ---------------
@publish load_source(topic) = @publish load_source(topic) =
match WebClient.Get.try_get(uri_for_topic(topic)) with match WebClient.Get.try_get(uri_for_topic(topic)) with
| {failure = _} -> "Error, could not connect" | {failure = _} -> "Error, could not connect"
| {~success} -> match WebClient.Result.get_class(success) with | {~success} ->
| {success} -> success.content match WebClient.Result.get_class(success) with
| _ -> "Error {success.code}" | {success} -> success.content
end | _ -> "Error {success.code}"
end
--------------- ---------------


As in previous variants of the wiki, this version of +load_source+ attempts to 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] [source,opa]
--------------- ---------------
@publish load_rendered(topic) = @publish load_rendered(topic) =
source = load_source(topic) source = load_source(topic)
match Template.try_parse( Template.default, source) with Markdown.xhtml_of_string(Markdown.default_options, source)
| {failure = _} -> <>{source}</>
| ~{success} -> Template.to_xhtml(Template.default, success)
--------------- ---------------


Finally, we can adapt +save_source+, as follows: Finally, we can adapt +save_source+, as follows:


[source,opa] [source,opa]
--------------- ---------------
@publish save_source(topic, source) = @publish save_source(topic, source) =
match Template.try_parse(Template.default, source) with match WebClient.Post.try_post(uri_for_topic(topic), source) with
| ~{success} -> match WebClient.Post.try_post(uri_for_topic(topic), source) with | { failure = _ } ->
| { failure = _ } -> {failure = "Could not reach distant server"} {failure = "Could not reach the distant server"}
| { success = s } -> match WebClient.Result.get_class(s) with | { success = s } ->
| {success} -> {success = Template.to_xhtml(Template.default, success)} match WebClient.Result.get_class(s) with
| _ -> {failure = "Error {s.code}"} | {success} -> {success = load_rendered(topic)}
end | _ -> {failure = "Error {s.code}"}
end end
| {failure = _} -> {failure = "Incorrect syntax"}
--------------- ---------------


This version of +save_source+ differs slightly from the original, not only because 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 it uses a +\{post\}+ request to send the information, but also because it either
something slightly more precise than the original, for better error reporting. 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 We take this opportunity to tweak our UI with a box meant to report such
errors: errors:
Expand All @@ -517,34 +507,31 @@ and we update it in +edit+ and +save+, as follows:
[source,opa] [source,opa]
--------------- ---------------
display(topic) = display(topic) =
Resource.styled_page("About {topic}", ["/resources/css.css"], Resource.styled_page("About {topic}", ["/resources/css.css"],
<div id=#header><div id=#logo></div>About {topic}</div> <div id=#header><div id=#logo></div>About {topic}</div>
<div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}> <div class="show_content" id=#show_content ondblclick={_ -> edit(topic)}>{load_rendered(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)}></>
<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) = edit(topic) =
do Dom.transform([#show_messages <- <></>]) //Clear messages do Dom.transform([#show_messages <- <></>])
do Dom.set_value(#edit_content, load_source(topic)) do Dom.set_value(#edit_content, load_source(topic))
do Dom.hide(#show_content) do Dom.hide(#show_content)
do Dom.show(#edit_content) do Dom.show(#edit_content)
do Dom.give_focus(#edit_content) do Dom.give_focus(#edit_content)
void void
save(topic) = save(topic) =
match save_source(topic, Dom.get_value(#edit_content)) with match save_source(topic, Dom.get_value(#edit_content)) with
| { ~success } -> | { ~success } ->
do Dom.transform([#show_content <- success]); do Dom.transform([#show_content <- success]);
do Dom.hide(#edit_content); do Dom.hide(#edit_content);
do Dom.show(#show_content); do Dom.show(#show_content);
void void
| {~failure} -> | {~failure} ->
do Dom.transform([#show_messages <- <>{failure}</>]) do Dom.transform([#show_messages <- <>{failure}</>])
void void
--------------- ---------------


And that is all for the user interface. 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] [source,opa]
-------------- --------------
uri_for_topic = topic -> uri_for_topic = topic ->
(
Uri.of_absolute({Uri.default_absolute with Uri.of_absolute({Uri.default_absolute with
schema = {some = "http"} schema = {some = "http"}
domain = "localhost" domain = "localhost"
port = {some = 8080} port = {some = 8080}
path = ["_rest_", topic] path = ["_rest_", topic]
}) })
)
-------------- --------------


So far, this is absolutely equivalent to what we had written earlier. Note 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 = uri_for_topic =
( (
default_uri = {Uri.default_absolute with domain = "localhost" default_uri =
schema = {some = "http"}} {Uri.default_absolute with
base_uri = CommandLine.filter({ domain = "localhost"
title = "Wiki arguments" schema = {some = "http"}
init = default_uri }
parsers = [] base_uri =
anonymous = [] CommandLine.filter({
}) title = "Wiki arguments"
init = default_uri
parsers = []
anonymous = []
})
topic -> Uri.of_absolute({base_uri with topic -> Uri.of_absolute({base_uri with
path = ["_rest_", topic] path = ["_rest_", topic]
}) })
Expand All @@ -784,10 +773,12 @@ parser_) to our family, as follows:
[source,opa] [source,opa]
.Parsing option +--wiki-server-port+ .Parsing option +--wiki-server-port+
-------------- --------------
option_port = {CommandLine.default_parser with port_parser =
names = ["--wiki-server-port"] {CommandLine.default_parser with
on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}} 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 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: We can now define in the exact same manner the command-line parser for the host:
[source,opa] [source,opa]
.Parsing option +--wiki-server-host+ .Parsing option +--wiki-server-domain+
-------------- --------------
host_parser = {CommandLine.default_parser with domain_parser =
names = ["--wiki-server-host"] {CommandLine.default_parser with
on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}} 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 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). 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) .Command-line arguments (complete)
-------------- --------------
uri_for_topic = uri_for_topic =
host_parser = {CommandLine.default_parser with domain_parser =
names = ["--wiki-server-host"] {CommandLine.default_parser with
description = "The host of a the server offering a REST API for this wiki. By default, localhost." names = ["--wiki-server-domain"]
on_param(x) = parser y=Rule.consume -> {no_params = {x with domain = y}} 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 port_parser =
names = ["--wiki-server-port"] {CommandLine.default_parser with
description = "The port of a the server offering a REST API for this wiki. By default, 8080." names = ["--wiki-server-port"]
on_param(x) = parser y=Rule.natural -> {no_params = {x with port = {some = y}}} 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({ base_uri =
title = "Wiki arguments" CommandLine.filter(
init = {Uri.default_absolute with domain = "localhost" {title = "Wiki arguments"
schema = {some = "http"}} init = {Uri.default_absolute with domain = "localhost" schema = {some = "http"}}
parsers = [domain_parser, port_parser] parsers = [domain_parser, port_parser]
anonymous = [] anonymous = []
}) }
topic -> Uri.of_absolute({base_uri with path = ["_rest_", topic]}) )
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 This completes our REST client. We now have a full-featured REST client that can also act as a server
Expand Down
Loading

0 comments on commit 626cab3

Please sign in to comment.