diff options
author | Eduardo Julian | 2021-09-20 23:01:35 -0400 |
---|---|---|
committer | Eduardo Julian | 2021-09-20 23:01:35 -0400 |
commit | 8196ab379495ab00c11b74b55b6f2fabd99ab351 (patch) | |
tree | 07b5e9eacbe1532ff4eb7506ac5d492e367b1d7b /documentation/book | |
parent | 0bc2c541ab27e44b760618d15a248a794ab2f98e (diff) |
Updates and fixes for the book.
Diffstat (limited to 'documentation/book')
28 files changed, 1196 insertions, 1064 deletions
diff --git a/documentation/book/the_lux_programming_language/appendix_a.md b/documentation/book/the_lux_programming_language/appendix_a.md index a14ef2823..4002c13e9 100644 --- a/documentation/book/the_lux_programming_language/appendix_a.md +++ b/documentation/book/the_lux_programming_language/appendix_a.md @@ -5,136 +5,152 @@ You've already seen some import syntax, but now you'll see all the options avail If you recall [Chapter 1](chapter_1.md), there was this example code: ```clojure -(.module: - [library - [lux #* - [program (#+ program:)] - ["." debug] - [control - ["." io]]]]) +(.using + [library + [lux "*" + [program {"+" program:}] + ["[0]" debug] + [control + ["[0]" io]]]]) ``` Here, we're importing the `library/lux` module. -The `#*`/`#all` option means _locally import every definition exported by the `library/lux` module_. + +The `"*"`/`"all"` option means _locally import every definition exported by the `library/lux` module_. This allows usage of those definitions without having to give them the `library/lux.` prefix, or even the `.` shortcut prefix. This may cause some issues if you import 2 definitions with the same name from different modules; or if you get a definition from one module, but then write your own definition with the same name in your code. + In those circumstances, the compiler will complain, saying that you can't re-define `X`; where `X` is the name of the definition. Then, we import `library/lux/program`, but we **only** import locally the `program:` definition. -That is what the `#+`/`#only` option allows. +That is what the `"+"`/`"only"` option allows. - There is also a `#-`/`#exclude` option which means locally import everything **except** the specified definitions_. - You could use it like this: `[program (#- foo bar baz)]` + There is also a `"-"`/`"exclude"` option which means locally import everything **except** the specified definitions_. + You could use it like this: `[your_module {"-" foo bar baz}]` Finally, we import both the `library/lux/debug` and `library/lux/control/io` modules. + In neither case do we import any of their definitions locally. + We also give both of those modules local aliases. -That is what that `"."` syntax does. -The `.module:` macro recognizes that syntax for aliases and replaces the dot/period with the import name directly to the right. + +That is what that `"[0]"` syntax does. + +The `.using` macro recognizes that syntax for aliases and replaces the `[0]` with the import name directly to the right. + That means: -* `"."` + `debug` = `debug` -* `"."` + `io` = `io` +* `"[0]"` + `debug` = `debug` +* `"[0]"` + `io` = `io` This might not seem like a big deal, but the aliasing syntax allows you to give imports arbitrary names, so long as you take into account the substitutions that may happen. So, for example: -* `"my_."` + `debug` = `my_debug` -* `"._."` + `io` = `io_io` +* `"my_[0]"` + `debug` = `my_debug` +* `"[0]_[0]"` + `io` = `io_io` It is also important to note that while imports can be nested for convenience, they don't have to be. -The `.module:` declaration could just as easily been written like this: +The `.using` declaration could just as easily been written like this: ```clojure -(.module: - [library/lux #*] - [library/lux/program (#+ program:)] - ["debug" library/lux/debug] - ["io" library/lux/control/io]) +(.using + [library/lux "*"] + [library/lux/program {"+" program:}] + ["debug" library/lux/debug] + ["io" library/lux/control/io]) ``` You might also guess that `library` was not imported as a module because it was neither given an alias, not had any definitions specified as local imports. + Any module-path fragments included in the import syntax without such options will not be imported and will simply be assumed to be part of the module-paths of the sub-modules specified under them. --- -It is also possible to have the `.module:` macro open interface implementations for you when importing the modules that contain them. +It is also possible to have the `.using` macro open interface implementations for you when importing the modules that contain them. + For example: ```clojure -(.module: - [library - [lux #* - [data - [collection - ["." list ("#::." functor monoid)]]]]]) +(.using + [library + [lux "*" + [data + [collection + ["[0]" list ("[1]::[0]" functor monoid)]]]]]) ``` The import above would locally import: -* `list::map`, from `functor`. +* `list::each`, from `functor`. * `list::identity`, from `monoid`. -* `list::compose`, from `monoid`. +* `list::composite`, from `monoid`. Here, we can also see some additional syntax for aliasing. + First of all, when opening implementations, aliasing syntax is used to determine the names of the local implementation imports. -The `.` is replaced with the name of the implementation member. -The `#` is bound to the name of the _context_ of the import. + +The `[0]` is replaced with the name of the implementation member. + +The `[1]` is bound to the name of the _context_ of the import. + In this case, the implementations are coming from the `library/lux/data/collection/list` module, so that is the context. -And since that module has been imported with the local alias `list`, that is the name that replaces the `#` in the aliasing syntax for the implementation imports. + +And since that module has been imported with the local alias `list`, that is the name that replaces the `[1]` in the aliasing syntax for the implementation imports. + And that is how we end up with the list of names I enumerated above. -The `#` syntax for aliasing can also be used between modules, and not just when importing implementation members. +The `[1]` syntax for aliasing can also be used between modules, and not just when importing implementation members. For example: ```clojure -(.module: - [library - [lux #* - [data - ["." collection #_ - ["#/." list ("#::." functor monoid)]]]]]) +(.using + [library + [lux "*" + [data + ["[0]" collection "_" + ["[1]/[0]" list ("[1]::[0]" functor monoid)]]]]]) ``` Would locally import: -* `collection/list::map`, from `functor`. +* `collection/list::each`, from `functor`. * `collection/list::identity`, from `monoid`. -* `collection/list::compose`, from `monoid`. +* `collection/list::composite`, from `monoid`. The context between module imports corresponds to the closest ancestor path which has itself been aliased. + Non-aliased paths don't count as context. This means: ```clojure -(.module: - [library - [lux #* - ["." data #_ - [collection - ["#/." list ("#::." functor monoid)]]]]]) +(.using + [library + [lux "*" + ["[0]" data "_" + [collection + ["[1]/[0]" list ("[1]::[0]" functor monoid)]]]]]) ``` Would locally import: -* `data/list::map`, from `functor`. +* `data/list::each`, from `functor`. * `data/list::identity`, from `monoid`. -* `data/list::compose`, from `monoid`. +* `data/list::composite`, from `monoid`. - Also, that `#_`/`#ignore` syntax you may have noticed means _do not import this module; just give it an alias I can refer to later as a context_. + Also, that `"_"`/`"ignore"` syntax you may have noticed means _do not import this module; just give it an alias I can refer to later as a context_. I should also note that you can **both** locally import definitions and open implementations as parts of the same module import. For example: ```clojure -(.module: - [library - [lux #* - [data - [collection - ["." list (#+ repeated size) ("#::." monad)]]]]]) +(.using + [library + [lux "*" + [data + [collection + ["[0]" list {"+" repeated size} ("[1]::[0]" monad)]]]]]) ``` --- @@ -162,18 +178,20 @@ You can import other modules in the hierarchy like this: ```clojure ... In program/foo/baz -(.module: - [library - [lux #*]] - ["." /quux] ... program/foo/baz/quux, aliased as /quux - ["." //bar] ... program/foo/bar, aliased as //bar - ["." ///] ... program, aliased as /// +(.using + [library + [lux "*"]] + ["[0]" /quux] ... program/foo/baz/quux, aliased as /quux + ["[0]" //bar] ... program/foo/bar, aliased as //bar + ["[0]" ///] ... program, aliased as /// ) ``` A single forward slash (`/`) signifies _"this module"_ in the hierarchy, so anything after the forward slash is assumed to be under _this module_. -Two forward slashes (`//`) signify _"the module above"_, and any forward slash after that allows you to go further up the hierarchy. -In the case of `program`, it's enough to just specify three forward slashes (`///`) for the `.module:` macro to know which module you're referring to. + +Two forward slashes (`//`) signify _"the module above"_, and any forward slash after that allows you to go further **up** the hierarchy. + +In the case of `program`, it's enough to just specify three forward slashes (`///`) for the `.using` macro to know which module you're referring to. You can think about it like this: @@ -185,34 +203,35 @@ Also, this relative path syntax can be nested, like so: ```clojure ... In program/foo/baz -(.module: - [library - [lux #*]] - [/ - ["." quux]] ... program/foo/baz/quux, aliased as quux - [// - ["." bar] ... program/foo/bar, aliased as bar - ] - ["." ///] ... program, aliased as /// - ) +(.using + [library + [lux "*"]] + [/ + ["[0]" quux]] ... program/foo/baz/quux, aliased as quux + [// + ["[0]" bar] ... program/foo/bar, aliased as bar + ] + ["[0]" ///] ... program, aliased as /// + ) ``` Or even: ```clojure ... In program/foo/baz -(.module: - [library - [lux #*]] - [/ - ["." quux] ... program/foo/baz/quux, aliased as quux - [// - ["." bar] ... program/foo/bar, aliased as bar - ["program" //] ... program, aliased as program - ]]) +(.using + [library + [lux "*"]] + [/ + ["[0]" quux] ... program/foo/baz/quux, aliased as quux + [// + ["[0]" bar] ... program/foo/bar, aliased as bar + ["program" //] ... program, aliased as program + ]]) ``` You may have noticed that when importing `program`, we went from `///` to `//`. + That is because, since it's nested under another `//`, it's relative to `program/foo` instead of `program/foo/baz`, so only 1 step up is necessary instead of the 2 steps a `///` would provide. --- @@ -221,27 +240,29 @@ For the second way to do relative imports, you can see this example: ```clojure ... In program/foo/baz -(.module: - [library - [lux #*]] - [\\test - ["." /] ... test/foo/baz, aliased as / - ] - ... Alternatively - ["." \\test] ... test/foo/baz, aliased as \\test - ... Or - [\\ - [\test - ["." /] ... test/foo/baz, aliased as / - ]] - ) +(.using + [library + [lux "*"]] + [\\test + ["[0]" /] ... test/foo/baz, aliased as / + ] + ... Alternatively + ["[0]" \\test] ... test/foo/baz, aliased as \\test + ... Or + [\\ + [\test + ["[0]" /] ... test/foo/baz, aliased as / + ]] + ) ``` The backslash (`\`) works in the reverse direction to the forward slash (`/`). -If the forward slash allows you append paths to the back, and to move up the hierarchy from the end; then the backslash allows you to append paths to the front, and the move down the hierarchy from the beginning. + +If the forward slash allows you append paths to the **back**, and to move **up** the hierarchy from the end; then the backslash allows you to append paths to the **front**, and the move **down** the hierarchy from the beginning. Why would you want such a thing? Because it allows you to easily establish parallel hierarchies of modules, which is a useful way to separate orthogonal aspects of your program (like the `program` and `test` hierarchies in our example). + Then, by using this relative syntax, you can refer to one hierarchy from another in an easy way. diff --git a/documentation/book/the_lux_programming_language/appendix_b.md b/documentation/book/the_lux_programming_language/appendix_b.md index 8ff38eb68..9ba711711 100644 --- a/documentation/book/the_lux_programming_language/appendix_b.md +++ b/documentation/book/the_lux_programming_language/appendix_b.md @@ -13,21 +13,33 @@ Whereas other programming languages often overload the math operators `+`, `-`, The differences may look trivial, but since the numeric types are treated differently in Lux, you must be aware of which function-set you're using when working with your data. However, this is not the biggest difference in Lux's math operators in comparison to other languages. + The difference that takes the crown is the ordering of the arguments. What do I mean? In most languages you'd write `4 - 7`. + In other lisps you'd write `(- 4 7)`. + But in Lux, you'd write `(- 7 4)`. + This even applies to ordering operators. + In most languages you'd write `4 < 7`. + In other lisps you'd write `(< 4 7)`. + But in Lux, you'd write `(< 7 4)`. + _What is going on!? This is so bizarre!_ Calm down. + Everything is going to be fine. What's going on is that in functional programming, there is this convention of putting the most significant argument to a function as the last one. -In the case of math functions, this would be the argument on which you're operating. I call it the _"subject"_ of the function. + +In the case of math functions, this would be the argument on which you're operating. + +I call it the _"subject"_ of the function. In the case of the subtraction operation, it would be the 4, since you're subtracting 7 from it. @@ -78,6 +90,7 @@ Note that `infix` doesn't enforce any grouping rules, since you can actually use The rule is simple, the argument to the right of the operator will be taken first, and then the argument to the left. So `[3.0 pow 2.0]` becomes `(pow 2.0 3.0)`, and `[5.0 * 8.0]` becomes `(* 8.0 5.0)`. + Thus, the infix syntax is transformed into Lux's prefix variation. --- diff --git a/documentation/book/the_lux_programming_language/appendix_c.md b/documentation/book/the_lux_programming_language/appendix_c.md index 85b976c3c..38580fee8 100644 --- a/documentation/book/the_lux_programming_language/appendix_c.md +++ b/documentation/book/the_lux_programming_language/appendix_c.md @@ -7,11 +7,13 @@ _Why?_, you may wonder. _What does being a macro add to the mix?_ Well, as it turns out, by making `case` be a macro, Lux can perform some compile-time calculations which ultimately enable a set of really cool features to be implemented: custom pattern-matching. Most languages with pattern-matching have a fixed set of rules and patterns for how everything works. + Not so with Lux. Lux provides a set of default mechanisms, but by using macros where patterns are located, `case` can expand those macro calls to get the myriad benefits they offer. But enough chit-chat. + Let's see them in action. ## Pattern-matching macros in the Standard Library @@ -19,10 +21,10 @@ Let's see them in action. ```clojure (case (list 1 2 3) (^ (list x y z)) - (#.Some (+ x (* y z))) + {.#Some (+ x (* y z))} _ - #.None) + {.#None}) ``` You may remember how annoying it was to pattern-match against lists in the [Chapter 5](chapter_5.md) example. @@ -34,22 +36,22 @@ Well, by using the `^` pattern-matching macro, you can use any normal macros you ... Useful in situations where the result of a branch depends on further refinements on the values being matched. ... For example: (case (split (size static) uri) - (^multi (#.Some [chunk uri']) - {(text::= static chunk) true}) + (^multi {.#Some [chunk uri']} + [(text::= static chunk) #1]) (match_uri endpoint? parts' uri') _ - (#.Left (format "Static part " (%.text static) " doesn't match URI: " uri))) + {.#Left (format "Static part " (%.text static) " doesn't match URI: " uri)}) ... Short-cuts can be taken when using boolean tests. ... The example above can be rewritten as... (case (split (size static) uri) - (^multi (#.Some [chunk uri']) + (^multi {.#Some [chunk uri']} (text::= static chunk)) (match_uri endpoint? parts' uri') _ - (#.Left (format "Static part " (%.text static) " doesn't match URI: " uri))) + {.#Left (format "Static part " (%.text static) " doesn't match URI: " uri})) ``` I **love** `^multi`. @@ -61,19 +63,19 @@ The possibilities are endless when it comes to the refinement you can do, and wh ```clojure ... Allows you to simultaneously bind and de-structure a value. (def: (hash (^@ set [element_hash _])) - (list::mix (function (_ elem acc) - (n.+ (\ element_hash hash elem) acc)) - 0 - (set.list set))) + (list#mix (function (_ elem acc) + (n.+ (# element_hash hash elem) acc)) + 0 + (set.list set))) ``` `^@` is for when you want to deal with a value both as a whole and in parts. ```clojure ... Same as the "open" macro, but meant to be used as a pattern-matching macro for generating local bindings. -... Can optionally take a "prefix" text for the generated local bindings. -(def: #export (range (^open ".") from to) - (All [a] (-> (Enum a) a a (List a))) +... Can optionally take an aliasing text for the generated local bindings. +(def: .public (range (^open "[0]") from to) + (All (_ a) (-> (Enum a) a a (List a))) (range' <= succ from to)) ``` @@ -83,19 +85,20 @@ It's excellent when taking structures as function arguments, or when opening str ```clojure ... Or-patterns. -(type: Weekday - #Monday - #Tuesday - #Wednesday - #Thursday - #Friday - #Saturday - #Sunday) +(type: .public Day + (Variant + {#Sunday} + {#Monday} + {#Tuesday} + {#Wednesday} + {#Thursday} + {#Friday} + {#Saturday})) (def: (weekend? day) - (-> Weekday Bool) + (-> Day Bit) (case day - (^or #Saturday #Sunday) + (^or {#Saturday} {#Sunday}) true _ @@ -111,32 +114,32 @@ It's a real time-saver. (def: (beta_reduce env type) (-> (List Type) Type Type) (case type - (#.Primitive name params) - (#.Primitive name (list::map (beta_reduce env) params)) + {.#Primitive name params} + {.#Primitive name (list#map (beta_reduce env) params)} (^template [<tag>] - [(<tag> left right) - (<tag> (beta_reduce env left) (beta_reduce env right))]) - ([#.Sum] - [#.Product] - [#.Function] - [#.Apply]) + [{<tag> left right} + {<tag> (beta_reduce env left) (beta_reduce env right)}]) + ([.#Sum] + [.#Product] + [.#Function] + [.#Apply]) (^template [<tag>] - [(<tag> old_env def) + [{<tag> old_env def} (case old_env - #.End - (<tag> env def) + {.#End} + {<tag> env def} _ type)]) - ([#.UnivQ] - [#.ExQ]) + ([.#UnivQ] + [.#ExQ]) - (#.Parameter idx) + {.#Parameter idx} (maybe.else type (list.item idx env)) - (#.Named name type) + {.#Named name type} (beta_reduce env type) _ @@ -152,34 +155,23 @@ You can save yourself quite a lot of typing (and debugging) by reusing a lot of It's a great asset! ```clojure -... Allows you to extract record members as local variables with the same names. -... For example: -(let [(^slots [#foo #bar #baz]) quux] - (f foo bar baz)) -``` - -`^slots` is great for working with records, as it allows you to create local variables with the names of the tags you specify. - -Now you can work with record member values with ease. - -```clojure ... Allows destructuring of streams in pattern-matching expressions. ... Caveat emptor: Only use it for destructuring, and not for testing values within the streams. -(let [(^sequence& x y z _tail) (some_sequence_function 1 2 3)] +(let [(^stream& x y z _tail) (some_stream_function 1 2 3)] (func x y z)) ``` -`^sequence&` hails from the `library/lux/data/collection/sequence` module, and it's quite special, because it allows you to de-structure something you normally wouldn't be able to: functions. +`^stream&` hails from the `library/lux/data/collection/stream` module, and it's quite special, because it allows you to de-structure something you normally wouldn't be able to: functions. -You see, Lux sequences (as defined in `library/lux/data/collection/sequence`) are implemented using functions. +You see, Lux streams (as defined in `library/lux/data/collection/stream`) are implemented using functions. The reason is that they are infinite in scope, and having an infinite data-structure would be... well... impossible (_unless you manage to steal a computer with infinite RAM from space aliens_). Well, no biggie! -`^sequence&` does some black magic to make sure you can de-structure your stream just like any other data-structure. +`^stream&` does some black magic to make sure you can de-structure your stream just like any other data-structure. -## How to Make your Own +## How to make your own The technique is very simple. @@ -205,7 +197,7 @@ _What gives?_ It's simple: some macros are so advanced that they require altering not just their bodies, but anything that comes later. -A great example of that is the `^multi` macro (which is actually the reason those inputs are given to pattern-matching macros in the first place). +A great example of that is the `^multi` macro. `^multi` performs some large-scale transformations on your code which require getting access to the rest of the code after a given usage of `^multi`. @@ -216,11 +208,11 @@ To make things easier to understand, here is the implementation of the `^` macro ```clojure (macro: (^ tokens) (case tokens - (#Item [_ (#Form (#Item pattern #End))] (#Item body branches)) + {#Item [_ {#Form {#Item pattern {#End}}}] {#Item body branches}} (do meta_monad [pattern+ (full_expansion pattern)] (case pattern+ - (#Item pattern' #End) + {#Item pattern' {#End}} (in (list& pattern' body branches)) _ diff --git a/documentation/book/the_lux_programming_language/appendix_d.md b/documentation/book/the_lux_programming_language/appendix_d.md index 16d49b6bb..2d8adcd8e 100644 --- a/documentation/book/the_lux_programming_language/appendix_d.md +++ b/documentation/book/the_lux_programming_language/appendix_d.md @@ -14,7 +14,7 @@ I try to figure out ways to get my code to be more pipe-sensitive, to see how fa ## Piping macros in the standard library -Anyhow, after looking at some of the innovations in Clojure on the piping department, I decided to come up with my own tricks to try to get Lux to become a piping superpower. +Anyhow, after looking at some of the innovations of Clojure in the piping department, I decided to come up with my own tricks to try to get Lux to become a piping superpower. I added the `library/lux/control/pipe` module, which contains several macros meant to be used within the `|>` macro, and which extend it with awesome capabilities. @@ -75,7 +75,7 @@ Useful in certain kinds of situations. (let> @ (+ @ @))) ``` -`let>` binds the current value piped into it so you can refer to it multiple times within it's body. +`let>` binds the current value piped into it so you can refer to it multiple times within its body. Pretty nifty, huh? @@ -108,7 +108,7 @@ You'll thank me later. ```clojure ... Monadic pipes. -... Each steps in the monadic computation is a pipe and must be given inside a tuple. +... Each step in the monadic computation is a pipe and must be given inside a tuple. (|> 5 (do> identity.monad [(i.* 3)] @@ -118,7 +118,7 @@ You'll thank me later. And just to show you I'm serious, I did the unthinkable. -Piped macro expressions! +Piped monadic expressions! ## How to make your own piping macros @@ -146,10 +146,10 @@ These macros can keep you in the flow while you're writing complex code, so you Oh... and did I mention the `|>>` macro? -It generates for you a single-argument function that will immediately pipe its argument through all the steps you give it? +It generates for you a single-argument function that will immediately pipe its argument through all the steps you give it. ```clojure -(only (|>> (member? forbidden-definitions) +(only (|>> (member? forbidden_definitions) not) all_definitions) ``` diff --git a/documentation/book/the_lux_programming_language/appendix_e.md b/documentation/book/the_lux_programming_language/appendix_e.md index a68636e91..4ce648625 100644 --- a/documentation/book/the_lux_programming_language/appendix_e.md +++ b/documentation/book/the_lux_programming_language/appendix_e.md @@ -18,9 +18,10 @@ That means an n-tuple is (_roughly_) an n-array. The reason why I say _"roughly"_ will be explained shortly. Variants, on the other hand, are 3-arrays. -The first element is the int value of its associated tag. -The second element is a kind of boolean flag used internally by the Lux run-time infrastructure. -The third element contains the variant's value. + +* The first element is the int value of its associated tag. +* The second element is a kind of boolean flag used internally by the Lux run-time infrastructure. +* The third element contains the variant's value. Finally, functions produce custom classes, and function values are just objects of those classes. @@ -49,6 +50,7 @@ But, as I said in [Chapter 6](chapter_6.md), Lux treats both the same. _How does that work?_ Well, Lux knows how to work with both flat and nested tuples and it can do so efficiently; so ultimately it doesn't matter. + It will all be transparent to you. When it comes to variants, the situation is similar in some ways, but different in others. @@ -86,18 +88,18 @@ Here are some examples from the `library/lux/ffi` module, where I have some type ```clojure (type: .public Privacy (Variant - #PublicP - #PrivateP - #ProtectedP - #DefaultP)) + {#PublicP} + {#PrivateP} + {#ProtectedP} + {#DefaultP})) (def: privacy_modifier^ (Parser Privacy) - (let [(^open ".") <>.monad] + (let [(^open "[0]") <>.monad] ($_ <>.or - (<code>.this! (' #public)) - (<code>.this! (' #private)) - (<code>.this! (' #protected)) + (<code>.this! (' "public")) + (<code>.this! (' "private")) + (<code>.this! (' "protected")) (in [])))) ``` @@ -121,11 +123,11 @@ Here's an example of `<>.and` in action: ... From library/lux/ffi (def: (argument^ type_vars) (-> (List (Type Var)) (Parser Argument)) - (<code>.record (<>.and <code>.local_identifier - (..type^ type_vars)))) + (<code>.tuple (<>.and <code>.local_symbol + (..type^ type_vars)))) ``` -The cool thing is that these combinators show up not just in syntax parsers, but also in command-line argument parsing, lexing, concurrency/asynchrony operations, error-handling and in many other contexts. +The cool thing is that these combinators show up not just in syntax parsers, but also in command-line argument parsing, lexing, concurrenct/asynchronous operations, error-handling and in many other contexts. The nested/composable semantics of Lux entities provide a flexibility that enables powerful features (such as this) to be built on top. diff --git a/documentation/book/the_lux_programming_language/appendix_f.md b/documentation/book/the_lux_programming_language/appendix_f.md index 461d54417..37b0788d3 100644 --- a/documentation/book/the_lux_programming_language/appendix_f.md +++ b/documentation/book/the_lux_programming_language/appendix_f.md @@ -1,6 +1,6 @@ # Appendix F: Implicit polymorphism -If you've used Lux's interfaces and implementations already (with the `\` macro), you've probably noticed that you need to pass around the specific implementations you need every time you want to call some interface's method, or access some constant value. +If you've used Lux's interfaces and implementations already (with the `#` macro), you've probably noticed that you need to pass around the specific implementations you need every time you want to call some interface's method. That can become tiresome if you need to do it all the time, and specially if you come from languages that do method-selection for you automatically. @@ -19,19 +19,20 @@ Why do you have to pay for something you're not taking advantage of? Clearly, there is an asymmetry here. It is a feature that is most useful in the few instances when you want full power. + At any other point, it's a hindrance. Well... there is an alternative. -The Lux Standard Library includes a module called `library/lux/type/implicit`, which provides a macro called `\\`, that serves as an easier-to-use alternative to the `\` macro. +The Lux Standard Library includes a module called `library/lux/type/implicit`, which provides a macro called `##`, that serves as an easier-to-use alternative to the `#` macro. -What it does is that instead of requiring the implementation you want to use, it only requires the name of the function you want to call and the arguments. +What it does is that instead of requiring the implementation you want to use, it only requires the name of the method you want to call and the arguments. Then, at compile-time, it does some type-checking and some look-ups and selects an implementation for you that will satisfy those requirements. That implementation can come from the local-var environment, from the definitions in your own module, or even from the exported definitions of the modules you're importing. -That way, you can use `\` whenever you need precision and power, and use `\\` whenever you're doing more lightweight programming. +That way, you can use `#` whenever you need precision and power, and use `##` whenever you're doing more lightweight programming. Fantastic! @@ -39,30 +40,30 @@ This is how you'd use it: ```clojure ... Equality for nats -(\ nat.equivalence = x y) -## vs -(\\ = x y) +(# nat.equivalence = x y) +... vs +(## = x y) ``` ```clojure ... Equality for lists of nats -(\ (list.equivalence nat.equivalence) = +(# (list.equivalence nat.equivalence) = (list.indices 10) (list.indices 10)) ... vs -(\\ = (list.indices 10) (list.indices 10)) +(## = (list.indices 10) (list.indices 10)) ``` ```clojure ... Functor mapping -(\ list.functor each nat.inc (list.indices 10)) +(# list.functor each ++ (list.indices 10)) ... vs -(\\ each nat.inc (list.indices 10)) +(## each ++ (list.indices 10)) ``` --- Thanks to implicit polymorphism, you don't have to choose between power and ease of use. -Just do a static-import of the `library/lux/type/implicit` module, and you'll get the `\\` available and ready for action. +Just do a static-import of the `library/lux/type/implicit` module, and you'll get the `##` available and ready for action. diff --git a/documentation/book/the_lux_programming_language/appendix_g.md b/documentation/book/the_lux_programming_language/appendix_g.md index 9d6505c77..3f8113057 100644 --- a/documentation/book/the_lux_programming_language/appendix_g.md +++ b/documentation/book/the_lux_programming_language/appendix_g.md @@ -27,7 +27,7 @@ A good example of text parsers being used is the `library/lux/data/format/json` --- -However, programmers coming from other programming languages may be familiar with a different approach to test processing that has been very popular for a number of years now: regular expressions. +However, programmers coming from other programming languages may be familiar with a different approach to text processing that has been very popular for a number of years now: regular expressions. Regular expressions offer a short syntax to building text parsers that is great for writing quick text-processing tools. @@ -35,7 +35,7 @@ Lux also offers support for this style in its `library/lux/data/text/regex` modu The `regex` macro, in turn, compiles the given syntax into a text parser, which means you can combine both approaches, for maximum flexibility. -Here are some samples for regular expressions: +Here are some examples of regular expressions: ```clojure ... Literals diff --git a/documentation/book/the_lux_programming_language/appendix_h.md b/documentation/book/the_lux_programming_language/appendix_h.md index b20a6240f..3ed682294 100644 --- a/documentation/book/the_lux_programming_language/appendix_h.md +++ b/documentation/book/the_lux_programming_language/appendix_h.md @@ -139,10 +139,10 @@ Now that we have seen the available commands, it would be useful to see an annot ... "organization" ["name" "Lux Foundation" ... "url" "http://example.com/lux_foundation"] ]] - ... #contributors [["name" "Eduardo Julian" - ... "url" "https://github.com/eduardoejp" - ... "organization" ["name" "Lux Foundation" - ... "url" "http://example.com/lux_foundation"]]] + ... "contributors" [["name" "Eduardo Julian" + ... "url" "https://github.com/eduardoejp" + ... "organization" ["name" "Lux Foundation" + ... "url" "http://example.com/lux_foundation"]]] ] ... An optional list of repositories you can deploy to, given aliases so they're easy to refer to with the "deploy" command. @@ -198,11 +198,12 @@ Now that we have seen the available commands, it would be useful to see an annot ] "bibliotheca" - ["info" ["description" "Standard library for the Lux programming language."] + ["info" ["description" "Standard Library for the Lux programming language."] "test" "test/lux"] - "documentation" - ["program" "documentation/lux" + "scriptum" + ["info" ["description" "Documentation for the Standard Library for the Lux programming language."] + "program" "documentation/lux" "test" "documentation/lux"] "aedifex" diff --git a/documentation/book/the_lux_programming_language/chapter_1.md b/documentation/book/the_lux_programming_language/chapter_1.md index 0b93b5f29..29d2e8a70 100644 --- a/documentation/book/the_lux_programming_language/chapter_1.md +++ b/documentation/book/the_lux_programming_language/chapter_1.md @@ -20,8 +20,11 @@ The instructions for how to install it are at the link and it won't take much ti ## Question #2: How do I build Lux programs? Lux uses a custom-made build tool named _Aedifex_ which is configured using a declarative Lux-based syntax. + To install Aedifex, go to https://github.com/LuxLang/lux/tree/master/shell and download either `lux.bat` or `lux.sh` depending on whether you're on Windows or Linux/Mac. + Also download the `aedifex.jar` file, and place it (along with either of the scripts you downloaded) somewhere in your `PATH`. + Now, you'll have access to the `lux` command, which allows you to run Aedifex to build and test Lux projects. ## Question #3: How do I use Aedifex? @@ -35,36 +38,36 @@ These are the steps: 3. Add this to the project file: ```clojure -{#identity ["my.group" "my_project" "0.1.0-SNAPSHOT"] - #repositories ["https://oss.sonatype.org/content/repositories/snapshots/" - "https://oss.sonatype.org/service/local/staging/deploy/maven2/"] - - #dependencies [["com.github.luxlang" "stdlib" "0.6.0" "tar"]] - #compiler ["com.github.luxlang" "lux-jvm" "0.6.0" "jar"] - - #program "main"} - -## By default, Aedifex uses the "source" directory for finding your source-code. -## The file containing our program will be my_project/source/main.lux. - +["" + ["identity" ["my.group" "my_project" "0.1.0-SNAPSHOT"] + "repositories" ["https://oss.sonatype.org/content/repositories/snapshots/" + "https://oss.sonatype.org/service/local/staging/deploy/maven2/"] + + "dependencies" [["com.github.luxlang" "stdlib" "0.6.0" "tar"]] + "compiler" ["com.github.luxlang" "lux-jvm" "0.6.0" "jar"] + + "program" "main"]] + +... By default, Aedifex uses the "source" directory for finding your source-code. +... The file containing our program will be my_project/source/main.lux. ``` 4. Create `my_project/source/main.lux` and add this code to it: ```clojure -(.module: - [library - [lux #* - [program (#+ program:)] - ["." debug] - [control - ["." io]]]]) +(.using + [library + [lux "*" + [program {"+" program:}] + ["[0]" debug] + [control + ["[0]" io]]]]) (program: args (io.io (debug.log! "Hello, world!"))) -## As you can see, this is nothing more than a very simple "Hello, world!" program to test things out. -## Everything will be explained later in the rest of the book. +... As you can see, this is nothing more than a very simple "Hello, world!" program to test things out. +... Everything will be explained later in the rest of the book. ``` 5. In your terminal, go to `my_project`, and execute `lux build`. @@ -87,7 +90,8 @@ A directory named `target` will have been created, containing everything that wa ## Question #4: Where can I find documentation for Lux? -A specially useful source of information is [the documentation for the standard library](https://luxlang.github.io/lux/). +A specially useful source of information is [the documentation for the standard library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard). + You can also explore [the Lux repository on GitHub](https://github.com/LuxLang/lux) for more information. ## Question #5: Where do I talk about Lux? diff --git a/documentation/book/the_lux_programming_language/chapter_10.md b/documentation/book/the_lux_programming_language/chapter_10.md index 9d136bc2e..d2937a83b 100644 --- a/documentation/book/the_lux_programming_language/chapter_10.md +++ b/documentation/book/the_lux_programming_language/chapter_10.md @@ -11,6 +11,7 @@ There's a macro for _this_ and a macro for _that_. You use macros for defining stuff, for making types and functions and lists, for doing pattern-matching, and for control-flow. There's a macro for everything. + Yet, I haven't even shown a macro being defined yet. Quiet your mind, young grasshopper. You're about to be enlightened. @@ -24,6 +25,7 @@ The word **AST** stands for _Abstract Syntax Tree_. An AST is a representation of the syntax of a programming language, and compilers use them for the sake of analyzing the source-code (like, by type-checking it), and then generating the binary/byte-code output. You might think that's none of your business. + Only compiler writers have to worry about that stuff, right? Oh, you have much to learn, young grasshopper. @@ -34,7 +36,7 @@ Macros allow you to implement your own features in the language and to have them I mean, beyond the native syntax for writing numbers, text, variants, tuples and records, every single thing you have written so far has been macros. -Module statements? _Yep, macros_. +`.using` import statements? _Yep, macros_. Definition statements? _Yep, macros_. @@ -49,29 +51,31 @@ But macros work with the Lux _AST_, so that's the first thing you need to master Check it out: ```clojure -(type: #export Location - {#module Text - #line Nat - #column Nat}) - -(type: #export (Ann m v) - {#meta m - #datum v}) - -(type: #export (Code' w) - (#Bit Bit) - (#Nat Nat) - (#Int Int) - (#Rev Rev) - (#Frac Frac) - (#Text Text) - (#Identifier Name) - (#Tag Name) - (#Form (List (w (Code' w)))) - (#Tuple (List (w (Code' w)))) - (#Record (List [(w (Code' w)) (w (Code' w))]))) - -(type: #export Code +(type: .public Location + (Record + [#module Text + #line Nat + #column Nat])) + +(type: .public (Ann m v) + (Record + [#meta m + #datum v])) + +(type: .public (Code' w) + (Variant + {#Bit Bit} + {#Nat Nat} + {#Int Int} + {#Rev Rev} + {#Frac Frac} + {#Text Text} + {#Symbol Symbol} + {#Form (List (w (Code' w)))} + {#Variant (List (w (Code' w)))} + {#Tuple (List (w (Code' w)))})) + +(type: .public Code (Ann Location (Code' (Ann Location)))) ``` @@ -79,13 +83,15 @@ The `Code` type is the one you'll be interacting with, but all it does is wrap ( The real magic is in the `Code'` type, where you can see all the alternative syntactic elements. -The `Name` type (from the `library/lux` module), is just a `[Text Text]` type. -The first part holds the module/prefix of the identifier/tag, and the second part holds the name itself. So `library/lux/data/collection/list.reversed` becomes `["library/lux/data/collection/list" "reversed"]`, and `map` becomes `["" "map"]`. +The `Symbol` type (from the `library/lux` module), is just a `[Text Text]` type. + +The first part holds the module/prefix of the identifier/symbol, and the second part holds the name itself. + +So `library/lux/data/collection/list.reversed` becomes `["library/lux/data/collection/list" "reversed"]`, and `each` becomes `["" "each"]`. `list.reversed` would become `["library/lux/data/collection/list" "reversed"]` anyway, because aliases get resolved prior to analysis and macro expansion. -Forms are `(syntactic structures delimited by parentheses)`, and tuples are `[syntactic structures delimited by brackets]`. -Records `{#have lists #of pairs}` of `Code`s instead of single `Code`s, because everything must come in key-value pairs. +Forms are `(syntactic structures delimited by parentheses)`, variants are `{syntactic structures delimited by braces}`, and tuples are `[syntactic structures delimited by brackets]`. ## Quotations @@ -97,12 +103,13 @@ That sounds... exhausting. Well, we don't have to. There are actually many nice tools for making our lives easier. -One nice resource within our reach is the `library/lux/macro/code` module, which contains a variety of functions for building `Code` values, so we don't have to worry about cursors and variants and all that stuff. +One nice resource within our reach is the `library/lux/macro/code` module, which contains a variety of functions for building `Code` values, so we don't have to worry about locations and tags and all that stuff. But, even with that, things would get tedious. + Imagine having to generate an entire function definition (or something even larger), by having to call a bunch of functions for every small thing you want. -Well, don't fret. The Lux Standard Library already comes with a powerful mechanism for easily generating any code you want and you don't even need to import it (i.e. it's in the `library/lux` module). +Well, don't fret. [The Lux Standard Library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard) already comes with a powerful mechanism for easily generating any code you want and you don't even need to import it (i.e. it's in the `library/lux` module). ```clojure ... Quotation as a macro. @@ -113,7 +120,8 @@ Quotation is a mechanism that allows you to write the code you want to generate, The `'` macro is the simplest version, which does exactly what I just described. -This would turn the text `"YOLO"` into `[{#.module "" #.line 0 #.column 0} (#.Text "YOLO")]`. +This would turn the text `"YOLO"` into `[[.#module "" .#line 0 .#column 0] {.#Text "YOLO"}]`. + If you want to know what that would look like with the tools at `library/lux/macro/code`, it would be: `(text "YOLO")`. The beautiful thing is that `(' (you can use the "'" #macro [to generate {arbitrary code} without] worrying (about the "complexity")))`. @@ -121,7 +129,7 @@ The beautiful thing is that `(' (you can use the "'" #macro [to generate {arbitr ```clojure ... Hygienic quasi-quotation as a macro. ... Unquote (~) and unquote-splice (~+) must also be used as forms. -... All unprefixed identifiers will receive their parent module's prefix if imported; otherwise will receive the prefix of the module on which the quasi-quote is being used. +... All unprefixed symbols will receive their parent module's prefix if imported; otherwise will receive the prefix of the module on which the quasi-quote is being used. (` (def: (~ name) (function ((~ name) (~+ args)) (~ body)))) @@ -167,13 +175,16 @@ First, let's check the type of macros: ``` That does not look particularly useful. + What the hell is a `"#Macro"`? Fundamentally, all macros are functions. + However, the compiler cannot treat them as normal functions because they must be applied to code at compile-time, rather than run-time. For this reason, the Lux compiler must have some way to identify macros as distinct from functions. -It does so by labelling (_type-wise_) with this funky type. + +It does so by labelling macros (_type-wise_) with this funky type. There is, however, another type which elucidates what is going on with macros. @@ -183,67 +194,53 @@ There is, however, another type which elucidates what is going on with macros. ``` You might remember from the previous chapter that you can only access the `Lux` compiler state inside of macros. + Now, you can see how everything connects. You define macros by using the `macro:` macro (_so meta..._): ```clojure -(macro: .public (name_of tokens) - {#.doc (doc "Given an identifier or a tag, gives back a 2 tuple with the module and name parts, both as Text." - (name_of #.doc) - "=>" - ["library/lux" "doc"])} +(macro: .public (symbol tokens) (case tokens - (^template [<tag>] - [(^ (list [_ (<tag> [module name])])) - (\ meta.monad in (list (` [(~ (code.text module)) (~ (code.text name))])))]) - ([#Identifier] [#Tag]) + (^ (list [_ (.#Symbol [module name])])) + (# meta.monad in (list (` [(~ (code.text module)) (~ (code.text name))]))) _ - (meta.failure "Wrong syntax for 'name_of'."))) + (meta.failure "Wrong syntax for 'symbol'."))) ``` Here's another example: ```clojure (macro: .public (else tokens state) - {#.doc (doc "Allows you to provide a default value that will be used" - "if a (Maybe x) value turns out to be #.None." - "Note: the expression for the default value will not be computed if the base computation succeeds." - (else +20 (#.Some +10)) - "=>" - +10 - -------------------------- - (else +20 #.None) - "=>" - +20)} (case tokens (^ (.list else maybe)) - (let [g!temp (macro.gensym "")] - (#.Right [state (.list (` (case (~ maybe) - (#.Some (~ g!temp)) + (let [g!temp (macro.symbol "")] + (.#Right [state (.list (` (case (~ maybe) + {.#Some (~ g!temp)} (~ g!temp) - #.None + {.#None} (~ else))))])) _ - (#.Left "Wrong syntax for else"))) + (.#Left "Wrong syntax for else"))) ``` You may want to read [Appendix C](appendix_c.md) to learn about the pattern-matching macros used in these examples. As you can see, I'm using both quotation and the functions from the `library/lux/macro/code` module to generate code here. -I'm also using the `gensym` function from `library/lux/macro`, which generates unique identifiers for usage within code templates in order to avoid collision with any code provided by the user of the macro. +I'm also using the `symbol` function from `library/lux/macro`, which generates unique symbols for usage within code templates in order to avoid collision with any code provided by the user of the macro. + +A macro receives the raw `List` of `Code` tokens and must process them manually to extract any information it needs for code generation. -The macro receives the raw `List` of `Code` tokens and must process them manually to extract any information it needs for code generation. After that, a new `List` of `Code` tokens must be generated. -If there are any macros in the output, they will be _expanded_ further until only primitive/native syntax remains that the Lux compiler can then analyze and compile. +If any macros are used in the output, they will be _expanded_ further until only primitive/native syntax remains that the Lux compiler can then analyze and compile. You may be wondering what is the relationship between the `Macro` and `Macro'` types. - When you define a macro, you define it as a function, which is to say a `Macro'` type. + When you define a macro, you define it as a function, which is to say, a `Macro'` type. But once it has been defined, it gets re-labelled as a `Macro`, so that way the Lux compiler can distinguish it from other functions. This is all done for you by the `macro:` macro, so there's no need to worry about it. @@ -258,6 +255,7 @@ I mean, if I have to pattern-match against the code I receive; what happens when Clearly, analyzing the input code is far more difficult than generating it with the quoting macros. Don't worry about it. + Because in the next chapter, you will learn a more sophisticated method of macro definition that will make writing complex macros a breeze. See you in [the next chapter](chapter_11.md)! diff --git a/documentation/book/the_lux_programming_language/chapter_11.md b/documentation/book/the_lux_programming_language/chapter_11.md index a2d603ca0..e3e73d97a 100644 --- a/documentation/book/the_lux_programming_language/chapter_11.md +++ b/documentation/book/the_lux_programming_language/chapter_11.md @@ -18,15 +18,13 @@ For starters, it's the home of the (_code_) `Parser` type: ```clojure (type: .public Parser - {#.doc "A Lux code parser."} (//.Parser (List Code))) ``` -Which is based on the `Parser` type from the `library/lux/control/parser` module: +Which is based on the _generic_ `Parser` type from the `library/lux/control/parser` module: ```clojure (type: .public (Parser s a) - {#.doc "A generic parser."} (-> s (Try [s a]))) ``` @@ -41,21 +39,21 @@ There are many such code-parsers (and combinators) in the `library/lux/control/p Then, in the `library/lux/macro/syntax` module, there is a mechanism for defining macros: the `syntax:` macro. ```clojure -"A more advanced way to define macros than 'macro:'." -"The inputs to the macro can be parsed in complex ways through the use of syntax parsers." -"The macro body is also (implicitly) run in the Meta monad, to save some typing." -"Also, the compiler state can be accessed through the *compiler* binding." -(syntax: .public (object {.let [imports (class_imports *compiler*)]} - {.let [class_vars (list)]} - {super (opt (super_class_decl^ imports class_vars))} - {interfaces (tuple (some (super_class_decl^ imports class_vars)))} - {constructor_args (constructor_args^ imports class_vars)} - {methods (some (overriden_method_def^ imports))}) - (let [def_code ($_ text\compose "anon-class:" +... A more advanced way to define macros than 'macro:'. +... The inputs to the macro can be parsed in complex ways through the use of syntax parsers. +... The macro body is also (implicitly) run in the Meta monad, to save some typing. +... Also, the compiler state can be accessed through the *lux* binding. +(syntax: .public (object [.let [imports (class_imports *lux*) + class_vars (list)] + super (opt (super_class_decl^ imports class_vars)) + interfaces (tuple (some (super_class_decl^ imports class_vars))) + constructor_args (constructor_args^ imports class_vars) + methods (some (overriden_method_def^ imports))]) + (let [def_code ($_ text#composite "anon-class:" (spaced (list (super_class_decl$ (maybe.else object_super_class super)) - (with_brackets (spaced (list\map super_class_decl$ interfaces))) - (with_brackets (spaced (list\map constructor_arg$ constructor_args))) - (with_brackets (spaced (list\map (method_def$ id) methods))))))] + (with_brackets (spaced (list#each super_class_decl$ interfaces))) + (with_brackets (spaced (list#each constructor_arg$ constructor_args))) + (with_brackets (spaced (list#each (method_def$ id) methods))))))] (in (list (` ((~ (code.text def_code)))))))) ``` @@ -63,9 +61,11 @@ Then, in the `library/lux/macro/syntax` module, there is a mechanism for definin The difference between `macro:` and `syntax:` is that `syntax:` allows you to parse, in a structured manner, the inputs to your macro, thereby reducing considerably the complexity necessary for making _big_ macros. -Also, because you're using code-parsers for the hard work, you can write reusable parsers that you can share throughout your macros, if you want to have common syntax. You can even compose your parsers, or use parsers from someone else's library. +Also, because you're using code-parsers for the hard work, you can write reusable parsers that you can share throughout your macros, if you want to have common syntax. - There are already small modules under `library/lux/macro/syntax/` which house some reusable code-parsers and code-generators. +You can even compose your parsers, or use parsers from someone else's library. + + There are already some small modules under `library/lux/macro/syntax/` which house some reusable code-parsers and code-generators. Additionally, `syntax:` binds the `Lux` value on a variable called `*lux*`, so you can use it during your parsing. @@ -76,44 +76,44 @@ Here is an example: ```clojure ... Taken from library/lux/math/infix. -(.module: - {#.doc "Common mathematical constants and functions."} +(.using [library - [lux #* + [lux "*" [abstract - [monad (#+ do)]] + [monad {"+" do}]] [control - ["<>" parser ("#\." functor) - ["<.>" code (#+ Parser)]]] + ["<>" parser ("[1]#[0]" functor) + ["<[0]>" code {"+" Parser}]]] [data - ["." product] + ["[0]" product] [collection - ["." list ("#\." fold)]]] + ["[0]" list ("[1]#[0]" mix)]]] [macro - [syntax (#+ syntax:)] - ["." code]] + [syntax {"+" syntax:}] + ["[0]" code]] [math [number ["n" nat] ["i" int]]]]]) -(type: #rec Infix - (#Const Code) - (#Call (List Code)) - (#Unary Code Infix) - (#Binary Infix Code Infix)) +(type: Infix + (Rec Infix + (Variant + {#Const Code} + {#Call (List Code)} + {#Unary Code Infix} + {#Binary Infix Code Infix}))) (def: literal (Parser Code) ($_ <>.either - (<>\map code.bit <code>.bit) - (<>\map code.nat <code>.nat) - (<>\map code.int <code>.int) - (<>\map code.rev <code>.rev) - (<>\map code.frac <code>.frac) - (<>\map code.text <code>.text) - (<>\map code.identifier <code>.identifier) - (<>\map code.tag <code>.tag))) + (<>#each code.bit <code>.bit) + (<>#each code.nat <code>.nat) + (<>#each code.int <code>.int) + (<>#each code.rev <code>.rev) + (<>#each code.frac <code>.frac) + (<>#each code.text <code>.text) + (<>#each code.symbol <code>.symbol))) (def: expression (Parser Infix) @@ -122,29 +122,15 @@ Here is an example: ..literal (<code>.form (<>.many <code>.any)) (<code>.tuple (<>.and <code>.any expression)) - (<code>.tuple ($_ <>.either - (do <>.monad - [_ (<code>.this! (' #and)) - init_subject expression - init_op <code>.any - init_param expression - steps (<>.some (<>.and <code>.any expression))] - (in (product.right (list\fold (function (_ [op param] [subject [_subject _op _param]]) - [param [(#Binary _subject _op _param) - (` and) - (#Binary subject op param)]]) - [init_param [init_subject init_op init_param]] - steps)))) - (do <>.monad - [init_subject expression - init_op <code>.any - init_param expression - steps (<>.some (<>.and <code>.any expression))] - (in (list\fold (function (_ [op param] [_subject _op _param]) - [(#Binary _subject _op _param) op param]) - [init_subject init_op init_param] - steps))) - )) + (<code>.tuple (do <>.monad + [init_subject expression + init_op <code>.any + init_param expression + steps (<>.some (<>.and <code>.any expression))] + (in (list#mix (function (_ [op param] [_subject _op _param]) + [{#Binary _subject _op _param} op param]) + [init_subject init_op init_param] + steps)))) ))) ``` @@ -156,64 +142,43 @@ And here are some examples of syntax macros: (def: (prefix infix) (-> Infix Code) (case infix - (#Const value) + {#Const value} value - (#Call parts) + {#Call parts} (code.form parts) - (#Unary op subject) + {#Unary op subject} (` ((~ op) (~ (prefix subject)))) - (#Binary left op right) + {#Binary left op right} (` ((~ op) (~ (prefix right)) (~ (prefix left)))))) -(syntax: .public (infix {expr ..expression}) - {#.doc (example "Infix math syntax." - (infix [x i.* +10]) - (infix [[x i.+ y] i.* [x i.- y]]) - (infix [sin [x i.+ y]]) - (infix [[x n.< y] and [y n.< z]]) - (infix [#and x n.< y n.< z]) - (infix [(n.* 3 9) gcd 450]) - - "The rules for infix syntax are simple." - "If you want your binary function to work well with it." - "Then take the argument to the right (y) as your first argument," - "and take the argument to the left (x) as your second argument.")} +(syntax: .public (infix [expr ..expression]) (in (list (..prefix expr)))) ``` ```clojure -(syntax: .public (^sequence& {patterns (<code>.form (<>.many <code>.any))} - body - {branches (<>.some <code>.any)}) - {#.doc (example "Allows destructuring of sequences in pattern-matching expressions." - "Caveat emptor: Only use it for destructuring, and not for testing values within the sequences." - (let [(^sequence& x y z _tail) (some_sequence_func +1 +2 +3)] - (func x y z)))} - (with_identifiers [g!sequence] - (let [body+ (` (let [(~+ (list\join (list\map (function (_ pattern) - (list (` [(~ pattern) (~ g!sequence)]) - (` ((~! //.result) (~ g!sequence))))) - patterns)))] +(syntax: .public (^stream& [patterns (<code>.form (<>.many <code>.any)) + body <code>.any + branches (<>.some <code>.any)]) + (with_symbols [g!stream] + (let [body+ (` (let [(~+ (|> patterns + (list#each (function (_ pattern) + (list (` [(~ pattern) (~ g!stream)]) + (` ((~! //.result) (~ g!stream)))))) + list#conjoint))] (~ body)))] - (in (list& g!sequence body+ branches))))) + (in (list& g!stream body+ branches))))) ``` ```clojure -(syntax: .public (cond> {_ _reversed_} - prev - {else body^} - {_ _reversed_} - {branches (p.some (p.and body^ body^))}) - {#.doc (example "Branching for pipes." - "Both the tests and the bodies are piped-code, and must be given inside a tuple." - (|> +5 - (cond> [i.even?] [(i.* +2)] - [i.odd?] [(i.* +3)] - [(new> -1 [])])))} - (with_identifiers [g!temp] +(syntax: .public (cond> [_ _reversed_ + prev <code>.any + else body^ + _ _reversed_ + branches (<>.some (<>.and body^ body^))]) + (with_symbols [g!temp] (in (list (` (let [(~ g!temp) (~ prev)] (cond (~+ (do list.monad [[test then] branches] @@ -231,6 +196,7 @@ This may be a short chapter, but not because its subject is small. The opportunities that code-parsers open are fantastic, as it puts within your reach macros which would otherwise be much harder to implement correctly. Don't worry about complex inputs: your macros can implement entire new embedded programming languages if you want them to. + Code-parsers can generate any data-type you want, so you can easily translate the information in the input syntax to whatever data-model you need. But, now that we've spent 3 chapters about metaprogramming in Lux, I think it's fair that we clear our minds a little by looking at other subjects. diff --git a/documentation/book/the_lux_programming_language/chapter_12.md b/documentation/book/the_lux_programming_language/chapter_12.md index 94e072823..fe30e5295 100644 --- a/documentation/book/the_lux_programming_language/chapter_12.md +++ b/documentation/book/the_lux_programming_language/chapter_12.md @@ -22,7 +22,7 @@ Everything, in one way or another, revolves around _I/O_, and it is a pervasive Most programming languages you may be familiar with subscribe to this idea (either by choice of the language designer(s) or by simply following standard practice). -Here, we see operations which have side-effects (such as printing to standard output, or reading files, or connecting to another computer over the network) be treated like operations which lack them (like adding two numbers), with the side-effects being seen as some kind of magical property exhibited by those operations, which neither the language nor its libraries are obligated to handle in any special way. +Here, we see operations which have side-effects (such as printing to standard output, or reading files, or connecting to another computer over the network) be treated like operations which lack them (like adding two numbers), with the side-effects being seen as some kind of magical property exhibited by those operations, which neither the language nor its libraries are obligated to handle or track in any special way. Side-effects tend to be mentioned in documentation, but the lack of separation means that programmers must be wary of what they're using if the want to avoid the unfortunate consequences of careless coding. @@ -52,7 +52,7 @@ Lux chooses explicit _I/O_ as its underlying model. It may seem odd that I have to justify the choice of explicit _I/O_ in Lux; but, at the time of this writing, implicit _I/O_ is the industry standard, with many even doubting the benefits of an alternative approach. - The only _major_ language which also adopts this model is Haskell (from which Lux takes _heavy_ inspiration), in its efforts to maintain theoretical purity. + The only _major_ language which also adopts this model is Haskell (from which Lux takes _much inspiration_), in its efforts to maintain theoretical purity. ## How does explicit _I/O_ work? diff --git a/documentation/book/the_lux_programming_language/chapter_13.md b/documentation/book/the_lux_programming_language/chapter_13.md index 1faba95ee..8fbd4b85d 100644 --- a/documentation/book/the_lux_programming_language/chapter_13.md +++ b/documentation/book/the_lux_programming_language/chapter_13.md @@ -32,47 +32,47 @@ It's all done with the help of the `import:` macro: ... Allows importing JVM classes, and using them as types. ... Their methods, fields and enum options can also be imported. (import: java/lang/Object - ["#::." + ["[1]::[0]" (new []) (equals [java/lang/Object] boolean) - (wait [int] #io #try void)]) + (wait [int] "io" "try" void)]) ... Special options can also be given for the return values. -... #? means that the values will be returned inside a Maybe type. That way, null becomes #.None. -... #try means that the computation might throw an exception, and the return value will be wrapped by the Try type. -... #io means the computation has side effects, and will be wrapped by the IO type. -... These options must show up in the following order [#io #try #?] (although, each option can be used independently). +... "?" means that the values will be returned inside a Maybe type. That way, null becomes .#None. +... "try" means that the computation might throw an exception, and the return value will be wrapped inside the Try type. +... "io" means the computation has side effects, and will be wrapped inside the IO type. +... These options must show up in the following order ["io" "try" "?"] (although, each option can be used independently). (import: java/lang/String - ["#::." + ["[1]::[0]" (new [[byte]]) - (#static valueOf [char] java/lang/String) - (#static valueOf #as int_valueOf [int] java/lang/String)]) + ("static" valueOf [char] java/lang/String) + ("static" valueOf "as" int_valueOf [int] java/lang/String)]) (import: (java/util/List e) - ["#::." + ["[1]::[0]" (size [] int) (get [int] e)]) (import: (java/util/ArrayList a) - ["#::." + ["[1]::[0]" ([T] toArray [[T]] [T])]) ... The class-type that is generated is of the fully-qualified name. ... This avoids a clash between the java.util.List type, and Lux's own List type. ... All enum options to be imported must be specified. (import: java/lang/Character$UnicodeScript - ["#::." - (#enum ARABIC CYRILLIC LATIN)]) + ["[1]::[0]" + ("enum" ARABIC CYRILLIC LATIN)]) -... It should also be noted, the only types that may show up in method arguments or return values may be Java classes, arrays, primitives, void or type-vars. -... Lux types, such as Maybe cannot be named (otherwise, they'd be confused for Java classes). +... It should also be noted, the types that show up in method arguments or return values may only be Java classes, arrays, primitives, void or type-vars. +... Lux types, such as Maybe cannot be used (otherwise, they'd be confused for Java classes). (import: (lux/concurrency/async/JvmAsync A) - ["#::." + ["[1]::[0]" (resolve [A] boolean) (poll [] A) (wasResolved [] boolean) (waitOn [lux/Function] void) - (#static [A] make [A] (lux/concurrency/async/JvmAsync A))]) + ("static" [A] make [A] (lux/concurrency/async/JvmAsync A))]) ... Also, the names of the imported members will look like Class::member (java/lang/Object::new []) @@ -81,12 +81,14 @@ It's all done with the help of the `import:` macro: (java/util/List::size [] my_list) -java/lang/Character$UnicodeScript::LATIN +(java/lang/Character$UnicodeScript::LATIN) ``` This will be the tool you use the most when working with the JVM. -As you have noticed, it works by creating functions (and constant values) for all the class members you need. It also creates Lux type definitions matching the classes you import, so that you may easily refer to them when you write your own types later in regular Lux code. +As you have noticed, it works by creating functions for all the class members you need. + +It also creates Lux type definitions matching the classes you import, so that you may easily refer to them when you write your own types later in regular Lux code. It must be noted that `import:` requires that you only import methods and fields from their original declaring classes/interfaces. @@ -99,24 +101,24 @@ Normally, you'd use the `class:` macro: ```clojure ... Allows defining JVM classes in Lux code. ... For example: -(class: #final (TestClass A) [Runnable] +(class: "final" (TestClass A) [Runnable] ... Fields - (#private foo boolean) - (#private bar A) - (#private baz java/lang/Object) + ("private" foo boolean) + ("private" bar A) + ("private" baz java/lang/Object) ... Methods - (#public [] (new [value A]) [] - (exec - (:= ::foo #1) - (:= ::bar value) - (:= ::baz "") - [])) - (#public (virtual) java/lang/Object - "") - (#public #static (static) java/lang/Object - "") + ("public" [] (new [value A]) [] + (exec + (:= ::foo #1) + (:= ::bar value) + (:= ::baz "") + [])) + ("public" (virtual) java/lang/Object + "") + ("public" "static" (static) java/lang/Object + "") (Runnable [] (run) void - []) + []) ) ... The tuple corresponds to parent interfaces. @@ -140,9 +142,9 @@ And, for anonymous classes, you'd use `object`: (object [] [Runnable] [] (Runnable [] (run self) void - (exec - (do_something some_value) - []))) + (exec + (do_something some_value) + []))) ``` ## Special features @@ -160,8 +162,8 @@ And, for anonymous classes, you'd use `object`: ... Checks whether an object is an instance of a particular class. ... Caveat emptor: Can't check for polymorphism, so avoid using parameterized classes. (case (check java/lang/String "YOLO") - (#.Some value_as_string) - #.None) + {.#Some value_as_string} + {.#None}) ``` * Synchronizing threads. @@ -169,10 +171,10 @@ And, for anonymous classes, you'd use `object`: ```clojure ... Evaluates body, while holding a lock on a given object. (synchronized object-to-be-locked -(exec - (do something) - (do something else) - (finish the computation))) + (exec + (do something) + (do something else) + (finish the computation))) ``` Calling multiple methods consecutively @@ -187,7 +189,7 @@ Calling multiple methods consecutively `do_to` is inspired by Clojure's own doto macro. The difference is that, whereas Clojure's version pipes the object as the first argument to the method, Lux's pipes it at the end (which is where method functions take their object values). -The `library/lux/ffi` module offers much more, but you'll have to discover it yourself by heading over to the documentation for the Standard Library. +The `library/lux/ffi` module offers much more, but you'll have to discover it yourself by heading over to [the documentation for the Standard Library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard). --- diff --git a/documentation/book/the_lux_programming_language/chapter_14.md b/documentation/book/the_lux_programming_language/chapter_14.md index 8b3fc49d9..5552c3347 100644 --- a/documentation/book/the_lux_programming_language/chapter_14.md +++ b/documentation/book/the_lux_programming_language/chapter_14.md @@ -22,7 +22,7 @@ And watch out, because the amount of concurrency models may increase with future Anyhow, let's quit the chit-chat and dive in! -## Asynchronous computation +## Asynchronous computations This is my favorite one, because it can be used for almost anything, whereas I see the other modules as more specialized tools for certain use cases. @@ -38,7 +38,7 @@ I pretty much came to the conclusion that, for all intents and purposes, their s So, I just fused them. -And so, Lux implements promises in the `library/lux/control/concurrency/async` module, by means of the `Async` type. +And so, Lux implements futures/promises in the `library/lux/control/concurrency/async` module, by means of the `Async` type. You can run `IO` computations concurrently using the `future` function (which returns an `Async` that will contain the result of the computation). @@ -52,7 +52,7 @@ If you're curious about how that looks, take a peek: ```clojure (def: .public (and left right) - (All [a b] (-> (Async a) (Async b) (Async [a b]))) + (All (_ a b) (-> (Async a) (Async b) (Async [a b]))) (do monad [a left b right] @@ -61,7 +61,9 @@ If you're curious about how that looks, take a peek: Oh, and did I mention there are _combinators_ in that module? -If you didn't know there was some magic going on in the `Async` type, you wouldn't have suspected this was concurrent code. It looks just like any other old synchronous code you might have use with any other monad. +If you didn't know there was some magic going on in the `Async` type, you wouldn't have suspected this was concurrent code. + +It looks just like any other old synchronous code you might have use with any other monad. Pretty neat, huh? @@ -70,10 +72,12 @@ Pretty neat, huh? FRP is based on the idea of _values that change over time_, and structuring your programs to dynamically respond to those changes in a **reactive** way. The way its implemented in Lux is through the `Channel` type in `library/lux/control/concurrency/frp` (itself implemented on top of `Async`). + `Channel` instances are (potentially infinite) sequences of values that you can process in various ways as new values come into being. + `Channel` instances can be closed, but they may also go on forever if you'd like them to. -The `library/lux/control/concurrency/frp` module offers various functions for processing channels in various them (some of them generating new channels), and the `Channel` type also happens to be a monad, so you can write fairly complex and powerful code with it. +The `library/lux/control/concurrency/frp` module offers various functions for processing channels in various ways (some of them generating new channels), and the `Channel` type also happens to be a monad, so you can write fairly complex and powerful code with it. ## Software Transactional Memory @@ -92,13 +96,16 @@ Let's break down those last 3 terms: For those of you familiar with relational databases, this might remind you of their _ACID_ properties (with the caveat that Lux's STM is non-durable, as it works entirely in memory). The way it works is by running multiple transactions concurrently, and then committing their results to the affected variables. + If 2 transactions modify any common variables, the first one to commit wins, and the second one would be re-calculated to take into account the changes to those variables. -This implies that transactions are sensitive to some "version" of the variables they involve and that is correct. -That is the mechanism use to avoid collisions and ensure no inconsistencies ever arise. + +This implies that transactions are sensitive to some "version" of the variables they involve. + +That is the mechanism used to avoid collisions and ensure no inconsistencies ever arise. The relevant types are `Var`, which corresponds to the variables, and `STM` which are computations which transform transactions in some way and yield results. -Like `IO` and unlike `Async`, just writing `STM` computations doesn't actually run them, and you must call the `commit!` function to actually schedule the system to execute them (receiving a `Async` value for the result of the transaction). +Like `IO` and unlike `Async`, just writing `STM` computations doesn't actually run them, and you must call the `commit!` function to actually schedule the system to execute them (receiving an `Async` value for the result of the transaction). You may also `follow!` variables to get `Channel`s of their values if you're interesting in tracking them. @@ -130,8 +137,9 @@ To create an actor, you must first specify its `Behavior`: ```clojure (type: .public (Behavior o s) - {#on_init (-> o s) - #on_mail (-> (Mail s) s (Actor s) (Async (Try s)))}) + (Record + [#on_init (-> o s) + #on_mail (-> (Mail s) s (Actor s) (Async (Try s)))])) ``` These functions know how to initialize an actor, and how to react to incoming mail. @@ -144,8 +152,6 @@ But writing complex actors with multiple options for its messages can be messy w ... Defines a named actor, with its behavior and internal state. ... Messages for the actor must be defined after the on_mail handler. (actor: .public (stack a) - {} - (List a) ((on_mail mail state self) @@ -155,30 +161,30 @@ But writing complex actors with multiple options for its messages can be messy w .let [_ (debug.log! "AFTER")]] (in output))) - (message: .public (push {value a} state self) + (message: .public (push [value a] state self) Nat - (let [state' (#.Item value state)] - (async.resolved (#try.Success [state' (list.size state')]))))) + (let [state' {.#Item value state}] + (async.resolved {try.#Success [state' (list.size state')]})))) (actor: .public counter - {} - Nat - (message: .public (count! {increment Nat} state self) + (message: .public (count! [increment Nat] state self) Any (let [state' (n.+ increment state)] - (async.resolved (#try.Success [state' []])))) + (async.resolved {try.#Success [state' []]}))) - (message: .public (read! state self) + (message: .public (read! [] state self) Nat - (async.resolved (#try.Success [state state])))) + (async.resolved {try.#Success [state state]}))) ``` -For every method you define, a function will be defined in your module with the same name, and taking the same arguments, plus the actor. +For every message type you define, a function will be defined in your module with the same name, and taking the same arguments, plus the actor. + That function will always take the actor itself as its last argument, and will return an `Async` of the return type. -You can either die with a `#librarylux/control/try.Failure` value, or continue on to the next message with a `#librarylux/control/try.Success` containing an updated _actor state_, and a _return value_ for the method. +You can either die with a `librarylux/control/try.#Failure` value, or continue on to the next message with a `librarylux/control/try.#Success` containing an updated _actor state_, and a _return value_ for the method. + The type of the return value must match the type following the method signature. --- diff --git a/documentation/book/the_lux_programming_language/chapter_15.md b/documentation/book/the_lux_programming_language/chapter_15.md index c87494929..d30feb1cf 100644 --- a/documentation/book/the_lux_programming_language/chapter_15.md +++ b/documentation/book/the_lux_programming_language/chapter_15.md @@ -15,26 +15,29 @@ That is why there are so many different types of data-structures, and so many di But not all such implementations fit into the functional paradigm of keeping all your data immutable, and most implementations of data-structures are actually mutable, and meant for imperative programming. Now, let's not be naïve. + Everybody can figure out that making a data-structure immutable and just copying the whole thing every time you want to make a change would make those data-structures prohibitively expensive to use. Luckily for us, there is a way to have immutable data-structures that have _reasonable performance_. + The reason why they are _fast enough_ to be used, is that they are designed to re-use as many nodes as they can whenever an update needs to be done, in order to avoid wasteful re-work wherever possible. Make no mistake, they are still not as fast as their mutable counterparts (which you can still access by doing host-interop), but they are designed with high-performance in mind, and so they tend to be _fast enough_ for most use-cases. Lux offers a variety of these persistent data-structures. + Here are some examples: -## Rows +## Sequences - Located in `library/lux/data/collection/row`. + Located in `library/lux/data/collection/sequence`. These are similar to lists in that they are sequential data-structures, but there are a few differences: -1. Whereas lists prepend values to the front, rows append them to the back. -2. Random access on lists has a complexity of O(N), whereas it's O(log N) for rows. +1. Whereas lists prepend values to the front, sequences append them to the back. +2. Random access on lists has a complexity of O(N), whereas it's O(log N) for sequences. -Rows are a great substitute for lists whenever random access is a must, and their implementation ensures updates are as cheap as possible. +Sequences are a great substitute for lists whenever random access is a must, and their implementation ensures updates are as cheap as possible. ## Queues @@ -52,7 +55,7 @@ This is your standard key-value data-structure. Known by other names (tables, maps, etc), dictionaries give you efficient access and updating functionality. -All you need to do is give it a `Hash` instance (from `library/lux/abstract/hash`) for your _"key"_ type, and you're good to go. +All you need to do is give it a `Hash` implementation (from `library/lux/abstract/hash`) for your _"key"_ type, and you're good to go. ## Sets @@ -68,17 +71,18 @@ This is a killer combination. Instead of using mutable data-structures for your changing program data, you can just use persistent data-structures, with the mutability being delegated the the STM system. -This will make concurrently working with these data-structures a piece of cake, since you never have to worry about synchronizing/locking anything to avoid simultaneous updating, or any of the other crazy things programmers have to do to avoid data corruption. +This will make working concurrently with these data-structures a piece of cake, since you never have to worry about synchronizing/locking anything to avoid simultaneous updating, or any of the other crazy things programmers have to do to avoid data corruption. ## Arrays: the not-so-persistent data structures Located in `library/lux/data/collection/array`. -The `library/lux/data/collection/array` module features mutable arrays you can use if you need fast access and mutation and are willing to run the risks involved with using mutable data. +The `library/lux/data/collection/array` module features mutable arrays you can use if you need fast random access and mutation and are willing to run the risks involved with using mutable data. -Another possible use is to implement other data-structures (and, as it turns out, rows, dictionaries and sets all rely on arrays in their implementations). +Another possible use is to implement other data-structures (and, as it turns out, sequences and dictionaries both rely on arrays for their implementations). Also, it's worth nothing that in the _JVM_, this corresponds to _object arrays_. + If you want primitive arrays, you should check out the functionality for that in `library/lux/ffi`. --- diff --git a/documentation/book/the_lux_programming_language/chapter_16.md b/documentation/book/the_lux_programming_language/chapter_16.md index 0040876d0..78c46fcf5 100644 --- a/documentation/book/the_lux_programming_language/chapter_16.md +++ b/documentation/book/the_lux_programming_language/chapter_16.md @@ -17,36 +17,42 @@ The `library/lux/test` module contains the machinery you need to write unit-test Not only that, but the _Aedifex_ build tool for Lux also includes a command for testing: `lux test` How do you set that up? + Let's take a look at the `project.lux` file for the Lux standard library itself. ```clojure -{#identity ["com.github.luxlang" "stdlib" "0.6.0"] +["" + ["identity" ["com.github.luxlang" "stdlib" "0.6.0-SNAPSHOT"] + + "deploy_repositories" ["snapshots" "https://oss.sonatype.org/content/repositories/snapshots/" + "releases" "https://oss.sonatype.org/service/local/staging/deploy/maven2/"] -#deploy_repositories {"snapshots" "https://oss.sonatype.org/content/repositories/snapshots/" - "releases" "https://oss.sonatype.org/service/local/staging/deploy/maven2/"} + "repositories" ["https://oss.sonatype.org/content/repositories/snapshots/" + "https://oss.sonatype.org/service/local/staging/deploy/maven2/"]] -#repositories ["https://oss.sonatype.org/content/repositories/snapshots/" - "https://oss.sonatype.org/service/local/staging/deploy/maven2/"] + "jvm" + ["compiler" ["com.github.luxlang" "lux-jvm" "0.6.0-SNAPSHOT" "jar"]] -#compiler ["com.github.luxlang" "lux-jvm" "0.6.0" "jar"] -#description "Standard library for the Lux programming language." -#test "test/lux"} + "bibliotheca" + ["info" ["description" "Standard Library for the Lux programming language."] + "test" "test/lux"] + ] ``` -The `#test` parameter is similar to the `#program` parameter in that it specifies the name of a Lux module. +The `"test"` parameter specifies the name of a Lux module that serves as the entry point for testing. Here is a summary of the file: ```clojure -(.module: - [library - ["/" lux #* - [program (#+ program:)] - ["_" test (#+ Test)] - [control - ["." io]] - ... - ]]) +(.using + [library + ["/" lux "*" + [program {"+" program:}] + ["_" test {"+" Test}] + [control + ["[0]" io]] + ... + ]]) (program: args (io.io (_.run! (_.times 100 ..test)))) @@ -54,41 +60,45 @@ Here is a summary of the file: ``` A test suit consists of a `Test` (or a composite of as many `Test`s as you want), which is then `run!`. + The `times` combinator allows you to execute `Test`s several times. + This can be very useful when using random data generation within your tests, as each run of the tests will lead to the generation of different sorts of data. + This will help you cover many possible scenarios within the same test run, and perhaps uncover tricky corner cases you wouldn't have thought of. But where do those tests come from? + Nothing is being defined here. Let's take a look at the tests defined in a simpler module. -Well, the run macro, from lux/test pulls in all the tests from the imported modules to run them later once the program starts. +Well, the `run` macro, from `library/lux/test` pulls in all the tests from the imported modules to run them later once the program starts. To know how tests work, let's take a look at one of those modules. From `test/lux/data/collection/stack`. ```clojure -(.module: +(.using [library - [lux #* - ["_" test (#+ Test)] + [lux "*" + ["_" test {"+" Test}] [abstract - [monad (#+ do)] + [monad {"+" do}] [\\specification - ["$." equivalence] - ["$." functor (#+ Injection)]]] + ["$[0]" equivalence] + ["$[0]" functor {"+" Injection}]]] [control - ["." maybe]] + ["[0]" maybe]] [data - ["." bit ("#\." equivalence)]] + ["[0]" bit ("[1]#[0]" equivalence)]] [math - ["." random] + ["[0]" random] [number ["n" nat]]]]] [\\library - ["." /]]) + ["[0]" /]]) (def: (injection value) (Injection /.Stack) @@ -99,7 +109,7 @@ To know how tests work, let's take a look at one of those modules. (<| (_.covering /._) (_.for [/.Stack]) (do random.monad - [size (\ random.monad map (n.% 100) random.nat) + [size (# random.monad map (n.% 100) random.nat) sample (random.stack size random.nat) expected_top random.nat] ($_ _.and @@ -111,33 +121,33 @@ To know how tests work, let's take a look at one of those modules. (_.cover [/.size] (n.= size (/.size sample))) (_.cover [/.empty?] - (bit\= (n.= 0 (/.size sample)) + (bit#= (n.= 0 (/.size sample)) (/.empty? sample))) (_.cover [/.empty] (/.empty? /.empty)) (_.cover [/.value] (case (/.value sample) - #.None + {.#None} (/.empty? sample) - (#.Some _) + {.#Some _} (not (/.empty? sample)))) (_.cover [/.next] (case (/.next sample) - #.None + {.#None} (/.empty? sample) - (#.Some [top remaining]) - (\ (/.equivalence n.equivalence) = + {.#Some [top remaining]} + (# (/.equivalence n.equivalence) = sample (/.top top remaining)))) (_.cover [/.top] (case (/.next (/.top expected_top sample)) - (#.Some [actual_top actual_sample]) + {.#Some [actual_top actual_sample]} (and (same? expected_top actual_top) (same? sample actual_sample)) - #.None + {.#None} false)) )))) @@ -146,11 +156,15 @@ To know how tests work, let's take a look at one of those modules. There's a lot going on here. First of all, by using the `covering` macro, you can tell the test suit to track the coverage that your test suite has of a given module. -That way, if your tests miss some _exported_, the report you'll get after running the tests will tell you, so you can judiciously choose to either expand your coverage, or skip covering them. + +That way, if your tests miss some _exported/public_ definitions, the report you'll get after running the tests will tell you, so you can judiciously choose to either expand your coverage, or skip covering them. + The `for` and `cover` macros then signal whenever one or more definitions are being covered by a given test. Lux also defines some _specifications_, which are basically parameterizable tests, which implement consistent testing for various interfaces in the standard library. + That way, when testing an implementation of those interfaces, instead of having to copy-paste, or re-invent the testing every time, the specification is imported. + This enables consistent testing of implementations. `and` allows you to _sequentially_ compose `Test`s into a larger `Test`. @@ -160,6 +174,7 @@ You can also see an example of how to use randomness to generate sample data for --- If you want to learn more about how to write tests, feel free to check out the test-suite for the Lux standard library. + It's very comprehensive and filled with good examples. --- diff --git a/documentation/book/the_lux_programming_language/chapter_17.md b/documentation/book/the_lux_programming_language/chapter_17.md index b75df3cb2..6eefda219 100644 --- a/documentation/book/the_lux_programming_language/chapter_17.md +++ b/documentation/book/the_lux_programming_language/chapter_17.md @@ -1,14 +1,15 @@ # Chapter 17: Cross-platform Lux -_Where you will sail to exotic foreign platforms on the S.S. Lux._ +_Where you will sail to exotic foreign platforms aboard the S.S. Lux._ -It was always my desire for Lux to be a language that could be used to write software in multiple platforms. +It was always my desire for Lux to be a language that could be used to write software for multiple platforms. -I've always found it annoying to write a piece of software on one language, and then if it became necessary to run the software under different circumstances, a rewrite had to be done because the language in which the software was written was not capable of adapting to the new requirements. +I've always found it annoying to write a piece of software in one language, and then if it became necessary to run the software under different circumstances, a rewrite had to be done because the language in which the software was written was not capable of adapting to the new requirements. In theory, programming languages are universal. -Logic is logic, no matter the language in which it is expressed. -Instructions are instructions, no matter who executes them. + +* Logic is logic, no matter the language in which it is expressed. +* Instructions are instructions, no matter who executes them. And yet, in practice, you need JavaScript for the browser, and Swift for the IPhone (to give you some examples). @@ -16,10 +17,12 @@ Granted, with the advent of WebAssembly, it has now become possible to have any But there is still another type of constraint that is probably not going away any time soon. -If a programmer or a company write a piece of software on some language, they have made a very significant investment. -If tomorrow, they realize that another language might be a better fit for what they need, it might be too costly for them to rewrite their software on the new language. +If a programmer or a company write a piece of software in some language, they have made a very significant investment. + +If tomorrow, they realize that another language might be a better fit for what they need, it might be too costly for them to rewrite their software in a new language. Cross-platform support for Lux is not just about accessing as many devices as possible, but also about being able to inter-operate with as many languages as possible. + Being able to interact with and extend a Python server, or a Lua script. Ideally, I would like Lux to be able to inter-operate with every programming language that exists, thereby giving Lux programmers the capacity to write any program they want for any device; and the capacity to extend any codebase without being forcefully tied to any legacy language or implementation. @@ -50,10 +53,13 @@ The problem with this approach is that libraries written for one platform might This approach would allow Lux programmers to target different platforms, but it would make sharing code between them impossible at worst, and risky at best. Instead, I've designed the semantics and the feature set of Lux to be independent of any host platform. + When the feature-set of a platform fits well with the feature-set of Lux, I use the platform's resources to implement Lux's functionality. + And when Lux needs something the platform does not offer, but does not disallow either, I emulate the feature in other to ensure a consistent experience. -This means all of Lux's integers and naturals are 64-bit, on the JVM (which supports them), and even on JavaScript (where they are emulated). +This means all of Lux's naturals and integers are 64-bit, on the JVM (which supports them), and even on JavaScript (where they are emulated). + And this also means that Lux's concurrency mechanisms work as expected on the JVM (which allows multi-core processing), and on JavaScript/Python/Lua/Ruby (which don't). --- @@ -72,6 +78,7 @@ Here are the compilers for the alternative platforms: * For Ruby: `["com.github.luxlang" "lux-ruby" "0.6.0" "jar"]` You don't need to use any special command on Aedifex in order to compile Lux to any alternative platform. + Just set the compiler, and build/test your program as usual. For a thorough specification of what Aedifex can do, please refer to [Appendix H](appendix_h.md). @@ -95,8 +102,8 @@ First, let's go with the smaller mechanism: ```clojure (def: js "JavaScript") -(for {"JVM" (do jvm stuff) - ..js (do js stuff)} +(for ["JVM" (do jvm stuff) + ..js (do js stuff)] (do default stuff)) ``` @@ -124,18 +131,18 @@ To give you an example of `for` in action, here is a definition from the `librar ```clojure (def: .public (replaced pattern replacement template) (-> Text Text Text Text) - (for {@.old + (for [@.old (:as Text ("jvm invokevirtual:java.lang.String:replace:java.lang.CharSequence,java.lang.CharSequence" - (:as (primitive "java.lang.String") template) - (:as (primitive "java.lang.CharSequence") pattern) - (:as (primitive "java.lang.CharSequence") replacement))) + (:as (Primitive "java.lang.String") template) + (:as (Primitive "java.lang.CharSequence") pattern) + (:as (Primitive "java.lang.CharSequence") replacement))) @.jvm (:as Text ("jvm member invoke virtual" [] "java.lang.String" "replace" [] - (:as (primitive "java.lang.String") template) - ["Ljava/lang/CharSequence;" (:as (primitive "java.lang.CharSequence") pattern)] - ["Ljava/lang/CharSequence;" (:as (primitive "java.lang.CharSequence") replacement)])) + (:as (Primitive "java.lang.String") template) + ["Ljava/lang/CharSequence;" (:as (Primitive "java.lang.CharSequence") pattern)] + ["Ljava/lang/CharSequence;" (:as (Primitive "java.lang.CharSequence") replacement)])) ... TODO: Comment/turn-off when generating a JS compiler using a JVM-based compiler because Nashorn's implementation of "replaceAll" is incorrect. @.js (:as Text @@ -154,15 +161,15 @@ To give you an example of `for` in action, here is a definition from the `librar ... TODO @.scheme ... TODO @.common_lisp ... TODO @.r - } + ] ... Inefficient default (loop [left "" right template] (case (..split_by pattern right) - (#.Some [pre post]) - (recur ($_ "lux text concat" left pre replacement) post) + {.#Some [pre post]} + (again ($_ "lux text concat" left pre replacement) post) - #.None + {.#None} ("lux text concat" left right))))) ``` @@ -233,6 +240,7 @@ Other extensions are _host_-specific, and are only meant to be around for a spec Either way, Lux uses the same mechanism for all of them: the humble _extension_. You want to know what's the coolest thing about extensions? + _**You can write your own**_, and by doing so you can teach the compiler how to type-check, optimize and even generate code for your own new types of expressions. Sounds cool? diff --git a/documentation/book/the_lux_programming_language/chapter_18.md b/documentation/book/the_lux_programming_language/chapter_18.md index b132446d2..51f12bbbb 100644 --- a/documentation/book/the_lux_programming_language/chapter_18.md +++ b/documentation/book/the_lux_programming_language/chapter_18.md @@ -85,37 +85,37 @@ And so, Lux provides 4 different types of extensions. The first type of extension we'll see is the `Analysis` extension: ```clojure -(.module: - [library - [lux "*" - [extension {"+" [analysis: synthesis: generation:]}] - ["@" target - ["." jvm] - ["." js] - ["." python] - ["." lua] - ["." ruby]] - [abstract - ["." monad {"+" [do]}]] - [control - ["<>" parser - ["<.>" code] - ["<.>" analysis] - ["<.>" synthesis]]] - [data - [collection - ["." row]]] - [tool - [compiler - ["." phase] - [language - [lux - ["." analysis] - ["." synthesis] - ["." directive] - [phase - [analysis - ["." type]]]]]]]]]) +(.using + [library + [lux "*" + [extension {"+" [analysis: synthesis: generation:]}] + ["@" target + ["[0]" jvm] + ["[0]" js] + ["[0]" python] + ["[0]" lua] + ["[0]" ruby]] + [abstract + ["[0]" monad {"+" [do]}]] + [control + ["<>" parser + ["<[0]>" code] + ["<[0]>" analysis] + ["<[0]>" synthesis]]] + [data + [collection + ["[0]" sequence]]] + [tool + [compiler + ["[0]" phase] + [language + [lux + ["[0]" analysis] + ["[0]" synthesis] + ["[0]" directive] + [phase + [analysis + ["[0]" type]]]]]]]]]) (analysis: ("my triple" self phase archive [elementC <code>.any]) (do phase.monad @@ -135,7 +135,7 @@ Each type of extension takes a different type of input, and produces a different In the case of `Analysis` extensions, they take `(List Code)` as an input, and produce a single `Analysis` node as output. - By the way, _"analysis"_ is the name Lux gives to the process of type-checking. + By the way, _"analysis"_ is the name Lux gives to the process of type-checking and verifying program correctness. Here, we've got a _trivial_ extension where we take a single value, and we produce a triple of the same value. @@ -152,7 +152,7 @@ Since you are in total control of the type-checking that is happening, it is ent It is **very important** to be **careful**, when _implementing extensions_, that the output of those extensions is **correct in every situation**; because, _unlike with normal Lux code_, the compiler **cannot verify** that _your extension_ is not doing something that it shouldn't. -I have gifted you _Pandora's box_. +I have gifted you _promethean fire_. **DO NOT MAKE ME REGRET IT** ;) @@ -165,6 +165,13 @@ Also, you might have noticed that, besides the input code we're parsing, our ext --- ```clojure +(analysis: ("my quadruple" self phase archive [elementC <code>.any]) + (do phase.monad + [[type elementA] (type.with_inference + (phase archive elementC)) + _ (type.infer (.Tuple type type type type))] + (in {analysis.#Extension self (list elementA)}))) + (synthesis: ("my quadruple" self phase archive [elementA <analysis>.any]) (do phase.monad [elementS (phase archive elementA)] @@ -177,10 +184,6 @@ The `Synthesis` phase is where we do optimizations. Currently, the optimization infrastructure Lux provides is not very sophisticated, and much of it has yet to be properly exposed to programmers, so you'll probably not be working too much in this layer for now. -However, I'd like to use this opportunity to point out that when Lux encounters a usage of an extension during any phase, and it does not know this extension, it just proceeds to process the parameters to the extension, and then hands over the extension call, with the processed parameters, to the next phase. - -As a consequence, we can write a `Synthesis` extension without having to write the preceeding `Analysis` extension, because we can trust Lux to handle things reasonably and then use our `Synthesis` extension when its turn comes up. - --- ```clojure @@ -189,7 +192,7 @@ As a consequence, we can write a `Synthesis` extension without having to write t [[type elementA] (type.with_inference (phase archive elementC)) _ (type.infer (.Tuple type type type type type))] - (in (#analysis.Extension self (list elementA))))) + (in {analysis.#Extension self (list elementA)}))) (generation: ("my quintuple" self phase archive [elementS <synthesis>.any]) (do phase.monad @@ -209,6 +212,8 @@ As a consequence, we can write a `Synthesis` extension without having to write t Now, let's talk about the star of the show: _generation extensions_. _Generation_ is just a short way of saying _code-generation_, and it's the part of the compiler that generates the actual output that you eventually execute as a program. + Also, I'd like to use this opportunity to point out that when Lux encounters a usage of an extension during any phase, and it does not know this extension, it just proceeds to process the parameters to the extension, and then hands over the extension call, with the processed parameters, to the next phase. + As a consequence, we can write a generation extension without having to write the preceeding `Synthesis` extension, because we can trust Lux to handle things reasonably and then use our generation extension when its turn comes up. Generation extensions take a `(List Synthesis)` as input, and produce _suitable code_ as output. diff --git a/documentation/book/the_lux_programming_language/chapter_2.md b/documentation/book/the_lux_programming_language/chapter_2.md index 0bd29faf3..c4f566736 100644 --- a/documentation/book/the_lux_programming_language/chapter_2.md +++ b/documentation/book/the_lux_programming_language/chapter_2.md @@ -16,9 +16,11 @@ Modules contain a single _module statement_, various definitions and a few other Definitions are the top-level or global values that are declared within a module. -They may be of different types, such as constant values or functions, or even fancier things like types, signatures or structures (more on those in later chapters). +They may be of different types, such as constant values or functions, or even fancier things like types, interfaces or implementations (more on those in later chapters). -Also, definitions may be private to a module, or exported so other modules can refer to them. By default, all definitions are private. +Also, definitions may be private to a module, or exported so other modules can refer to them. + +By default, all definitions are private. ## Values @@ -41,13 +43,13 @@ Lux supports a variety of basic and composite values: ## Types -Types are descriptions of values that the compiler uses to make sure that programs are correct and invalid operations (such as multiplying two bits) are never performed. +Types are descriptions of values that the compiler uses to make sure that programs are correct, and invalid operations (such as multiplying two texts) are never performed. The thing that makes Lux types special is that they are first-class values, the same as bits and ints (albeit, a little more complex). They are data-structures, and they even have a type... named `Type` (_I know, it's **so** meta_). -We'll talk more about that in later chapters. +We'll talk more about types in later chapters. ## Macros @@ -60,8 +62,8 @@ We'll also explore macros further in later chapters. ## Comments ```clojure -## They look like this. -## They all start with 2 continuous # characters and go on until the end of the line. +... They look like this. +... They all start with 3 contiguous . characters and go on until the end of the line. ``` ## Expressions @@ -72,13 +74,13 @@ Data literals (like int, tuple or function literals) are expressions, but so are Macro calls can also be involved if the macro in question generates code that constitutes an expression. -## Statements +## Directives -Statements looks similar to expressions, except that their purpose is not to produce a value, but to communicate something to the compiler. +Directives looks similar to expressions, except that their purpose is not to produce a value, but to communicate something to the compiler. This is a bit of a fuzzy line, since some things which also communicate stuff to the compiler are actually expressions (for example, type annotations, which we'll see in next chapter). -Examples of statements are module statements and definitions of all kinds (such as program definitions). +Examples of directives are `.using` declarations at the top of modules, and definitions of all kinds (such as program definitions). ## Programs @@ -92,7 +94,7 @@ That action must be of type `(IO Any)`, which just means it is a synchronous pro Lux programs can have graphical user interfaces, and in the future they may run in various environments with much different means of interfacing with users, or other programs. -But as a bare minimum, the Lux standard library provides the means to implement command-line interfaces, through the functionality in the `lux/control/parser/cli` module. +But as a bare minimum, the Lux standard library provides the means to implement command-line interfaces, through the functionality in the `library/lux/control/parser/cli` module. That module implements a variety of parsers for implementing rich command-line argument processing, and you should definitely take a look at it once you're ready to write your first serious Lux program. @@ -109,28 +111,28 @@ Now, let's talk a bit more about the program we saw last time. In the previous chapter we compiled and ran a Lux program, but nothing has been explained yet. Let's review the code and see in detail what was done. ```clojure -(.module: - {#.doc "This will be our program's main module."} - [library - [lux #* - [program (#+ program:)] - ["." debug] - [control - ["." io]]]]) +... This will be our program's main module. +(.using + [library + [lux "*" + [program {"+" program:}] + ["[0]" debug] + [control + ["[0]" io]]]]) (program: args (io.io (debug.log! "Hello, world!"))) ``` -The first part of this program is the module declaration. +The first part of this program specifies which dependencies we're `using`. -All Lux modules automatically import the `library/lux` module, but they don't locally import every single definition, so everything would have to be accessed by using the `lux.` prefix or the `;` (short-cut) prefix. +All Lux modules automatically import the `library/lux` module, but they don't locally import every single definition, so everything would have to be accessed by using the `library/lux.` prefix or the `.` (short-cut) prefix. To avoid that, we import the `library/lux` module in a plain way. - By the way, what I just explained about the `library/lux` module is the reason why we couldn't just use the module macro as `module:`. + By the way, what I just explained about the `library/lux` module is the reason why we couldn't just use the `.using` macro as `using`. -Then we import the `library/lux/control/io` module. We're giving this module an alias, using that `"."` syntax. The way aliasing works here is that it replaces the period/dot with the short name of the import, and so `.` becomes `io`, and that is the alias given to the import. The same process happens when we import the `library/lux/debug` module. This might seems weird and sort of useless, but the aliasing syntax has some more features and flexibility, enabling you to have your own naming convention when importing modules. +Then we import the `library/lux/control/io` module. We're giving this module an alias, using that `"[0]"` syntax. The way aliasing works here is that it replaces the `[0]` with the short name of the import, and so `[0]` becomes `io`, and that is the alias given to the import. The same process happens when we import the `library/lux/debug` module. This might seems weird and sort of useless, but the aliasing syntax has some more features and flexibility, enabling you to have your own naming convention when importing modules. Notice how we express nested modules (up to arbitrary depths) by simply nesting in brackets. @@ -144,7 +146,7 @@ We're defining the entry point of our program (what in many other languages is r Suffice it to say that the `debug.log!` function will produce a value of type `Any` after printing/logging our `"Hello, world!"` text, and the `io.io` macro will wrap that in the `IO` type. -That `(IO Any)` value will then be run by the system at run-time, giving us the result we want. +That `(IO Any)` value will then be _run_ by the system at run-time, giving us the result we want. --- diff --git a/documentation/book/the_lux_programming_language/chapter_3.md b/documentation/book/the_lux_programming_language/chapter_3.md index a08fbc618..8565e24e3 100644 --- a/documentation/book/the_lux_programming_language/chapter_3.md +++ b/documentation/book/the_lux_programming_language/chapter_3.md @@ -4,13 +4,13 @@ _Where you will learn the what Lux code is made of._ --- -# Syntax for data-types +## Syntax for data-types * `Bit`s look like this: ```clojure -#0 ## false -#1 ## true +#0 ... false +#1 ... true ``` * `Nat`s look like this: @@ -63,8 +63,8 @@ _Where you will learn the what Lux code is made of._ * Variants look like this: ```clojure -#Foo -(#Bar 10 +20.0 "thirty") +{#Foo} +{#Bar 10 +20.0 "thirty"} ``` * Tuples look like this: @@ -76,30 +76,20 @@ _Where you will learn the what Lux code is made of._ * Records look like this: ```clojure -{#name "Lux" #paradigm #Functional #platforms (list #JVM)} +[#name "Lux" #paradigm {#Functional} #platforms (list {#JVM})] ``` **Note**: As you can see, commas (`,`) can be used as separators for the numeric literals. --- -From looking at this, we can see a few interesting bits we haven't discussed. - -One is that the hash (`#`) character is overloaded. - -In the last chapter we saw it being used for comments, but now we're seeing it being used as a prefix for _some weird "label" thingies_ (more on that in a moment). - -To avoid reserving many characters for the language, Lux overloads the hash (`#`) character in situations where it can be used unambiguously. That way, most characters can be used by anyone without fear of stepping on the feet of the language. - ---- - -Regarding _those label thingies_ we saw earlier, they're called **tags**, and the reason they're not mentioned as part of Lux's data-types is that they're not really data-types; they're just part of the language syntax. +Regarding _those label thingies_ we saw earlier, they're called **labels**, and the reason they're not mentioned as part of Lux's data-types is that they're not really data-types; they're just part of the language syntax. They're used as part of the syntax for data-types, but they're not data-types in themselves. -Also, you can't just use anything you want as a tag, as you first have to declare them. +Also, you can't just use anything you want as a label, as you first have to declare them. -We'll talk more about tags a bit later, when we talk about defining types. +We'll talk more about labels a bit later, when we talk about defining types. --- @@ -111,41 +101,57 @@ Also, just from looking at the syntax for unit and tuples, you can see that they In the section for variants, you can see 2 different alternatives, and you might wonder how do they differ. -Well, a variant is a pair of a tag and a _single_ value. That's right, I said **single** value; so you might be wondering how come we're associating 3 values with the `#Bar` tag. +Well, a variant is a pair of a _label_ (referred to as a _tag_) and a _single_ value. -It's pretty simple, actually. Whenever you're trying to create a variant with more than one value, Lux just wraps all the values inside a tuple for you. +That's right, I said **single** value; so you might be wondering how come we're associating 3 values with the `#Bar` tag. -So, `(#Bar 10 +20.0 "thirty")` is the same as `(#Bar [10 +20.0 "thirty"])`. +It's pretty simple, actually: whenever you're trying to create a variant with more than one value, Lux just wraps all the values inside a tuple for you. + +So, `{#Bar 10 +20.0 "thirty"}` is the same as `{#Bar [10 +20.0 "thirty"]}`. Now, you might be thinking: _what's up with that `#Foo` variant?_ -Well, sometimes you only care about a variant for its tag, and not for any value it may hold (for example, if you're trying to use a variant type as an enumeration). In that case, you'll want to pair the tag with an empty value (since it has to be paired with something). +Well, sometimes you only care about a variant for its tag, and not for any value it may hold (for example, if you're trying to use a variant type as an enumeration). + +In that case, you'll want to pair the tag with an empty value (since it has to be paired with something). -That's right! You've just witnessed **unit** value in action and you didn't even know it. If you just write the name of the tag without any parentheses, Lux will stick a **unit** in there for you. +That's right! You've just witnessed **unit** value in action and you didn't even know it. -That means `#Foo` is the same as ``(#Foo [])``. +If you just write the tag in braces, with nothing else in there, Lux will stick a **unit** in there for you. + +That means `{#Foo}` is the same as `{#Foo []}`. --- You might have noticed that I mentioned **records** in this chapter, but not in the previous chapter, where I also talked about the basic data-types Lux offers. -The reason is that records are a bit of a magic trick in Lux. That means records are not really a data-type that's distinct from the other ones. In fact, records just offer you an alternative syntax for writing tuples. +The reason is that records are a bit of a magic trick in Lux. + +That means records are not really a data-type that's distinct from the other ones. -That's right! `{#name "Lux" #paradigm #Functional #platforms (list #JVM)}` could mean the same as `["Lux" #Functional (list #JVM)]`, depending on the ordering imposed by the tags. +In fact, records just offer you an alternative syntax for writing tuples. + +That's right! `[#name "Lux" #paradigm {#Functional} #platforms (list {#JVM})]` could mean the same as `["Lux" {#Functional} (list {#JVM})]`, depending on the ordering imposed by the _slots_ (which is the name given to record _labels_). --- -Remember when I said that you needed to declare your tags? Well, depending on the order in which you declare them, that means that `#name` could point to the first element in the tuple, or to another position entirely. Also, in the same way that tags have a numeric value when it comes to their usage in tuples/records, that's also the case for variants. +Remember when I said that you needed to declare your labels? + +Well, depending on the order in which you declare them, that means that `#name` could point to the first element in the tuple, or to another position entirely. -For example, the `List` type has two tags: `#.End` and `#.Item`. The `#.End` tag has value 0, while the `#.Item` tag has value 1. That's what allows Lux to the able to identify which option it is working with at runtime when you're dealing with variants. +Also, in the same way that labels have a numeric value when it comes to their usage in tuples/records, that's also the case for variants. -Tags belong to the module in which they were declared, and you must use the module name (or an alias) as a prefix when using tags. That is why I've written `#.End` and `#.Item`, instead of `#End` and `#Item`. However, you may forgo the prefixes if you're referring to tags which were defined in the same module in which they're being used. +For example, the `List` type has two tags: `.#End` and `.#Item`. -Finally, you may have noticed that, unlike all other data-types, variants re-use some syntax that you're already seen before: _the parentheses_. Clearly, we didn't build our program by creating a bunch of variants, so, what's going on? +The `.#End` tag has value 0, while the `.#Item` tag has value 1. -Well, the parenthesis delimit the syntax of what is called a **form** in Lux. This is actually an old concept that's very familiar to those with experience with other Lisp-like languages. Basically, a form is a composite expression or statement. +That's what allows Lux to the able to identify which option it is working with at runtime when you're dealing with variants. -When the form starts with a tag, Lux interprets that to be a variant. +Labels belong to the module in which they were declared, and you must use the module name (or an alias) as a prefix when using tags. + +That is why I've written `.#End` and `.#Item`, instead of `#End` and `#Item`. + +However, you may forgo the prefixes if you're referring to tags which were defined in the same module in which they're being used. ## Types for data-types @@ -155,7 +161,7 @@ Patience, young grasshopper. We'll talk about those in the next chapter. For now, let's talk about **types**. -The type-annotation macro is called `:` (I know, _real cute_). You use it like this `(: Some_Type some_value)`. +The type-annotation macro is called `:` (I know, _real cute_). You use it like this: `(: Some_Type some_value)`. There is also a separate macro for type-coerciones that's called `:as`, which is used the same way. However, you should probably steer clear off that one, unless you know what you're doing, since you can trick the compiler into thinking a value belongs to any type you want by using it. @@ -171,38 +177,41 @@ Now that we know about type annotations, I'll show you some types by giving you (: Text "YOLO") (type: Some_Enum - #primitive - #tuple - #variant) + (Variant + {#Primitive} + {#Variant} + {#Tuple})) (: [Int [Text Some_Enum] Bit] - [10 ["nested" #tuple] .false]) + [10 ["nested" {#Tuple}] .false]) (type: Quux - #Foo - (#Bar Int Frac Text)) + (Variant + {#Foo} + {#Bar Int Frac Text})) -(: Quux #Foo) +(: Quux {#Foo}) -(: Quux (#Bar 10 +20.0 "thirty")) +(: Quux {#Bar 10 +20.0 "thirty"}) (type: Lang - {#name Text - #paradigm Paradigm - #platforms (List Platform)}) + (Record + [#name Text + #paradigm Paradigm + #platforms (List Platform)])) (: Lang - {#name "Lux" - #paradigm #Functional - #platforms (list #JVM)}) + [#name "Lux" + #paradigm {#Functional} + #platforms (list {#JVM})]) (: Lang - ["Lux" #Functional (list #JVM)]) + ["Lux" {#Functional} (list {#JVM})]) (: [Text Paradigm (List Platform)] - {#name "Lux" - #paradigm #Functional - #platforms (list #JVM)}) + [#name "Lux" + #paradigm {#Functional} + #platforms (list {#JVM})]) ``` By the way, the value of a type-annotation or a type-coearcion expression is just the value being annotated/coerced. So `(: Bit #1)` simply yields `#1`. @@ -211,13 +220,15 @@ _What is that `type:` thingie?_ It's just a macro for defining types. We'll learn more about it in a future chapter. -The tags that get mentioned in the type definition get automatically declared, and the order in which they appear determines their value. `#Foo` came first, so it's value is 0. `#Bar`, as you may guess, gets the value 1. +The labels that get mentioned in the type definition get automatically declared, and the order in which they appear determines their value. + +`#Foo` came first, so its value is 0. `#Bar`, as you may guess, gets the value 1. Also, you might be wondering what's the difference between `List` and `list`. Well, the first one is the type of lists (or a type-constructor for list types, however you want to look at it). The second one is a _macro_ for constructing actual list values. `List` can only take one argument (the type of the list elements), while `list` can take any number of arguments (the elements that make up the list value). --- -Again, we haven't mentioned functions. But if you're impatient to learn about them, just click the link below. +Again, we haven't mentioned functions. But the next chapter is about them. See you in [the next chapter](chapter_4.md)! diff --git a/documentation/book/the_lux_programming_language/chapter_4.md b/documentation/book/the_lux_programming_language/chapter_4.md index 8387715c6..ebb58a78e 100644 --- a/documentation/book/the_lux_programming_language/chapter_4.md +++ b/documentation/book/the_lux_programming_language/chapter_4.md @@ -11,26 +11,27 @@ No worries. You're about to find out! First, let's talk about how to make your own functions. ```clojure -(function (plus_two x) (inc (inc x))) +(function (plus_two x) (++ (++ x))) ``` Here's the first example. + This humble function increases a `Nat` twice. -What is it's type? +What is its type? Well, I'm glad you asked. ```clojure (: (-> Nat Nat) - (function (plus_two x) (inc (inc x)))) + (function (plus_two x) (++ (++ x)))) ``` That `->` thingie you see there is a macro for generating function types. It works like this: ```clojure -(-> arg1 arg2 ... argN return) +(-> arg1 arg2 ,,, argN return) ``` The types of the arguments and the return type can be any type you want (even other function types, but more on that later). @@ -38,8 +39,8 @@ The types of the arguments and the return type can be any type you want (even ot How do we use our function? Just put it at the beginning for a form: ```clojure -((function (plus_two x) (inc (inc x))) 5) -## => 7 +((function (plus_two x) (++ (++ x))) 5) +... => 7 ``` Cool, but... inconvenient. @@ -54,7 +55,7 @@ Well, we just need to define it! (def: plus_two (: (-> Nat Nat) (function (_ x) - (inc (inc x))))) + (++ (++ x))))) ``` Or, alternatively: @@ -63,7 +64,7 @@ Or, alternatively: (def: plus_two (-> Nat Nat) (function (_ x) - (inc (inc x)))) + (++ (++ x)))) ``` Notice how the `def:` macro can take the type of its value before the value itself, so we don't need to wrap it in the type-annotation `:` macro. @@ -72,7 +73,7 @@ Now, we can use the square function more conveniently. ```clojure (plus_two 7) -## => 9 +... => 9 ``` Nice! @@ -82,7 +83,7 @@ Also, I forgot to mention another form of the `def:` macro which is even more co ```clojure (def: (plus_two x) (-> Nat Nat) - (inc (inc x))) + (++ (++ x))) ``` The `def:` macro is very versatile, and it allows us to define constants and functions. @@ -91,7 +92,9 @@ If you omit the type, the compiler will try to infer it for you, and you will ge You will also get an error if you add the types but there's something funny with your code and things don't match up. -Error messages keep improving on each release, but in general you'll be getting the **file, line and column** on which an error occurs, and if it's a type-checking error, you'll usually get the type that was expected and the actual type of the offending expression... in multiple levels, as the type-checker analyses things in several steps. That way, you can figure out what's going on by seeing the more localized error alongside the more general, larger-scope error. +Error messages keep improving on each release, but in general you'll be getting the **file, line and column** on which an error occurs, and, if it's a type-checking error, you'll usually get the type that was expected and the actual type of the offending expression... in multiple levels, as the type-checker analyses things in several steps. + +That way, you can figure out what's going on by seeing the more localized error alongside the more general, larger-scope error. --- @@ -104,7 +107,7 @@ Check this one out: (-> Nat Nat Nat) (if (n.= 0 n) acc - (factorial' (n.* n acc) (dec n)))) + (factorial' (n.* n acc) (-- n)))) (def: (factorial n) (-> Nat Nat) @@ -117,14 +120,16 @@ And if we just had the function expression itself, it would look like this: (function (factorial' acc n) (if (n.= 0 n) acc - (factorial' (n.* n acc) (dec n)))) + (factorial' (n.* n acc) (-- n)))) ``` -Here, we're defining the `factorial` function by counting down on the input and multiplying some accumulated value on each step. We're using an intermediary function `factorial'` to have access to an accumulator for keeping the in-transit output value, and we're using an `if` expression (one of the many macros in the `library/lux` module) coupled with a recursive call to iterate until the input is 0 and we can just return the accumulated value. +Here, we're defining the `factorial` function by counting down on the input and multiplying some accumulated value on each step. + +We're using an intermediary function `factorial'` to have access to an accumulator for keeping the in-transit output value, and we're using an `if` expression (one of the many macros in the `library/lux` module) coupled with a recursive call to iterate until the input is 0 and we can just return the accumulated value. As it is (hopefully) easy to see, the `if` expression takes a _test_ expression as its first argument, a _"then"_ expression as its second argument, and an _"else"_ expression as its third argument. -Both the `n.=` and the `n.*` functions operate on `Nat`s, and `dec` is a function for decreasing `Nat`s; that is, to subtract 1 from the `Nat`. +Both the `n.=` and the `n.*` functions operate on `Nat`s, and `--` is a function for decreasing `Nat`s; that is, to subtract 1 from the `Nat`. You might be wondering what's up with that `n.` prefix. @@ -133,32 +138,32 @@ The reason it exists is that Lux's arithmetic functions are not polymorphic on t If you import the module for `Nat` numbers, like so: ```clojure -(.module - [library - [lux - [math - [number - ["n" nat]]]]]) +(.using + [library + [lux + [math + [number + ["n" nat]]]]]) ``` -You can access all definitions in the "library/lux/math/number/nat" module by just using the "n." prefix. +You can access all definitions in the `library/lux/math/number/nat` module by just using the `n.` prefix. - I know it looks annoying, but later in the book you'll discover a way to do math on any Lux number without having to worry about types and prefixes. +Also, it might be good to explain that Lux functions can be partially applied. -Also, it might be good to explain that Lux functions can be partially applied. This means that if a function takes N arguments, and you give it M arguments, where M < N, then instead of getting a compilation error, you'll just get a new function that takes the remaining arguments and then runs as expected. +This means that if a function takes N arguments, and you give it M arguments, where M < N, then instead of getting a compilation error, you'll just get a new function that takes the remaining arguments and then runs as expected. That means, our factorial function could have been implemented like this: ```clojure (def: factorial (-> Nat Nat) - (factorial' +1)) + (factorial' 1)) ``` Or, to make it shorter: ```clojure -(def: factorial (factorial' +1)) +(def: factorial (factorial' 1)) ``` Nice, huh? diff --git a/documentation/book/the_lux_programming_language/chapter_5.md b/documentation/book/the_lux_programming_language/chapter_5.md index a391f99f2..a7d09016a 100644 --- a/documentation/book/the_lux_programming_language/chapter_5.md +++ b/documentation/book/the_lux_programming_language/chapter_5.md @@ -16,7 +16,9 @@ But before we head into that, let's first see 2 weaker mechanisms for branching ### If -We've already met the humble `if` expression in the previous chapter. As explained there, the expression takes the following form: +We've already met the humble `if` expression in the previous chapter. + +As explained there, the expression takes the following form: ```clojure (if test @@ -42,7 +44,7 @@ Here is an example: "Aw, hell naw!") ``` -So, both branches must produce the same type for the type-checker to let it pass. +So, both branches must produce values of the same type for the type-checker to let it pass. ### Cond @@ -67,8 +69,7 @@ And, in terms of types, it looks like this: (: Bit test-2) (: X then-2) ... (: Bit test-n) (: X then-n) - (: X else) - )) + (: X else))) ``` Here is an example: @@ -76,17 +77,17 @@ Here is an example: ```clojure (cond (n.even? num) "even" (n.odd? num) "odd" - ## else-branch + ... else-branch "???") ``` -So, it's easy to intuit how `cond` would desugar into several nested `if` expressions. +So, it's easy to intuit how `cond` would de-sugar into several nested `if` expressions. Also, I'd like to point out that both `if` and `cond` are macros, instead of native Lux syntax. The reason for that is simply that they can both be implemented in terms of pattern-matching. -## Pattern-Matching +## Pattern-matching Some of you may not be familiar with the concept of pattern-matching if you come from non-functional programming languages, or from FP languages that lack pattern-matching (e.g. _Clojure_). @@ -103,13 +104,17 @@ For instance, the `factorial'` function you saw in the previous chapter could ha (-> Nat Nat Nat) (case n 0 acc - _ (factorial' (n.* n acc) (dec n)) + _ (factorial' (n.* n acc) (-- n)) )) ``` -As you may imagine, `case` is the pattern-matching macro. It takes the data you want to pattern-match against (in this case, the `n` variable), and then tests it against several patterns until it finds a match, in which case it executes its branch. +As you may imagine, `case` is the pattern-matching macro. + +It takes the data you want to pattern-match against (in this case, the `n` variable), and then tests it against several patterns until it finds a match, in which case it executes its branch. + +Here, we test if `n` equals `0`. If it does, we just return the `acc` value. -Here, we test if `n` equals `0`. If it does, we just return the `acc` value. Otherwise, we have a _default_ branch with a pattern that doesn't test anything called `_`. That will handle the case where the input is greater than 0. +Otherwise, we have a _default_ branch with a pattern that doesn't test anything called `_`. That will handle the case where the input is greater than 0. The _"default"_ branch works because we're binding the value of `n` onto a variable called `_`, and binding always succeeds, which is why we can use that branch as a default. @@ -120,7 +125,7 @@ However, since it is binding a variable, that means we could have used `_` inste (-> Nat Nat Nat) (case n 0 acc - _ (factorial' (n.* _ acc) (dec _)) + _ (factorial' (n.* _ acc) (-- _)) )) ``` @@ -144,17 +149,20 @@ Here are a couple more examples so you can see the possibilities. ```clojure (case (list 1 2 3) - (#.Item x (#.Item y (#.Item z #.End))) - (#.Some (n.+ x (n.* y z))) + {.#Item x {.#Item y {.#Item z {.#End}}}} + {.#Some (n.+ x (n.* y z))} _ - #.None) + {.#None}) ``` -In the first example, you'll notice that we have rewritten the prior `if` example in terms of pattern-matching. Also, you'll notice the introduction of a new macro, called `let`. +In the first example, you'll notice that we have rewritten the prior `if` example in terms of pattern-matching. + +Also, you'll notice the introduction of a new macro, called `let`. `let` is a simple way to create local-variables in Lux. -It's syntax looks like this: + +Its syntax looks like this: ```clojure (: X (let [var-1 expr-1 @@ -176,25 +184,31 @@ The `List` type is defined like this: ```clojure (type: (List a) - #End - (#Item a (List a))) + {#End} + {#Item a (List a)}) ``` -#.End represents the empty list, while #.Item constructs a list by prepending an element to the beginning of another list. +`.#End` represents the empty list, while `.#Item` constructs a list by prepending an element to the beginning of another list. With pattern-matching, we're opening our list up to 3 levels in order to extract its 3 items and do a simple math calculation. -If the match succeeds, we produce a value of type `(Maybe Int)` by wrapping our result with the `#.Some` tag, from the `Maybe` type. If the match fails, we just produce nothing, by using the `#.None` tag, also from the `Maybe` type. +If the match succeeds, we produce a value of type `(Maybe Int)` by wrapping our result with the `.#Some` tag, from the `Maybe` type. + +If the match fails, we just produce nothing, by using the `.#None` tag, also from the `Maybe` type. While `List` allows you to group an arbitrary number of values into a single structure, `Maybe` is for values you may or may not have. Also, you may have noticed how different (and uncomfortable!) it is to pattern-match against a `List`, since you have to use its real syntax, with its tags; whereas to build the list we can just piggy-back on the `list` macro. -Don't worry too much about it, because there's a better way to do it that also allows us to use the `list` macro. If you're curious about it, head over to [Appendix C](appendix_c.md) to learn more about pattern-matching macros. +Don't worry too much about it, because there's a better way to do it that also allows us to use the `list` macro. If you're curious about it, head over to [Appendix C](appendix_c.md) to learn more about **pattern-matching macros**. ## Looping -Alright. So, we know several ways to branch, and also how to bind variables. But we know life isn't just about making decisions. Sometimes, you just have to do your work over and over again until you're done. +Alright. So, we know several ways to branch, and also how to bind variables. + +But we know life isn't just about making decisions. + +Sometimes, you just have to do your work over and over again until you're done. That's what _looping_ is for! @@ -202,21 +216,23 @@ That's what _looping_ is for! In functional programming, _recursion_ is the main mechanism for looping in your code. -Recursion is nothing more than the capacity for a function to call itself (often with different parameters than the initial call). It's not hard to see how this mechanism can be used to loop in any way you want, and we've already seen examples of recursion in action. +Recursion is nothing more than the capacity for a function to call itself (often with different parameters than the initial call). + +It's not hard to see how this mechanism can be used to loop in any way you want, and we've already seen examples of recursion in action. ```clojure (def: (factorial' acc n) (-> Nat Nat Nat) (if (n.= 0 n) acc - (factorial' (n.* n acc) (dec n)))) + (factorial' (n.* n acc) (-- n)))) ``` The `factorial'` function calls itself with an ever increasing accumulator (that will hold the eventual result), and an ever decreasing input value. -Recursion in many languages is seen as a potentially dangerous operation, since programming languages have what are called `"stacks"`, which are structures holding the parameters to functions and the return addresses for where to send the results once the functions are done. +Recursion in many languages is seen as a risky operation, since programming languages have what are called `"stacks"`, which are structures holding the parameters to functions and the return addresses for where to send the results once the functions are done. -Every function call you issue puts a new frame onto the stack, and if enough frames are pushed, eventually the stack _"overflows"_ its capacity, causing the program to fail. +Every function call you issue pushes a new frame onto the stack, and if enough frames are pushed, eventually the stack _"overflows"_ its capacity, causing the program to fail. However, an old trick that has been employed for a long time in programming languages is called _tail-call optimization_, and it allows you to optimize recursive calls that are in a _"tail position"_; that is, a position where the result of the call can just be returned immediately, instead of needing any further processing. @@ -229,7 +245,7 @@ This alternative doesn't: (-> Nat Nat Nat) (if (n.= 0 n) acc - (n.+ 0 (factorial' (n.* n acc) (dec n))))) + (n.+ 0 (factorial' (n.* n acc) (-- n))))) ``` Can you spot the difference? @@ -259,12 +275,14 @@ To see it in action, let's rewrite (once more!) our `factorial` function: n n] (if (n.= +0 n) acc - (recur (n.* n acc) (dec n))))) + (again (n.* n acc) (-- n))))) ``` -We have eliminated our dependency on the factorial' function. +We have eliminated our dependency on the `factorial'` function. + +Just like with `let`, we're creating some local variables, but these are going to change on each iteration. -Just like with `let`, we're creating some local variables, but these are going to change on each iteration. Then, in the body, we perform the usual `if` test, and if the number is not 0, then I use the `recur` operator (which only works inside of loop) to update the values of my variables for the next iteration. +Then, in the body, we perform the usual `if` test, and if the number is not `0`, then I use the `again` operator (which only works inside of loop) to update the values of my variables for the next iteration. ## Piping @@ -276,34 +294,38 @@ Here is a simple example to see how it works: ```clojure (|> elems - (map to_text) - (interpose " ") - (fold append_text "")) - -## => -## (fold append_text "" -## (interpose " " -## (map to_text elems))) + (each to_text) + (interposed " ") + (mix append_text "")) + +... => +... (mix append_text "" +... (interposed " " +... (each to_text elems))) ``` If you read carefully, you'll see that each element (from left to right) gets lodged at the end of the next expression and the pattern continues until everything has been nested. -A good convention to follow in functional programming (and especially in Lux), is that the most important argument to a function (or its _subject_) ought to be the last argument the function takes. One of the really cool benefits of this convention is that your code becomes very amenable to piping, as the nesting is only done in one way. +A good convention to follow in functional programming (and especially in Lux), is that the most important argument to a function (or its _subject_) ought to be the last argument the function takes. + +One of the really cool benefits of this convention is that your code becomes very amenable to piping, as the nesting is only done in one way. It's not hard to see how much easier to read and understand the piped version is, compared to the resulting code. -Also, you might be interested to know that piping can also be extended in really cool ways (similarly to how pattern-matching can be extended). The way is to use piping macros (you may be noticing a theme here). +Also, you might be interested to know that piping can also be extended in really cool ways (similarly to how pattern-matching can be extended). + +The way is to use **piping macros** (you may be noticing a theme here). If you want to know more about those, feel free to check out [Appendix D](appendix_d.md), where I review them in detail. Oh, and before I forget, there is also a macro for doing reverse piping (which can be very useful in some situations). -Out previous example would look like this: +Our previous example would look like this: ```clojure -(<| (fold append_text "") - (interpose " ") - (map to_text) +(<| (mix append_text "") + (interposed " ") + (each to_text) elems) ``` @@ -313,7 +335,11 @@ I can't finish this chapter without talking about one of the coolest features in So far, we have seen several control-flow mechanisms that could potentially exist in any language/paradigm, but now we'll talk about something native to the FP landscape. -You already know that in the world of functional programming, functions are _first-class values_. That just means functions can be treated like other values (such as `Int`s, `Bit`s and `Text`s). You can create new functions at run-time, you can pass functions around as arguments to other functions and you can combine functions in arbitrary ways. +You already know that in the world of functional programming, functions are _first-class values_. + +That just means functions can be treated like other values (such as `Int`s, `Bit`s and `Text`s). + +You can create new functions at run-time, you can pass functions around as arguments to other functions and you can combine functions in arbitrary ways. Well, we haven't really seen that in action yet. @@ -321,24 +347,32 @@ It's time to put that theory into practice... with an example: ```clojure (def: (iterate_list f list) - (All [a b] (-> (-> a b) (List a) (List b))) + (All (_ a b) (-> (-> a b) (List a) (List b))) (case list - #.End - #.End + {.#End} + {.#End} - (#.Item head tail) - (#.Item (f head) (iterate_list f tail)))) + {.#Item head tail} + {.#Item (f head) (iterate_list f tail)})) ``` This is a function that allows you to transform lists in arbitrary ways. -However, you may notice that we're seeing many new things. For instance, what is that `All` thingie over there, and what does it do? Is it even a type? +However, you may notice that we're seeing many new things. + +For instance, what is that `All` thingie over there, and what does it do? Is it even a type? -Well, it's not a type. It's actually a macro for creating types (in the same way that `->` is a macro that creates types). The difference is that `All` allows you to create _universally-quantified types_. That's just a fancy way of saying that your types are not fixed to working in a particular way, but are flexible enough to allow some variability. +Well, it's not _technically_ a type. + +It's actually a macro for creating types (in the same way that `->` is a macro that creates types). + +The difference is that `All` allows you to create _universally-quantified types_. + +That's just a fancy way of saying that your types are not fixed to working in a particular way, but are flexible enough to allow some variability. Here, it's being used to make a function that can takes lists with elements of any type (denoted by the _type variable_ `a`), and can produce lists with elements of any other type (denoted by the _type variable_ `b`), so long as you give it a function `f`, that transforms values of type `a` into values of type `b`. -That... is... mind-blowing! +That... is... _mind-blowing_! In other programming languages, whenever you want to process the elements of a sequence (say, an array) you have to write something like a _for loop_ with some index variable, some condition... and then the code for actually _working with the data_. @@ -349,14 +383,16 @@ You could use it like this: ```clojure (iterate_list (n.* 5) (list 0 1 2 3 4 5 6 7 8 9)) -## => (list 0 5 10 15 20 25 30 35 40 45) +... => (list 0 5 10 15 20 25 30 35 40 45) ``` Pretty cool, huh! But this is just scratching the surface of what's possible. -As it turns out, higher-order functions (that is, functions which take or produce other functions) are at the foundation of many advanced techniques in functional programming. Mastering this little trick will prove invaluable to you as you delve deeper into the mysteries of functional programming. +As it turns out, higher-order functions (that is, functions which take or produce other functions) are at the foundation of many advanced techniques in functional programming. + +Mastering this little trick will prove invaluable to you as you delve deeper into the mysteries of functional programming. --- diff --git a/documentation/book/the_lux_programming_language/chapter_6.md b/documentation/book/the_lux_programming_language/chapter_6.md index a57ff4913..61582d1bf 100644 --- a/documentation/book/the_lux_programming_language/chapter_6.md +++ b/documentation/book/the_lux_programming_language/chapter_6.md @@ -9,18 +9,20 @@ We've talked about Lux types already, but only in a very high-level way. On this chapter, you'll see how types are constructed, and hopefully that will give you some insight to understand better the subjects of later chapters. ```clojure -(type: #export #rec Type - (#Primitive Text (List Type)) - (#Sum Type Type) - (#Product Type Type) - (#Function Type Type) - (#Parameter Nat) - (#Var Nat) - (#Ex Nat) - (#UnivQ (List Type) Type) - (#ExQ (List Type) Type) - (#Apply Type Type) - (#Named Name Type)) +(type: .public Type + (Rec Type + (Variant + {#Primitive Text (List Type)} + {#Sum Type Type} + {#Product Type Type} + {#Function Type Type} + {#Parameter Nat} + {#Var Nat} + {#Ex Nat} + {#UnivQ (List Type) Type} + {#ExQ (List Type) Type} + {#Apply Type Type} + {#Named Name Type}))) ``` This is the type of types. @@ -31,53 +33,65 @@ But as I've said before, Lux types are values like any other. `Type` is a variant type, which just means that there are multiple options for type values. -Also, you may have noticed that `#rec` tag in the definition. You need to add it whenever you're defining a recursive type that takes no parameters. +Also, you may have noticed that `Rec` macro in the definition. + +You need to add it whenever you're defining a recursive type that takes no parameters. So, the definition of `List` doesn't need it, but the definition of `Type` does. -Let's go over each of them. +Let's go over each option for `Type`. --- ```clojure -(#Primitive Text (List Type)) +{#Primitive Text (List Type)} ``` -This is what connects Lux's type-system with the host platform's. These types represent classes (in the JVM), with their respective parameters, if they have them (as would be the case for `ArrayList<Long>` in the JVM). +This is what connects Lux's type-system with the host platform's. + +These types represent classes (in the JVM) with their respective parameters, if they have them (as would be the case for `ArrayList<Long>` in the JVM). --- ```clojure -(#Sum Type Type) -(#Product Type Type) +{#Sum Type Type} +{#Product Type Type} ``` -You may have noticed that none of those options are called `#Variant` or `#Tuple`. The reason is that variants and tuples are just names for mathematical constructs called "sums" and "products". Funny names, right? +You may have noticed that none of those options are called `#Variant` or `#Tuple`. + +The reason is that variants and tuples are just names for mathematical constructs called _"sums"_ and _"products"_. + +Funny names, right? -Well, mathematicians see variants as a way of "adding" types and tuples as a way of "multiplying" types, Of course, it's a bit difficult to picture that if you're thinking of numbers. +Well, mathematicians see variants as a way of "adding" types and tuples as a way of "multiplying" types. -But a way to see variants is as an _"OR"_ operation for types: you get this option _OR_ that option. Conversely, tuples are like an _"AND"_ operation for types: you get this type _AND_ that type. +Of course, it's a bit difficult to picture that if you're thinking of numbers. -But, you may be wondering: "why do `#Variant` and `#Tuple` only take 2 types, instead of a list like `#Primitive` does?" +But a way to see variants is as an _"OR"_ operation for types: you get this option _OR_ that option. -Well, as it turns out, you don't need a list of types to implement variants and tuples, because you can actually chain `#Variant` and `#Tuple` with other instances of themselves to get the same effect. +Conversely, tuples are like an _"AND"_ operation for types: you get this type _AND_ that type. + +But, you may be wondering: "why do `#Sum` and `#Product` only take 2 types, instead of a list like `#Primitive` does?" + +Well, as it turns out, you don't need a list of types to implement variants and tuples, because you can actually chain `#Sum` and `#Product` with other instances of themselves to get the same effect. What do I mean? Well, let me show you. To the left, you'll see the type as it's written in normal Lux code, and to the right you'll see the type value it generates. ```clojure -(|) => Nothing -(| Bit) => Bit -(| Bit Int) => (#Sum Bit Int) -(| Bit Int Real) => (#Sum Bit (#Sum Int Real)) -(| Bit Int Real Char) => (#Sum Bit (#Sum Int (#Sum Real Char))) - -(&) => Any -(& Bit) => Bit -(& Bit Int) => (#Product Bit Int) -(& Bit Int Real) => (#Product Bit (#Product Int Real)) -(& Bit Int Real Char) => (#Product Bit (#Product Int (#Product Real Char))) +(Or) => Nothing +(Or Bit) => Bit +(Or Bit Int) => {#Sum Bit Int} +(Or Bit Int Real) => {#Sum Bit {#Sum Int Real}} +(Or Bit Int Real Char) => {#Sum Bit {#Sum Int {#Sum Real Char}}} + +(And) => Any +(And Bit) => Bit +(And Bit Int) => {#Product Bit Int} +(And Bit Int Real) => {#Product Bit {#Product Int Real}} +(And Bit Int Real Char) => {#Product Bit {#Product Int {#Product Real Char}}} ``` You can see where this is going. @@ -86,21 +100,25 @@ If I have a way to to pair up 2 types, and I can nest that, then I can chain thi What is a variant/tuple of 1 type? It's just the type itself; no pairing required. -This embedding means that [true 123 456.789 "X"] is the same as [true [123 456.789 "X"]], and the same as [true [123 [456.789 "X"]]]. +This embedding means that `[true 123 456.789 "X"]` is the same as `[true [123 456.789 "X"]]`, and the same as `[true [123 [456.789 "X"]]]`. -It also means 5 is the same as [5], and [[5]], and [[[[[5]]]]]. +It also means `5` is the same as `[5]`, and `[[5]]`, and `[[[[[5]]]]]`. As far as the compiler is concerned, there are no differences. -That might sound crazy, but there are some really cool benefits to all of this. If you're curious about that, you can check out [Appendix E](appendix_e.md) for more information on how Lux handles this sort of stuff. +That might sound crazy, but there are some really cool benefits to all of this. + +If you're curious about that, you can check out [Appendix E](appendix_e.md) for more information on how Lux handles this sort of stuff. -And what happens when the variant/tuple has 0 types? That's when `Nothing` and `Any` come into play. +And what happens when the variant/tuple has 0 types? + +That's when `Nothing` and `Any` come into play. `Nothing` is a type that has no instances; which is to say, there's no expression which can yield a value of such a type. It might seem oddd to have a type which has no instancces, but it can be useful to model computations which fail at runtime (thereby yielding no value). -So, another way of thinking of `Nothing` is as the type of failed expressions. +So, another way of thinking of `Nothing` is as the type of failed/erroneous computations. `Any`, on the other hand, is the opposite. @@ -108,7 +126,7 @@ You can think of it as the super-type of all other types: the type of all values This means that not only `(: Nat 123)`, but also `(: Any 123)`. -Since `Any` does not give you any specific information about a value, it only tells you that a value exists, regardless of what its specific type happens to be. +This works because `Any` does not give you any specific information about a value, it only tells you that a value exists, regardless of what its specific type happens to be. So, whenever a function accepts or returns a dummy value of some kind, `Any` is a good candidate for that. @@ -123,9 +141,10 @@ You might think that dummy values are, well, _dumb_, but they show up all the ti Consider the `Maybe` type: ```clojure -(type: #export (Maybe a) - #None - (#Some a)) +(type: .public (Maybe a) + (Variant + {#None} + {#Some a})) ``` The `#Some` tag holds values of type `a`, but what does `#None` hold? Nothing? @@ -133,7 +152,7 @@ The `#Some` tag holds values of type `a`, but what does `#None` hold? Nothing? Well, `Maybe` is a variant, which means it's a `#Sum`, which looks like this: ```clojure -(#Sum Type Type) +{#Sum Type Type} ``` So we know that `#None` must hold _something_. But what? @@ -143,23 +162,19 @@ Well, `Any`thing, really. So the type definition for `Maybe` is equivalent to this: ```clojure -(type: #export (Maybe a) - (#None Any) - (#Some a)) +(type: .public (Maybe a) + {#None Any} ... Alternatively, {#None []} + {#Some a}) ``` If you don't care what value you store somewhere, then you can store `Any` value in there. -In practice, you can create instances of `Maybe` by writing this `(#None [])`, or `(#None 123)`, or just `#None`. - -If you only write the tag, then Lux treats it as if you paired it up with an empty tuple. - -So `#None` is equivalent to `(#None [])`. +In practice, you can create instances of `Maybe` by writing this `{#None []}`, or `{#None 123}`, or just `{#None}`. --- ```clojure -(#Function Type Type) +{#Function Type Type} ``` Now that we have discussed variant and tuple types, it shouldn't come as a surprise that a similar trick can be done with function types. @@ -168,7 +183,7 @@ You see, if you can implement functions of 1 argument, you can implement functio All I need to do is to embed the rest of the function as the return value to the outer function. - It might sound like this whole business of embedding tuples, variants and functions inside one another must be super inefficient; but trust me: Lux has taken care of that. + It might sound like this whole business of embedding variants, tuples and functions inside one another must be super inefficient; but trust me: Lux has taken care of that. The Lux compiler features many optimizations that compile things down in a way that gives you maximum efficiency. So, to a large extent, these embedded encodings are there for the semantics of the language, but not as something that you'll pay for at run-time. @@ -179,7 +194,7 @@ Yep, that's a direct consequence of this theoretical model. --- ```clojure -(#Parameter Nat) +{#Parameter Nat} ``` This type is there mostly for keeping track of type-parameters in _universal and existential quantification_. @@ -189,7 +204,7 @@ We'll talk about those later. But, suffice it to say that `#Parameter` helps the --- ```clojure -(#Var Nat) +{#Var Nat} ``` These are type variables. @@ -203,37 +218,43 @@ Type-variables, however, cannot be _re-bound_ once they have been set, to avoid --- ```clojure -(#Ex Nat) +{#Ex Nat} ``` An existential type is an interesting concept (which is related, but not the same as existential quantification). -You can see it as a type that exists, but is unknown to you. It's like receiving a type in a box you can't open. +You can see it as a type that exists, but is unknown to you. + +It's like receiving a type in a box you can't open. -What can you do with it, then? You can compare it to other types, and the comparison will only succeed if it is matched against itself. +What can you do with it, then? + +You can compare it to other types, and the comparison will only succeed if it is matched against itself. It may sound like a useless thing, but it can power some advanced techniques. --- ```clojure -(#UnivQ (List Type) Type) +{#UnivQ (List Type) Type} ``` This is what the `All` macro generates: _universal quantification_. -That `(List Type)` you see there is meant to be the _context_ of the universal quantification. It's kind of like the environment of a function closure, only with types. +That `(List Type)` you see there is meant to be the _context_ of the universal quantification. + +It's kind of like the environment of a function closure, only with types. The other `Type` there is the _body_ of the universal quantification. To understand better what's going on, let's transform the type of our `iterate_list` function from [Chapter 5](chapter_5.md) into its type value. ```clojure -(All [a b] (-> (-> a b) (List a) (List b))) +(All (_ a b) (-> (-> a b) (List a) (List b))) -## => +... => -## (#.UnivQ #.End (#.UnivQ #.End (-> (-> (#.Parameter 3) (#.Parameter 1)) (List (#.Parameter 3)) (List (#.Parameter 1)))) +... {.#UnivQ {.#End} {.#UnivQ {.#End} (-> (-> {.#Parameter 3} {.#Parameter 1}) (List {.#Parameter 3}) (List {.#Parameter 1})}} ``` **Note**: I didn't transform the type entirely to avoid unnecessary verbosity. @@ -242,24 +263,24 @@ As you can see, I do the same embedding trick to have universal quantification w Also, `a` and `b` are just nice syntactic labels that get transformed into `#Parameter` types. - The reason the type-parameters have those IDs is due to a technique called [De Bruijn Indices](https://en.wikipedia.org/wiki/De_Bruijn_index). + The reason the type-parameters have those numeric IDs is due to a technique called [De Bruijn Indices](https://en.wikipedia.org/wiki/De_Bruijn_index). --- ```clojure -(#ExQ (List Type) Type) +{#ExQ (List Type) Type} ``` Existential quantification works pretty much the same way as universal quantification. Its associated macro is `Ex`. -Whereas universal quantification works with type-variables, existential quantification works with existential types. +Where universal quantification works with type-variables, existential quantification works with existential types. --- ```clojure -(#Apply Type Type) +{#Apply Type Type} ``` This is the opposite of quantification. @@ -268,14 +289,14 @@ This is the opposite of quantification. With `#Apply`, `(List Int)` transforms into `(#Apply Int List)`. -For multi-parameter types, like `Dictionary` (from `lux/data/collection/dictionary`), `(Dictionary Text User)` would become `(#Apply User (#Apply Text Dictionary))`. +For multi-parameter types, like `Dictionary` (from `library/lux/data/collection/dictionary`), `(Dictionary Text User)` would become `(#Apply User (#Apply Text Dictionary))`. As you can see, the nesting is slightly different than how it is for tuples, variant and functions. --- ```clojure -(#Named Name Type) +{#Named Symbol Type} ``` `#Named` is less of a necessity and more of a convenience. @@ -298,9 +319,11 @@ That may sound odd (if you come from Java or other languages with nominal types) ## Regarding Error Messages -When you get error messages from the type-checker during your coding sessions, types will show up in intuitive ways most of the time, with a few exceptions you might want to know. +When you get error messages from the type-checker during your coding sessions, types will show up in intuitive ways most of the time, with a few exceptions you might want to know about. + +Existential types show up in error messages like `⟨e:246⟩` (where 246 is the ID of the type). -Existential types show up in error messages like `⟨e:246⟩` (where 246 is the ID of the type). Whereas type-variables show up like `⌈v:278⌋`. +Whereas type-variables show up like `⌈v:278⌋`. Those types tend to show up when there are errors in the definition of some polymorphic function. diff --git a/documentation/book/the_lux_programming_language/chapter_7.md b/documentation/book/the_lux_programming_language/chapter_7.md index 49cae9980..bf888680f 100644 --- a/documentation/book/the_lux_programming_language/chapter_7.md +++ b/documentation/book/the_lux_programming_language/chapter_7.md @@ -8,7 +8,7 @@ You endured all that tedious talk about types; but it wasn't for nothing. Now, you'll see types in action, as they take a new _shape_... and a new _purpose_. -Many programming languages have some kind of module system or polymorphism system. +Many programming languages have some kind of polymorphism system or module system. You know what I'm talking about. @@ -16,21 +16,39 @@ Object-oriented languages have classes with methods that can be overriden by the Or maybe you come from Haskell, where they have type-classes, that basically perform the same process, but during compilation. Types are checked, instances get picked, and the proper functions and constants get plugged-in. -Or may, you come from the world of ML (specially Standard ML), where they have a module system based on signatures and structures. In those systems, the function implementations you want don't get selected for you automatically (you have to pick them yourself), but you tend to have more control when it comes to choosing what to use. +Or maybe, you come from the world of ML (specially Standard ML), where they have a _module system_ based on signatures and structures. In those systems, the function implementations you want don't get selected for you automatically (you have to pick them yourself), but you tend to have more control when it comes to choosing what to use. -The origin of Lux's polymorphism system is I... um... _borrowed_ it from the SML guys. I re-named signatures as interfaces and structures as implementations to give them more recognizable names. +The origin of Lux's polymorphism system is I... um... _borrowed_ it from the SML guys. + +I re-named signatures as _interfaces_ and structures as _implementations_ to give them more recognizable names. But I also added my own little twist. -You see, module/polymorphism systems in programming languages tend to live in a mysterious world that is removed from the rest of the language. It's a similar situation as with types. +You see, polymorphism/module systems in programming languages tend to live in a mysterious world that is removed from the rest of the language. + +It's a similar situation as with types. + +Remember Lux's type system? + +Most languages keep their types separate from their values. + +Types are just some cute annotations you put in your code to keep the compiler happy. + +Lux's types, on the other hand, are alive; for they are values. -Remember Lux's type system? Most languages keep their types separate from their values. Types are just some cute annotations you put in your code to keep the compiler happy. Lux's types, on the other hand, are alive; for they are values. Nothing stops you from using them, transforming them and analyzing them in ways that go beyond the language designer's imagination (_that would be me_). +Nothing stops you from using them, transforming them and analyzing them in ways that go beyond the language designer's imagination (_that would be **me**_). -Well, there's a similar story to tell about module/polymorphism systems. The run-time/compiler chooses everything for you; and even when you choose for yourself, you're still somewhat limited in what you can do. Implementations are not values, and there is a fundamental division between them and the rest of the language. +Well, there's a similar story to tell about polymorphism/module systems. -But not in Lux. +The run-time/compiler chooses everything for you; and even when you choose for yourself, you're still somewhat limited in what you can do. -Lux's polymorphism system is actually based on regular types and values. And because types are values, that means it's just ~~turtles~~ values all the way down. +Implementations are not values, and there is a fundamental division between them and the rest of the language. + +But **not** in Lux. + +Lux's polymorphism system is actually based on regular types and values. + +And because types are values, that means it's just ~~turtles~~ values all the way down. _But, how does it work?_ @@ -38,29 +56,37 @@ _But, how does it work?_ ## Interfaces -They provide a description of the functionality expected of proper implementations. They have a list of expected member values/functions, with their associated types. +They provide a description of the functionality expected of proper implementations. + +They have a list of expected member values/functions, with their associated types. Here's an example: ```clojure -(interface: #export (Order a) - (: (Equivalence a) - &equivalence) +(type: .public (Order a) + (Interface + (: (Equivalence a) + &equivalence) - (: (-> a a Bit) - <)) + (: (-> a a Bit) + <))) ``` -That _interface_ definition comes from the `lux/abstract/order` module, and it deals with _ordered_ types; that is, types for which you can compare their values in ways that imply some sort of sequential order. +That _interface_ definition comes from the `library/lux/abstract/order` module, and it deals with _ordered_ types; that is, types for which you can compare their values in ways that imply some sort of sequential order. It's polymorphic/parameterized because this interface must be able to adapt to any type that fits its requirements. -Also, you may notice that it has a member called `&equivalence`, of type `(Equivalence a)`. The reason is that interfaces can expand upon (or be based on) other interfaces (such as `Equivalence`). +Also, you may notice that it has a member called `&equivalence`, of type `(Equivalence a)`. + +The reason is that interfaces can expand upon (or be based on) other interfaces (such as `Equivalence`). _How do interfaces differ from types?_ -They don't. They're actually implemented as _types_. -Specifically, as record/tuple types. +They don't. + +They're actually implemented as _types_. + +Specifically, as tuple/record types. You see, if I can create a record type with one field for every expected definition in a interface, then that's all I need. @@ -73,8 +99,11 @@ If interfaces are record types, then that means implementations must be actual r Let's take a look at how you make one: ```clojure -(implementation: #export order (Order Frac) +(implementation: .public order + (Order Frac) + (def: &equivalence ..equivalence) + (def: < ..<)) ``` @@ -84,30 +113,36 @@ As you may notice, implementations have names; unlike in object-oriented languag For implementations, the convention is just to name them as lower-cased versions of the interfaces they implement. -Here is another example, from the `lux/data/collection/list` module: +Here is another example, from the `library/lux/data/collection/list` module: ```clojure -(implementation: #export monoid - (All [a] +(implementation: .public monoid + (All (_ a) (Monoid (List a))) - (def: identity #.End) + (def: identity + {.#End}) + (def: (compose xs ys) (case xs - #.End ys - (#.Item x xs') (#.Item x (compose xs' ys))))) + {.#End} ys + {.#Item x xs'} {.#Item x (compose xs' ys)}))) ``` -The reason why implementations have names (besides the fact that they are definitions like any other), is that you can actually construct multiple valid implementations for the same combination of interfaces and parameter types. That would require you to distinguish each implementation in some way in order to use it. This is one cool advantage over Haskell's -_type-classes_ and _instances_, where you can only have one instance for any combination of type-class and parameter. +The reason why implementations have names (besides the fact that they are definitions like any other), is that you can _usually_ construct multiple valid implementations for the same combination of interfaces and parameter types. + +That would require you to distinguish each implementation in some way in order to use it. + +This is one cool advantage over Haskell's _type-classes_ and _instances_, where you can only have one instance for any combination of type-class and parameter. - Haskellers often resort to "hacks" such as using newtype to try to get around this limitation. + Haskellers often resort to _"hacks"_ such as using newtype to try to get around this limitation. The upside of having the run-time/compiler pick the implementation for you is that you can avoid some boilerplate when writing polymorphic code. -The upside of picking the implementation yourself is that you get more control and predictability over what's happening (which is specially cool when you consider that _implementations_ are first-class values). +The upside of picking the implementation yourself is that you get more control and predictability over what's happening (which is specially useful when you consider that _implementations_ are first-class values). What's the big importance of _implementations_ being first-class values? + Simple: it means you can create your own _implementations_ at run-time based on arbitrary data and logic, and you can combine and transform _implementations_ however you want. Standard ML offers something like that by a mechanism they call "functors" (unrelated to a concept of "functor" we'll see in a later chapter), but they are more like _magical functions_ that the compiler uses to combine _structures_ in limited ways. @@ -120,38 +155,45 @@ We've put functions and values inside our implementations. It's time to get them out and use them. -There are 2 main ways to use the stuff inside your implementations: `open:` and `\`. +There are 2 main ways to use the stuff inside your implementations: `open:` and `#`. + Let's check them out. ```clojure -## Opens an implementation and generates a definition for each of its members (including nested members). -## For example: -(open library/lux/math/number/int.order "i::.") -## Will generate: -(def: i::= (\ library/lux/math/number/int.order =)) -(def: i::< (\ library/lux/math/number/int.order <)) +... Opens an implementation and generates a definition for each of its members (including nested members). + +... For example: +(open library/lux/math/number/int.order "i::[0]") + +... Will generate: +(def: .private i::= (# library/lux/math/number/int.order =)) +(def: .private i::< (# library/lux/math/number/int.order <)) ``` -The `open:` macro serves as a statement that creates private/un-exported definitions in your module for every member of a particular implementation. -You may also give it an optional prefix for the definitions, in case you want to avoid any name clash. +The `open:` macro serves as a directive that creates private/un-exported definitions in your module for every member of a particular implementation. + +You may also give it an optional _aliasing pattern_ for the definitions, in case you want to avoid any name clash. You might want to check out [Appendix C](appendix_c.md) to discover a pattern-matching macro version of `open:` called `^open`. ```clojure -## Allows accessing the value of a implementation's member. +... Allows accessing the value of a implementation's member. (: (-> Int Text) - (\ library/lux/math/number/int.decimal encode)) + (# library/lux/math/number/int.decimal encoded)) + +... Also allows using that value as a function. +(# library/lux/math/number/int.decimal encoded +123) -## Also allows using that value as a function. -(\ library/lux/math/number/int.decimal encode +123) -## => "+123" +... => "+123" ``` -`\` is for when you want to use individual parts of a implementation immediately in your code, instead of opening them first. +`#` is for when you want to use individual parts of a implementation immediately in your code, instead of opening them first. - Psss! Did you notice `\` is _piping enabled_? + Psss! Did you notice `#` is _piping compatible_? -Also, you don't really need to worry about boilerplate related to using implementations. There is a module called `lux/type/implicit` which gives you a macro called `\\` for using implementations without actually specifying which one you need. +Also, you don't really need to worry about boilerplate related to using implementations. + +There is a module called `library/lux/type/implicit` which gives you a macro called `##` for using implementations without actually specifying which one you need. The macro infers everything for you based on the types of the arguments, the expected type of the expression, and the implementations available in the environment. @@ -159,30 +201,32 @@ For more information about that, head over to [Appendix F](appendix_f.md) to rea ## Implementations as values -I can't emphasize enough that _implementations_ are values. And to exemplify it for you, here's a function from the `lux/abstract/monad` module that takes in an implementation (among other things) and uses it within its code: +I can't emphasize enough that _implementations_ are values. + +And to exemplify it for you, here's a function from the `library/lux/abstract/monad` module that takes in an implementation (among other things) and uses it within its code: ```clojure -(def: #export (map monad f xs) - (All [M a b] +(def: .public (each monad f xs) + (All (_ M a b) (-> (Monad M) (-> a (M b)) (List a) (M (List b)))) (case xs - #.End - (\ monad in #.End) + {.#End} + (# monad in {.#End}) - (#.Item x xs') + {.#Item x xs'} (do monad [y (f x) - ys (map monad f xs')] - (wrap (#.Item y ys))))) + ys (each monad f xs')] + (in {.#Item y ys})))) ``` -`Monad` is an interface and the `map` function takes arbitrary `Monad` implementations and can work with any of them without an issue. +`Monad` is an interface and the `each` function takes arbitrary `Monad` implementations and can work with any of them without any issue. --- -_Interfaces_ and _implementation_ are the main mechanism for writing polymorphic code in Lux, and they allow flexible and precise control over polymorphism. +_Interfaces_ and _implementation_ are the main mechanism for writing ad-hoc polymorphic code in Lux, and they allow flexible and precise control over polymorphism. -It may be the case that in the future Lux adds new mechanisms for achieving the same goals (I believe in having variety), but the spirit of implementing things in terms of accessible values anybody can manipulate will likely underlie every such mechanism. +It may be the case that in the future Lux includes new mechanisms for achieving the same goals (I believe in having variety), but the spirit of implementing things in terms of accessible values anybody can manipulate will likely underlie every such mechanism. Now that we've discussed _interfaces_ and _implementations_, it's time to talk about a _very special family of interfaces_. diff --git a/documentation/book/the_lux_programming_language/chapter_8.md b/documentation/book/the_lux_programming_language/chapter_8.md index 61045cec9..5f15eced5 100644 --- a/documentation/book/the_lux_programming_language/chapter_8.md +++ b/documentation/book/the_lux_programming_language/chapter_8.md @@ -10,75 +10,88 @@ The following topics are known to be troublesome to teach, so I can only promise ## Functors -Functors and monads are both mathematical concepts that are prevalent in **Category Theory**. You may have heard of it before. It's a branch of abstract mathematics that many are drawing inspiration from when developing new tools and techniques for functional programming. +Functors and monads are both mathematical concepts that are prevalent in **Category Theory**. + +You may have heard of it before. + +It's a branch of abstract mathematics that many are drawing inspiration from when developing new tools and techniques for functional programming. But I will not go down the route of explaining things to you from a mathematical perspective... as I'm not confident that's going to work. --- -Imagine that you have some data (maybe an `Int`, or a `Text`). You can work with that data: you can pass it around, apply functions to it, print it to the console/terminal, or pattern-match against it. +Imagine that you have some data (maybe an `Int`, or a `Text`). + +You can work with that data: you can pass it around, apply functions to it, print it to the console/terminal, or pattern-match against it. -Well, imagine a functor as some kind of wrapper on your data. You can still access what's inside, but the wrapper itself offers special superpowers, and each wrapper is different. +Well, imagine a `Functor` as some kind of wrapper on your data. + +You can still access what's inside, but the wrapper itself offers special superpowers, and each wrapper is different. For instance, there's one wrapper that allows us to have (or not to have) our data. -Schrodinger's wrapper (although most people prefer to call it `Maybe`). +_Schrodinger's wrapper_ (although most people prefer to call it `Maybe`). -That wrapper happens to be a type, as **all** functor wrappers are types. +That wrapper happens to be a type, as **all** `Functor` wrappers are types. But not just any type. You see, functors have requirements. ```clojure -(interface: #export (Functor f) - (: (All [a b] - (-> (-> a b) (f a) (f b))) - map)) +(type: .public (Functor f) + (Interface + (: (All (_ a b) + (-> (-> a b) (f a) (f b))) + each))) ``` -This is the `Functor` interface, from `lux/abstract/functor`. +This is the `Functor` interface, from `library/lux/abstract/functor`. + +As you can see, it only has a single member: the `each` function. -As you can see, it only has a single member: the `map` function. +The parameter type `f` is very special, because instead of being a simple type (like `Int` or `Text`), it's actually a parameterized type (with a single parameter). -The parameter type `f` is very special, because instead of being a simple type (like `Int` or `Text`), it's actually a parameterized type (with a single parameter). That explains why it's being used the way it is in the type of `map`. +That explains why it's being used the way it is in the type of `each`. -Not every parameterized type can be a functor, but if the type is something that you can open to work with its inner elements, then it becomes a good candidate. +Not every parameterized type can be a `Functor`, but if the type is something that you can open to work with its inner elements, then it becomes a good candidate. And you would be surprised how many things fit that requirement. -Remember that `Maybe` type we talked about? Let's see how it plays with `Functor`. +Remember that `Maybe` type we talked about? + +Let's see how it plays with `Functor`. ```clojure -(type: (Maybe a) - #.None - (#.Some a)) +(type: .public (Maybe a) + {.#None} + {.#Some a}) ``` -We've seen `Maybe` before, but now we can check out how it's implemented. +We've seen `Maybe` before, but now we can analyse out how it's implemented. By the way, it lives in the `library/lux` module, so you don't need to import anything. Here is its `Functor` implementation. ```clojure -(implementation: #export functor +(implementation: .public functor (Functor Maybe) - (def: (map f ma) + (def: (each f ma) (case ma - #.None #.None - (#.Some a) (#.Some (f a))))) + {.#None} {.#None} + {.#Some a} {.#Some (f a)}))) -## This one lives in the library/lux/data/maybe module, though. +... This one lives in the library/lux/data/maybe module, though. ``` -We'll know how everything fits if we fill in the blanks for `map`'s type: +We'll know how everything fits if we fill in the blanks for `each`'s type: ```clojure -(All [a b] +(All (_ a b) (-> (-> a b) (Maybe a) (Maybe b)) ``` -So, the job of `map` here is to take a `Maybe` containing some `a` value, and somehow transform it into a `b`, without escaping the `Maybe`. +So, the job of `each` here is to take a `Maybe` containing some `a` value, and somehow transform it into a `b`, without escaping the `Maybe`. By looking at the `Functor` implementation, we can see how this works out. @@ -88,25 +101,29 @@ Not that hard. Oh, and remember our `iterate_list` function from [chapter 5](chapter_5.md)? -Turns out, that's just the `Functor` implementation from `lux/data/collection/list`: +Turns out, that's just the `Functor` implementation from `library/lux/data/collection/list`: ```clojure -(implementation: #export functor +(implementation: .public functor (Functor List) - (def: (map f ma) + (def: (each f ma) (case ma - #.End #.End - (#.Item a ma') (#.Item (f a) (map f ma'))))) + {.#End} {.#End} + {.#Item a ma'} {.#Item (f a) (each f ma')}))) ``` Not bad. -In the case of `List`, the wrapper superpower it provides is the capacity to handle multiple values as a group. This can be used for some really cool techniques; like implementing non-deterministic computations by treating every list element as a branching value (but let's not go down that rabbit-hole for now). +In the case of `List`, the wrapper superpower it provides is the capacity to handle multiple values as a group. + +This can be used for some really cool techniques; like implementing non-deterministic computations by treating every list item as a branching value (but let's not go down that rabbit-hole for now). -The power of functors is that they allow you to decorate your types with extra functionality. You can still access the inner data and the `map` function will take advantage of the wrapper's properties to give you that extra power you want. +The power of `Functor`s is that they allow you to decorate your types with extra functionality. -You can implement things like stateful computations, error-handling, logging, I/O, asynchronous concurrency and many other crazy things with the help of functors. +You can still access the inner data and the `each` function will take advantage of the wrapper's properties to give you that extra power you want. + +You can implement things like stateful computations, error-handling, logging, I/O, asynchronous concurrency and many other crazy things with the help of `Functor`s. However, to make them really easy to use, you might want to add some extra functionality. @@ -114,23 +131,24 @@ However, to make them really easy to use, you might want to add some extra funct One thing you may have noticed about the `Functor` interface is that you have a way to operate on functorial values, but you don't have any _standardized_ means of creating them. -I mean, you can use the `list` and `list&` macros to create lists and the `#.None` and `#.Some` tags for `Maybe`, but there is no unified way for creating **any** functorial value. +I mean, you can use the `list` and `list&` macros to create lists and the `.#None` and `.#Some` tags for `Maybe`, but there is no unified way for creating **any** functorial value. Well, let me introduce you to `Monad`: ```clojure -(interface: #export (Monad m) - (: (Functor m) - &functor) - (: (All [a] - (-> a (m a))) - in) - (: (All [a] - (-> (m (m a)) (m a))) - join)) +(type: .public (Monad m) + (Interface + (: (Functor m) + &functor) + (: (All (_ a) + (-> a (m a))) + in) + (: (All (_ a) + (-> (m (m a)) (m a))) + conjoint))) ``` -This interface extends `Functor` with both the capacity to wrap a normal value `in` functorial structure, and to `join` 2 layers of functorial structure into a single one. +This interface extends `Functor` with both the capacity to wrap a normal value `in` a functorial structure, and to join 2 layers of functorial structure into a single, `conjoint` one. Sweet! @@ -143,66 +161,69 @@ To get a taste for it, let's check out another functorial type. Remember what I said about error-handling? ```clojure -(type: #export (Try a) - (#Failure Text) - (#Success a)) +(type: .public (Try a) + (Variant + {#Failure Text} + {#Success a})) ``` -This type expresses errors as `Text` values (and it lives in the `lux/control/try` module). +This type expresses errors as `Text` values (and it lives in the `library/lux/control/try` module). Here are the relevant `Functor` and `Monad` implementations: ```clojure -(implementation: #export functor +(implementation: .public functor (Functor Try) - (def: (map f ma) + (def: (each f ma) (case ma - (#Failure msg) - (#Failure msg) + {#Failure msg} + {#Failure msg} - (#Success datum) - (#Success (f datum))))) + {#Success datum} + {#Success (f datum)}))) -(implementation: #export monad +(implementation: .public monad (Monad Try) (def: &functor ..functor) (def: (in a) - (#Success a)) + {#Success a}) (def: (join mma) (case mma - (#Failure msg) - (#Failure msg) + {#Failure msg} + {#Failure msg} - (#Success ma) + {#Success ma} ma))) ``` -If you listen to functional programmers, you'll likely get the impression that the invention of monads rivals the invention of the wheel. It is this incredibly powerful and fundamental abstraction for a lot of functional programs. +If you listen to functional programmers, you'll likely get the impression that the invention of monads rivals the invention of the wheel. + +It is this incredibly powerful and fundamental abstraction for a lot of functional programs. -The thing about `Monad` is that, with it, you can use `map` functions that also generate wrapped values (and take advantage of their special properties), and then you can collapse/merge/combine those values into a "joined" value by using the `join` function. +The thing about `Monad` is that, with it, you can use `each` functions that also generate wrapped values (and take advantage of their special properties), and then you can collapse/merge/combine those values into a _"conjoint"_ value by using the `conjoint` function. Let's see that in action: ```clojure -(.module: - [library - [lux #* - [data - [collection - ["." list]]]]]) +(.using + [library + [lux "*" + [data + [collection + ["[0]" list]]]]]) (open: list.monad) (def foo (|> (list 1 2 3 4) - (map (list.repeated 3)) - join)) + (each (list.repeated 3)) + conjoint)) -## The value of 'foo' is: +... The value of 'foo' is: (list 1 1 1 2 2 2 3 3 3 4 4 4) ``` @@ -211,101 +232,105 @@ _It's magic!_ Not really. It's just the `Monad` for `List`: ```clojure -(implementation: #export functor +(implementation: .public functor (Functor List) - (def: (map f ma) + (def: (each f ma) (case ma - #.End - #.End + {.#End} + {.#End} - (#.Item a ma') - (#.Item (f a) (map f ma'))))) + {.#Item a ma'} + {.#Item (f a) (each f ma')}))) -(implementation: #export fold - (Fold List) +(implementation: .public mix + (Mix List) - (def: (fold f init xs) + (def: (mix f init xs) (case xs - #.End + {.#End} init - (#.Item x xs') - (fold f (f x init) xs')))) + {.#Item x xs'} + (mix f (f x init) xs')))) -(implementation: #export monoid - (All [a] (Monoid (List a))) +(implementation: .public monoid + (All (_ a) (Monoid (List a))) - (def: identity #.End) - (def: (compose xs ys) + (def: identity + {.#End}) + + (def: (composite xs ys) (case xs - #.End + {.#End} ys - (#.Item x xs') - (#.Item x (compose xs' ys))))) + {.#Item x xs'} + {.#Item x (compose xs' ys)}))) -(open: "." ..monoid) +(open: "[0]" ..monoid) -(implementation: #export monad +(implementation: .public monad (Monad List) (def: &functor ..functor) (def: (in a) - (#.Item a #.End)) + {.#Item a {.#End}}) - (def: (join list_of_lists) - (|> list_of_lists reversed (fold compose identity)))) + (def: (conjoint list_of_lists) + (mix composite + identity + (reversed list_of_lists)))) -## The fold function is for doing incremental iterative computations. -## Here, we're using it to build the total output list by composing/concatenating all the input lists in our `list_of_lists`. +... The mix function is for doing incremental iterative computations. +... Here, we're using it to build the total output list by composing/concatenating all the input lists in our `list_of_lists`. ``` -`Monad`s are incredibly powerful, since being able to use the special power of our `Functor` while `map`ping functions allows us to layer that power in complex ways. +`Monad`s are incredibly powerful, since being able to use the special power of our `Functor` while applying functions to `each` list item allows us to layer that power in complex ways. -But... you're probably thinking that writing a bunch of `map`s followed by `join`s is a very tedious process. And, you're right! +But... you're probably thinking that writing a bunch of `each`s followed by `conjoint`s is a very tedious process. And, you're right! If functional programmers had to subject themselves to that kind of tedium all the time, they'd probably not be so excited about monads. Time for the VIP treatment. -## The `do` Macro +## The `do` macro These macros always show up at the right time to saves us from our hurdles! ```clojure -(.module: - [library - [lux #* - [data - ["." maybe]]]]) +(.using + [library + [lux "*" + [data + ["[0]" maybe]]]]) -## Macro for easy concatenation of monadic operations. +... Macro for easy concatenation of monadic operations. (do maybe.monad [x (f0 123) - #let [y (f1 x)] ## #let enables you to use full-featured let-expressions within do + .let [y (f1 x)] ... .let enables you to use full-featured let-expressions within do z (f2 y)] - (wrap (f3 z))) + (in (f3 z))) ``` The `do` macro allows us to write monadic code with great ease (it's almost as if we're just making `let` bindings). -Just tell it which `Monad` implementation you want, and it will write all the steps in your computation piece by piece using `map` and `join` without you having to waste your time with all the boilerplate. +Just tell it which `Monad` implementation you want, and it will write all the steps in your computation piece by piece using `each` and `conjoint` without you having to waste your time with all the boilerplate. Finally, whatever you write as the body of the `do`, it must result in a functorial/monadic value (in this case, a `Maybe` value). -**Remember**: `join` may collapse/merge/combine layers of the functor, but it never escapes it, and _neither can you_ (generally-speaking). +**Remember**: A `conjoint` value may collapse/merge/combine layers of the `Functor`, but it never escapes it. --- `Functor`s and `Monad`s have a bad reputation for being difficult to understand, but hopefully I didn't botch this explanation _too much_. -Personally, I think the best way to understand functors and monads is to read different implementations of them for various types (and maybe write a few of your own). +Personally, I think the best way to understand `Functor`s and `Monad`s is to read different implementations of them for various types (and maybe write a few of your own). - For that, feel free to peruse the Lux Standard Library at your leisure. + For that, feel free to peruse [the Lux Standard Library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard) at your leisure. -This is the sort of think that you need to learn by intuition and kind of _get the feel for_. +This is the sort of thing that you need to learn by intuition and kind of _get the feel for_. Hopefully, you'll be able to get a feel for them in the next chapters, because we're going to be exploring a lot of monads from here on. diff --git a/documentation/book/the_lux_programming_language/chapter_9.md b/documentation/book/the_lux_programming_language/chapter_9.md index 49ffe48bb..13d3c2462 100644 --- a/documentation/book/the_lux_programming_language/chapter_9.md +++ b/documentation/book/the_lux_programming_language/chapter_9.md @@ -1,6 +1,6 @@ # Chapter 9: Meta-programming -_Where we go meta. For real._ +_Where we take a peek behind the curtains of the compiler._ --- @@ -18,39 +18,49 @@ Instead, I'll reveal the infrastructure that makes macros possible, and we'll di The Lux compiler was designed to integrate very well with the language itself. -Most compilers are just programs that take source code and emit some binary executable or some byte-code. But the Lux compiler opens itself for usage within Lux programs and provides Lux programmers with a wealth of information. +Most compilers are just programs that take source code and emit some binary executable or some byte-code. + +But the Lux compiler opens itself for usage within Lux programs and provides Lux programmers with a wealth of information. The `Lux` type enters the stage. ```clojure -(type: #export Lux - {#info Info - #source Source - #location Location - #current_module (Maybe Text) - #modules (List [Text Module]) - #scopes (List Scope) - #type_context Type_Context - #expected (Maybe Type) - #seed Nat - #scope_type_vars (List Nat) - #extensions Any - #host Any}) +(type: .public Lux + (Rec Lux + (Record + [#info Info + #source Source + #location Location + #current_module (Maybe Text) + #modules (List [Text Module]) + #scopes (List Scope) + #type_context Type_Context + #expected (Maybe Type) + #seed Nat + #scope_type_vars (List Nat) + #extensions Any + #eval (-> Type Code (-> Lux (Either Text [Lux Any]))) + #host Any]))) ``` - By the way, the `Lux` type and other weird types you may not recognize there are all defined in the `library/lux` module. Check the documentation for the Standard Library for more details. + By the way, the `Lux` type and other weird types you may not recognize there are all defined in the `library/lux` module. + Check [the documentation for the Standard Library](https://github.com/LuxLang/lux/tree/master/documentation/library/standard) for more details. The `Lux` type represents the state of the Lux compiler at any given point. -It is not a reflection of that state, or a subset of it. It is `the` state of the Lux compiler; and, as you can see, it contains quite a lot of information about compiled modules, the state of the type-checker, the lexical and global environments, and more. +It is not a reflection of that state, or a subset of it. + +It **is** the state of the Lux compiler; and, as you can see, it contains quite a lot of information about compiled modules, the state of the type-checker, the lexical and global environments, and more. Heck, you can even access the yet-unprocessed source code of a module at any given time. That's pretty neat. -You can actually write computations that can read and even modify (_careful with that one_) the state of the compiler. This turns out to be massively useful when implementing a variety of powerful macros. +You can actually write computations that can read and even modify (_careful with that one_) the state of the compiler. + +This turns out to be massively useful when implementing a variety of powerful macros. -For example, remember the `open:` and `\` macros from [chapter 7](chapter_7.md)? +For example, remember the `open:` and `#` macros from [chapter 7](chapter_7.md)? They actually look up the typing information for the structures you give them to figure out the names of members and generate the code necessary to get that functionality going. @@ -71,7 +81,7 @@ I won't go into detail about what's available, but you'll quickly get an idea of However, one thing I _will_ say is that those functions rely heavily on the `Meta` type, which is defined thusly: ```clojure -(type: #export (Meta a) +(type: .public (Meta a) (-> Lux (Either Text [Lux a]))) ``` @@ -79,7 +89,7 @@ However, one thing I _will_ say is that those functions rely heavily on the `Met The `Meta` type has a `Functor`, and a `Monad`, but they are a bit rather complicated. -You saw some functor/monad examples in the last chapter, but this is more colorful. +You saw some `Functor`/`Monad` examples in the last chapter, but this is more colorful. `Meta` instances are functions that given an instance of the `Lux` compiler state, will perform some calculations which may fail (_with an error message_); but if they succeed, they yield a value, plus a (_possibly updated_) instance of the `Lux` compiler state. @@ -93,72 +103,15 @@ The compiler is only ever present during... well... compilation. And that is precisely when all of your `Lux`-dependant code will execute. -Basically, in order for you to get your hands on that _sweet_ compiler information, your code must be run at compile-time. But only macro code can ever do that, so you will have to wait until the next chapter to learn how this story ends. - -## Definition annotations - -Another important piece of information you should be aware of is that definitions don't just have values and types associated with them, but also arbitrary meta-data which you can customize as much as you want. - -The relevant types in the `library/lux` module are: - -```clojure -(type: #export Location - {#module Text - #line Nat - #column Nat}) - -(type: #export (Ann m v) - {#meta m - #datum v}) - -(type: #export (Code' w) - (#Bit Bit) - (#Nat Nat) - (#Int Int) - (#Rev Rev) - (#Frac Frac) - (#Text Text) - (#Identifier Name) - (#Tag Name) - (#Form (List (w (Code' w)))) - (#Tuple (List (w (Code' w)))) - (#Record (List [(w (Code' w)) (w (Code' w))]))) - -(type: #export Code - (Ann Location (Code' (Ann Location)))) -``` - -You can add annotations to definitions in the many definition macros offered in the standard library. -You can also annotate modules when using the `module:` macro. +Basically, in order for you to get your hands on that _sweet_ compiler information, your code must be run at compile-time. -All you need to do is pass in some record syntax, with tags signaling the type of annotation you want, and the associated value being either an explicit variant `Code`, or some function or macro call that would produce such a value. - -Here's an example from `library/lux`: - -```clojure -(def: #export (is? reference sample) - {#.doc (doc "Tests whether the 2 values are identical (not just 'equal')." - "This one should succeed:" - (let [value +5] - (is? value value)) - - "This one should fail:" - (is? +5 (+ +2 +3)))} - (All [a] (-> a a Bit)) - ("lux is" reference sample)) -``` - - The (_optional_) annotations always goes after the declaration or name of the thing being defined. - -Note that all tag usage within annotation records should be fully qualified, to avoid potential confusions, as different modules could be using annotation tags with similar names. - -The `library/lux/meta/annotation` module contains various functions for reading and exploring annotations, and some modules in the standard library (for example, the `lbrary/lux/ffi` module) make heavy use of annotations to figure out properties of definitions which may be useful during code-generation and parsing in macros. - -And also, as you can appreciate from the previous example, some macros may be designed to be used during annotation specification. +But only macro code can ever do that, so you will have to wait until the next chapter to learn the conclusion to this story. --- -This chapter feels a little empty because the topic only makes sense within the context of macros. But macros by themselves are a huge subject, and involve more machinery than you've seen so far. +This chapter feels a little empty because the topic only makes sense within the context of macros. + +But macros by themselves are a huge subject, and involve more machinery than you've seen so far. However, I wanted to give you a taste of what's possible in order to whet your appetite, while keeping the chapter focused. diff --git a/documentation/book/the_lux_programming_language/index.md b/documentation/book/the_lux_programming_language/index.md index 1edc17559..63583ae39 100644 --- a/documentation/book/the_lux_programming_language/index.md +++ b/documentation/book/the_lux_programming_language/index.md @@ -18,7 +18,7 @@ * [Chapter 8: Functors and monads](chapter_8.md) _Where I will try to explain something really confusing, and you'll pretend you understand to avoid hurting my feelings._ * [Chapter 9: Meta-programming](chapter_9.md) - _Where we go meta. For real._ + _Where we take a peek behind the curtains of the compiler._ * [Chapter 10: Code and macros](chapter_10.md) _Where magic turns into science._ * [Chapter 11: Syntax macros](chapter_11.md) @@ -34,7 +34,7 @@ * [Chapter 16: Testing](chapter_16.md) _Where you will learn how to avoid annoying bug reports._ * [Chapter 17: Cross-platform Lux](chapter_17.md) - _Where you will sail to exotic foreign platforms on the S.S. Lux._ + _Where you will sail to exotic foreign platforms aboard the S.S. Lux._ * [Chapter 18: Extensions](chapter_18.md) _Where you will teach new tricks to an old compiler._ * [Conclusion](conclusion.md) diff --git a/documentation/book/the_lux_programming_language/introduction.md b/documentation/book/the_lux_programming_language/introduction.md index 8b335bf37..4b75ac8f1 100644 --- a/documentation/book/the_lux_programming_language/introduction.md +++ b/documentation/book/the_lux_programming_language/introduction.md @@ -1,16 +1,8 @@ # Introduction The Lux programming language is a functional language belonging to the Lisp family. -It features a flexible and expressive static type-system, and it's meant to run in a variety of different platforms. - -Lux is currently in development. -Some of the features expected of the language have yet to be added (in particular, more compilers to support more platforms). - -Despite this, Lux has already come far enough in its development that it can be used to write a variety of programs that can run on the Java Virtual Machine, the first platform targeted by Lux. - -The semantics of Lux are in no way tied to those of the JVM, and as such, Lux should be understood as a universal language; meant to express programs in a way that is as cross-platform as possible, while at the same time able to tap into the richness that each particular platform has got to offer. -Besides the focus on targeting multiple platforms, Lux's design also covers several other important topics in computer science and software engineering. +It features a flexible and expressive static type-system, and it's meant to run in a variety of different platforms. Lux is committed to the functional style of program design, being a purely-functional programming language, while also adopting eager-evaluation over lazy-evaluation, to promote simpler reasoning over the performance and behavior of programs. @@ -19,13 +11,16 @@ Lux also offers novel features in the area of meta-programming, with first-class While the richness and variety of what Lux has got to offer is much larger than what can be described in this introduction, hopefully I've already mentioned enough to stimulate the curiosity of those interested in advanced concepts in programming languages and computer science, and those engineers seeking powerful tools for both program design and implementation. Lux is both a simple and a complex language. -It's design allows you to make effective programs with just a small subset of what it has to offer, but the goal of the language is to provide its users with an arsenal of powerful tools to suit their various needs in their projects. + +Its design allows you to make effective programs with just a small subset of what it has to offer, but the goal of the language is to provide its users with an arsenal of powerful tools to suit their various needs in their projects. Finally, I must note that Lux is a practical language, meant for day-to-day usage by software engineers, instead of just research and experimentation by academics. + It may seem unnecessary to point that out, but both Lisp-like languages and functional languages have earned a reputation for being academic in nature. + While Lux's design does involve a lot of advanced ideas in computer science, it is with the intention of turning Lux into a powerful and effective tool for day-to-day software engineering. -It is my hope that within these pages the reader will find both a host of new ideas to enrich his/her perspective on programming, and the promise of great power, should they choose to add Lux to their arsenal of programming languages. +It is my hope that within these pages the reader will find both a host of new ideas to enrich their perspective on programming, and the promise of great power should they choose to add Lux to their arsenal of programming languages. I wish you, my dear reader, good luck on this journey, and much fun! |