diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index e9ad9e9552b..6992db99c67 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -748,7 +748,7 @@ fn to_expr_report<'b>( }; let it_is = match annotation_source { - TypedIfBranch { index, .. } => format!("The {} branch is", index.ordinal()), + TypedIfBranch { .. } => "This branch is".to_string(), TypedWhenBranch { index, .. } => format!("The {} branch is", index.ordinal()), TypedBody { .. } => "The body is".into(), RequiredSymbol { .. } => "The provided type is".into(), @@ -4667,17 +4667,19 @@ fn type_problem_to_pretty<'b>( } } - (IntFloat, _) => alloc.tip().append(alloc.concat([ - alloc.reflow("You can convert between "), - alloc.type_str("Int"), - alloc.reflow(" and "), - alloc.type_str("Frac"), - alloc.reflow(" using functions like "), - alloc.symbol_qualified(Symbol::NUM_TO_FRAC), - alloc.reflow(" and "), - alloc.symbol_qualified(Symbol::NUM_ROUND), - alloc.reflow("."), - ])), + (IntFloat, _) => { + alloc.tip().append(alloc.concat( + [ + alloc.reflow( + "You can convert between integers and fractions using functions like ", + ), + alloc.symbol_qualified(Symbol::NUM_TO_FRAC), + alloc.reflow(" and "), + alloc.symbol_qualified(Symbol::NUM_ROUND), + alloc.reflow("."), + ], + )) + } (TagsMissing(missing), ExpectationContext::WhenCondition) => match missing.split_last() { None => alloc.nil(), diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index e4e5e067eff..32732ceb7d8 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -1064,8 +1064,8 @@ mod test_reporting { Every element in a list must have the same type! - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -1532,7 +1532,7 @@ mod test_reporting { 5│ x = if Bool.true then 3.14 else 4 ^^^^ - The 1st branch is a fraction of type: + This branch is a fraction of type: Frac * @@ -1540,8 +1540,8 @@ mod test_reporting { Int * - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -1575,8 +1575,8 @@ mod test_reporting { Int * - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -1607,8 +1607,8 @@ mod test_reporting { Int * - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -1928,8 +1928,8 @@ mod test_reporting { { x : Int * } - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -2485,8 +2485,8 @@ mod test_reporting { Int * - Tip: You can convert between Int and Frac using functions like - `Num.toFrac` and `Num.round`. + Tip: You can convert between integers and fractions using functions + like `Num.toFrac` and `Num.round`. "### ); @@ -11001,7 +11001,7 @@ In roc, functions are always written as a lambda, like{} 5│ f = if Bool.true then {a: ""} else {a: "b", b: ""} ^^^^^^^ - The 1st branch is a record of type: + This branch is a record of type: { a : Str } diff --git a/www/generate_tutorial/src/tutorial.roc b/www/generate_tutorial/src/tutorial.roc index 545b816db2d..b03a16145e9 100644 --- a/www/generate_tutorial/src/tutorial.roc +++ b/www/generate_tutorial/src/tutorial.roc @@ -2,7 +2,7 @@ app "roc-tutorial" packages { pf: "../../../examples/static-site-gen/platform/main.roc" } imports [ pf.Html.{ html, head, body, footer, script, div, main, p, section, h1, h2, label, ol, input, text, nav, a, li, link, meta }, - pf.Html.Attributes.{ content, name, for, id, type, href, rel, lang, class, title, charset, src }, + pf.Html.Attributes.{ content, name, for, id, type, href, rel, lang, title, charset, src, role }, ] provides [transformFileContent] to pf @@ -36,9 +36,9 @@ view = \htmlContent -> viewNavbar : Html.Node viewNavbar = - div [id "top-bar"] [ + div [id "top-bar", role "presentation"] [ nav [] [ - a [class "home-link", href "/", title "The Roc Programming Language"] [text "roc"], + a [id "nav-home-link", href "/", title "The Roc Programming Language"] [text "roc"], div [id "top-bar-links"] [ a [href "/tutorial"] [text "tutorial"], a [href "https://github.com/roc-lang/roc/tree/main/getting_started"] [text "install"], diff --git a/www/public/logo.svg b/www/public/logo.svg deleted file mode 100644 index bb673d50139..00000000000 --- a/www/public/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/www/public/repl/index.html b/www/public/repl/index.html index 28887eda6ec..011bc9cdf10 100644 --- a/www/public/repl/index.html +++ b/www/public/repl/index.html @@ -17,13 +17,13 @@
-
-
+

The rockin’ Roc REPL

@@ -33,8 +33,8 @@

The rockin’ Roc REPL

- - +
+ diff --git a/www/public/repl/repl.css b/www/public/repl/repl.css index e35e6d497ae..dcddfea7693 100644 --- a/www/public/repl/repl.css +++ b/www/public/repl/repl.css @@ -153,7 +153,7 @@ li { text-align: center; } - main { + #repl { margin: 0; padding: 0; min-height: calc(100vh - var(--top-bar-height)); diff --git a/www/public/site.css b/www/public/site.css index 6f2a878ecb0..45eda7e93de 100644 --- a/www/public/site.css +++ b/www/public/site.css @@ -88,7 +88,7 @@ li { justify-content: space-between; } -#top-bar .home-link { +#nav-home-link { display: flex; color: var(--top-bar-fg); font-size: 1.8rem; diff --git a/www/wip_new_website/build-dev-local.sh b/www/wip_new_website/build-dev-local.sh old mode 100644 new mode 100755 index 1eb1743a681..a0e0c5a2548 --- a/www/wip_new_website/build-dev-local.sh +++ b/www/wip_new_website/build-dev-local.sh @@ -1,14 +1,13 @@ #!/usr/bin/env bash -# Use this script to for testing the WIP site locally without downloading assets every time. +# Use this script to for testing the WIP site locally without downloading assets every time. -# NOTE run `bash www/build.sh` to cache local copy of fonts, and repl assets etc +# NOTE run `bash www/build.sh` to cache local copy of fonts, and repl assets etc rm -rf dist/ +cp -r ../build dist/ mkdir -p dist/wip -cp -r ../build/wip/* dist/wip/ roc run main.roc -- content/ dist/wip/ -cp -r ../build/fonts/ dist/fonts/ cp -r static/* dist/wip/ -simple-http-server -p 8080 --nocache --index -- dist/ +simple-http-server -p 8080 --nocache --index -- dist/ diff --git a/www/wip_new_website/content/bdfn.md b/www/wip_new_website/content/bdfn.md new file mode 100644 index 00000000000..1681e382fdf --- /dev/null +++ b/www/wip_new_website/content/bdfn.md @@ -0,0 +1,13 @@ +# BDFN + +Hi, I'm [Richard](https://github.com/rtfeldman)! I created Roc in 2018 and am currently its BDFN. + +BDFN stands for Benevolent Dictator For Now. There's a whole group of wonderful people collaborating to advance Roc, and we discuss major decisions together before committing to a particular direction. That said, once we reach the point in our discussions where a decision has to be made, I—as BDFN—have the final say...for now. + +The difference between BDFN and [Benevolent Dictator For Life](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) is that I plan to transition away from being in charge of Roc with plenty of time left in my life to watch others take the helm and see how they run things. My last act as BDFN should be to hand over decision-making responsibility to a new system, whatever that ends up being. + +Something that needs figuring out between now and then is how to keep the language from growing excessively complex over time. Many committee-driven languages accumulate new features at a rate that would be bad for Roc's long-term health, and I hope we can find a system which minimizes the erosion of simplicity that seems to happen by default. + +(This would be easy to solve if zero happened to be the optimal number of new features to add over time, but unfortunately the programming landscape will change around the language, so remaining static is unlikely to be best. As such, the goal becomes finding a system which keeps the number of new features low without artificially forcing it to zero.) + +I don't have any plans for when the transition away from BDFN will happen, other than that I certainly imagine it being well after a 1.0 release of the language. diff --git a/www/wip_new_website/content/community.md b/www/wip_new_website/content/community.md index b87e517e779..ac8e71a7b45 100644 --- a/www/wip_new_website/content/community.md +++ b/www/wip_new_website/content/community.md @@ -1,41 +1,32 @@ # Community -WIP +[Roc Zulip Chat](https://roc.zulipchat.com/) is the most active community gathering place. +We love it when new people stop by and introduce themselves in [`#introductions`](https://roc.zulipchat.com/#narrow/stream/387892-introductions) so others can welcome you to the community! -## Values +The Roc Online Meetup meets about once per month. The organizer posts a [when2meet](https://when2meet.com) for the next month's meetup, so we can find the best time that works for everyone +who's interested in attending. This happens in [`#gatherings` on Zulip](https://roc.zulipchat.com/#narrow/stream/303057-gatherings). The organizer will then make a post on Zulip with a finalized time and a link to where people can join. They usually last between 1-2 hours, and consist of some informal presentations followed by casual conversation. They're fun! - - -## Meet - -- Group Chat [roc.zulipchat.com](https://roc.zulipchat.com/) -- Github Project [roc-lang/roc](https://github.com/roc-lang/roc) -- Meetups & Events - -## Proposals +## Code of Conduct -- [Good First Issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +The Roc project enforces [a code of conduct](https://github.com/roc-lang/roc/blob/main/CODE_OF_CONDUCT.md). Please read and follow it! - +All the source code to the Roc project is on GitHub in the [roc-lang](https://github.com/roc-lang) organization. The compiler and CLI are at [roc-lang/roc](https://github.com/roc-lang/roc), and there's a tag for [Good First Issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). -Roc doesn't have a formal process for managing design proposals. The guiding principal for now is that as a community we should all be friendly, supportive, and openly discuss and sharing ideas. +The project has [many contributors](https://github.com/roc-lang/roc/graphs/contributors), and we like to invest in helping new contributors get started. If you'd like to become a new contributor (even if you don't know what you'd like that contribution to be yet), just make a post in [the "new contributors" topic](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello! -There are three loose stages that a design proposal can go through in Roc: Idea, Proposal, and Implementation. +## Design ideas, feature requests, proposals, etc. -In the Idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and trade-offs. It is helpful to share the idea in the group chat using the #ideas channel. +Roc doesn't have a formal process for managing design proposals. At the current size of the project, having a formal process (e.g. a [RFC](https://en.wikipedia.org/wiki/Change_request) system) would be more heavyweight than it's worth. Fow now, the guiding principle is that as a community we should all be friendly, supportive, and openly share and discuss ideas without the expectation that they will necessarily be accepted or not. We follow a [BDFN](/bdfn) leadership model today, although this is planned to change someday. -If there is a general consensus that an idea is promising and worth developing, people are encouraged to develop a design Proposal. Some ideas are simple and should progress straight to implementation. However, for more complex Proposals a it write the design into an article or blog post and share it online. This documentation helps in clarifying the proposal and making it more accessible to the community. +There are three loose stages that a design proposal can go through in Roc: idea, proposal, and implementation. These are guidelines, not strict requirements; their goal is to prevent the outcome where someone does a lot of implementation work only to have their contribution never make it into the code base because it's determined that we wanted to go in a different design direction. Confirming ahead of time that the design direction is desired can prevent implementing something that ends up not being used. -Implementation using PR... +In the idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and trade-offs. It's a good idea to share the idea in [`#ideas` on Zulip](https://roc.zulipchat.com/#narrow/stream/304641-ideas). There's no prerequisite for sharing an idea (it's only an idea, after all!) and likewise there's also no obligation for any contributor to necessarily act on it. -## Code of Conduct +If the idea seems promising and worth developing further (as confirmed by a Roc contributor with expertise in the relevant area, doesn't have to be the [BDFN](/bdfn)), usually the next step is to get more specific with a written proposal that details all the necessary information about what the change would involve. A written proposal isn't always necessary (e.g. it may be deemed a simple and uncontroversial enough change that we're comfortable proceeding straight to implementation), but since writing proposals can be time-consuming, it's definitely a good idea to get confirmation at the idea stage from an experienced contributor before taking the time to write one up. - +There's no guarantee that a proposal will be accepted, and even if it is, there's no guarantee that something won't come up during implementation that changes the tradeoffs and means it doesn't end up making it into the language after all. But if it is accepted (again, doesn't have to be by the [BDFN](/bdfn) - although the BDFN does have final say if there's a disagreement at some point), that means a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) should be reviewed, and the general understanding is that the PR will be accepted unless some problem is discovered during implementation that wasn't anticipated at the proposal stage. diff --git a/www/wip_new_website/content/sponsor.md b/www/wip_new_website/content/donate.md similarity index 86% rename from www/wip_new_website/content/sponsor.md rename to www/wip_new_website/content/donate.md index 71a00cf1050..3990828612f 100644 --- a/www/wip_new_website/content/sponsor.md +++ b/www/wip_new_website/content/donate.md @@ -1,8 +1,8 @@ -# Sponsor +# Donate - [Github Sponsorship](https://github.com/sponsors/roc-lang) - \ No newline at end of file + --> diff --git a/www/wip_new_website/content/fast.md b/www/wip_new_website/content/fast.md new file mode 100644 index 00000000000..6eaf2cda1a6 --- /dev/null +++ b/www/wip_new_website/content/fast.md @@ -0,0 +1,51 @@ +# Fast + +Roc code is designed to build fast and run fast...but what does "fast" mean here? And how close is Roc's curent implementation to realizing that design goal? + +## Fast Programs + +What "fast" means in embedded systems is different from what it means in games, which in turn is different from what it means on the Web. To better understand Roc’s performance capabilities, let's look at the upper bound of how fast optimized Roc programs are capable of running, and the lower bound of what languages Roc should generally outperform. + +**Limiting factors: memory management and async I/O.** Part of Roc's design is that it is a [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust. Another part of Roc's design is that I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead. + +**Faster than dynamic or gradual languages.** As a general rule, Roc programs should have almost strictly less runtime overhead than equivalent programs written in languages with dynamic types and automatic memory management. This doesn't mean all Roc programs will outperform all programs in these languages, just that Roc should have a higher ceiling on what performance is achievable. This is because dynamic typing (and gradual typing) requires tracking types at runtime, which has unavoidable overhead. Roc tracks types only at compile time, and tends to have [minimal (often zero) runtime overhead](https://guide.handmade-seattle.com/c/2021/roc-lang-qa/) for language constructs compared to the top performers in industry. For example, Roc's generics, records, functions, numbers, and tag unions have no more runtime overhead than they would in their Rust or C++ equivalents. + +When [benchmarking compiled Roc programs](https://www.youtube.com/watch?v=vzfy4EKwG_Y), the normal goal is to have them outperform the fastest mainstream garbage-collected languages (for example, Go, C#, Java, and JavaScript), but it's a non-goal to outperform languages that support memory unsafety or manual memory management. There will always be some individual benchmarks where mainstream garbage-collected languages outperform Roc, but the goal is for these to be uncommon cases rather than the norm. + +### Current Progress + +Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety. However, there is plenty of room for further compiler optimizations; current optimizations include only [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), and [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations. + +An open design question is how atomic reference counting should work in the context of platforms. Roc uses automatic reference counting (with some compile-time optimizations) to manage memory, and updating reference counts runs a lot faster if you don't need to guard against [data races](https://en.wikipedia.org/wiki/Race_condition#Data_race). If Roc data structures are shared across threads (which might or might not come up, depending on the platform), then in some cases [atomic](https://en.wikipedia.org/wiki/Linearizability#Primitive_atomic_instructions) reference counting might be necessary, which is slower. + +A design of "always use atomic reference counts" would be unnecessarily slow for platforms where atomicity isn't needed. A design of "never use atomic reference counts" would mean sharing Roc data structures across threads would always require deeply copying the entire data structure (which has worked well for Erlang, but which might not work as well for Roc). It seems likely that the best design is for the compiler to sometimes choose one and sometimes choose the other, but the exact design to go with is currently an open question. + +## Fast Feedback Loops + +The goal is for Roc to provide fast feedback loops by making builds normally feel "instant" except on truly massive projects. It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). Hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program. + +Note that although having fast "clean" builds (without the benefit of caching) is a goal, the "normally feels instant" goal refers to builds where caching was involved. After all, the main downside of build latency is that it comes up over and over in a feedback loop; the initial "clean" build comes up rarely by comparison. + +### Current Progress + +`roc check` type-checks your code and reports problems it finds. `roc build` also does this, but it additionally +builds a runnable binary of your program. You may notice that `roc build` takes much longer to complete! This is because +of two projects that are underway but not completed yet: +- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` on x64 Linux. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these. +- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on Linux, Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking. + +Here's a table summarizing the current progress: + +Target | Dev backend | Surgical linking | +---------------------------|-------------|-------------------| +WebAssembly | yes | yes | +Linux x64 | repl only | yes | +Windows x64 | | yes | +macOS Intel (x64) | | | +Linux ARM | | | +Windows ARM | | | +macOS Apple Silicon (ARM) | | | + +Once we have full coverage, `roc build` (and `roc run` and `roc test`, which also perform builds) should take only a bit longer than `roc check`. + +The next major performance improvement will be caching. Currently, `roc` always builds everything from scratch. Most of the time, it could benefit from caching some of the work it had done in a previous build, but today it doesn't do that. There's a design for the caching system, but essentially none of the implementation has started yet. Hot code loading will be the next major improvement after caching, but it requires full dev backend coverage, and does not have a concrete design yet. diff --git a/www/wip_new_website/content/friendly.md b/www/wip_new_website/content/friendly.md new file mode 100644 index 00000000000..6840edac4ad --- /dev/null +++ b/www/wip_new_website/content/friendly.md @@ -0,0 +1,72 @@ +# Friendly + +Roc prioritizes being a user-friendly language. This impacts the syntax, semantics, and tools Roc ships with. + +## Syntax and source code formatter + +Roc's syntax isn't trivial, but there also isn't much of it to learn. Its design is generally uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples: + +- `x = combine y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `combine` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.) +- `user.email` always accesses the `email` field of a record named `user`. (Roc has no inheritance, subclassing, or proxying.) +- `Email.isValid` always refers to something named `isValid` exported by a module named `Email`. (Module names are always capitalized, and variables/constants never are.) Modules are always defined statically and can't be modified at runtime; there's no [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) to consider. +- `"My name is \(Str.trim name)"` uses *string interpolation* syntax: a backslash inside a string literal, followed by an expression in parentheses. This code is the same as combining the string `"My name is "` with the string returned by the function call `Str.trim name`. Because Roc's string interpolation syntax begins with a backslash (just like other backlash-escapes such as `\n` and `\"`), you can always tell which parts of a string involve special handling: the parts that begin with backslashes. Everything else works as normal. + +Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves you all the time that would otherwise be spent debating which stylistic tweaks to settle on! + +## Helpful compiler + +Roc's compiler is designed to help you out. It does complete type inference across all your code, and the type system is *sound*. This means you'll never get a runtime type mismatch if everything type-checked (including null exceptions; Roc doesn't have the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)), and you also don't have to write any type annotations for the compiler to be able to infer all the types in your program. + +If there's a problem at compile time, the compiler is designed to report it in a helpful way. Here's an example: + +``` +── TYPE MISMATCH ────────────────── /home/my-roc-project/main.roc ─ + +Something is off with the `then` branch of this `if` expression: + +4│ someInteger : I64 +5│ someInteger = +6│ if someDecimal > 0 then +7│ someDecimal + 1 + ^^^^^^^^^^^^^^^ + +This branch is a fraction of type: + + Dec + +But the type annotation on `someInteger` says it should be: + + I64 + +Tip: You can convert between integers and fractions using functions like +`Num.toFrac` and `Num.round`. +``` + +If you like, you can run a program that has compile-time errors like this. (If the program reaches the error at runtime, it will crash.) This lets you do things like trying out code that's only partially finished, or running tests for one part of your code base while other parts have compile errors. (Note that this feature is only partially completed, and often errors out; it has a ways to go before it works for all compile errors!) + +## Serialization inference + +- Type inference is used for schema inference, but you can also spell it out if you like +- Reports errors immediately + +## Testing + +You can run `roc test` to run all your tests. Each test is declared with the `expect` keyword, and can be as short as one line. For example, this is a complete test: + +``` +## One plus one should equal two. +expect 1 + 1 == 2 +``` + +If the test fails, `roc test` will show you the source code of the `expect`, along with the values of any named variables inside it, so you don't have to separately check what they were. If you write a documentation comment right before it (like `## One plus one should equal two` here), that will also be included in the test output, so you can use that to optionally describe the test if you want to. + +In the future, there are plans to add built-in support for [benchmarking](https://en.wikipedia.org/wiki/Benchmark_(computing)), [generative tests](https://en.wikipedia.org/wiki/Software_testing#Property_testing), [snapshot tests](https://en.wikipedia.org/wiki/Software_testing#Output_comparison_testing), simulated I/O (so you don't have to actually run the real I/O operations, but also don't have to change your code to accommodate the tests), and "reproduction replays"—tests generated from a recording of what actually happened during a particular run of your program, which deterministically simulate all the I/O that happened. + +- also note: future plan to cache tests so we only re-run tests whose answers could possibly have changed. also maybe note: tests that don't perform I/O are guaranteed not to flake b/c pure functions. + +## Future plans +- Package manager (Currently just URLs, content-hashed to make them immutable) with searchable index and no installation step, global cache of immutable downloads instead of per-project folders (no need to .gitignore anything) +- Step debugger with replay +- Customizable "linter" (e.g. code mods, project-specific rules to enforce) +- Editor plugin ecosystem that works across editors, where plugins ship with packages +- `roc edit` diff --git a/www/wip_new_website/content/functional.md b/www/wip_new_website/content/functional.md new file mode 100644 index 00000000000..bb1f079ed1f --- /dev/null +++ b/www/wip_new_website/content/functional.md @@ -0,0 +1,123 @@ +# Functional + +Roc is designed to have a small number of simple language primitives. This goal leads Roc to be a single-paradigm [functional](https://en.wikipedia.org/wiki/Functional_programming) language, while its [performance goals](/fast) lead to some design choices that are uncommon in functional languages. + +## Opportunistic mutation + +All Roc values are semantically immutable, but may be opportunistically mutated behind the scenes when it would improve performance (without affecting the program's behavior). For example: + +```elm +colors +|> Set.insert "Purple" +|> Set.insert "Orange" +|> Set.insert "Blue" +``` + +The [`Set.insert`](https://www.roc-lang.org/builtins/Set#insert) function takes a `Set` and returns a `Set`. The returned one has the given value inserted into it. Knowing this, it might seem like these three `Set.insert` calls would result in the creation of three brand-new sets, but Roc's *opportunistic mutation* optimizations mean this will be much more efficient than that. + +Opportunistic mutation works by detecting when a semantically immutable value can be safely mutated in-place without changing the behavior of the program. If `colors` is *unique* here—that is, nothing else is currently referencing it, and nothing else will reference it before it goes out of scope—then `Set.insert` will mutate it and then return it. Cloning it first would have no benefit, because nothing in the program could possibly tell the difference! + +If `colors` is not unique, then the first call to `Set.insert` will not mutate it. Instead, it will clone `colors`, insert `"Purple"` into the clone, and then return that. At that point, since the clone will be unique (nothing else is referencing it, since it was just created, and the only thing that will reference it in the future is the `Set.insert` function it's handed off to), the subsequent `Set.insert` calls will all mutate in-place. Roc has ways of detecting uniqueness at compile time, so this optimization will often have no runtime cost, but in some cases it instead uses automatic reference counting to tell when something that was previously shared has become unique over the course of the running program. + +## Everything is immutable (semantically) + +This design means that all Roc values are semantically immutable, even though they can still benefit from the performance of in-place mutation. In many languages, this is reversed; everything is mutable, and it's up to the programmer to "defensively" clone wherever modification is undesirable. Roc's approach means that cloning happens automatically, which can be less error-prone than defensive cloning (which might be accidentally forgotten), but which—to be fair—can also increase unintentional cloning. It's a different default with different tradeoffs. + +A performance benefit of this design compared to having direct mutation primitives is that it lets Roc rule out [reference cycles](https://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles). Any language which supports direct mutation can have reference cycles. Detecting these cycles automatically at runtime has a cost (which has similar characteristics to the cost of a tracing garbage collector), and failing to detect them can result in memory leaks. Roc's automatic reference counting neither pays for runtime cycle collection nor memory leaks from cycles, because the language's lack of direct mutation primitives lets it rule out reference cycles at language design time. + +An ergonomics benefit of having no direct mutation primitives is that functions in Roc tend to be chainable by default. For example, consider the `Set.insert` function. In many languages, this function would be written to accept a `Set` to mutate, and then return nothing. In contrast, in Roc it will necessarily be written to return a (potentially) new `Set`, even if in-place mutation will end up happening anyway if it's unique. + +This makes Roc functions naturally amenable to pipelining, as we saw in the earlier example: + +```elm +colors +|> Set.insert "Purple" +|> Set.insert "Orange" +|> Set.insert "Blue" +``` + +To be fair, direct mutation primitives have benefits too. Some algorithms are more concise or otherwise easier to read when written with direct mutation, and direct mutation can make the performance characteristics of some operations clearer. + +As such, Roc's opportunistic mutation design means that reference cycles can be ruled out, and functions will tend to be more ameanable for chaining, but also that some algorithms will be harder to express, and that performance optimization will likely tend to involve more profiling. These tradeoffs fit well with the language's overall design goals. + +## No reassignment or shadowing + +In some languages, the following is allowed. + +``` +x = 1 +x = 2 +``` + +In Roc, this will give a compile-time error. Once a name has been assigned to a value, nothing in the same scope can assign it again. (This includes [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing), which is disallowed.) This can make Roc code easier to read, because the answer to the question "might this have a different value at some later point in the scope?" is always "no." That said, this can also make Roc code take longer to write, because of needing to come up with unique names to avoid shadowing, although pipelining (as shown in the previous section) reduces how often intermediate values need to be named. + +A benefit of this design is that it makes Roc code easier to rearrange without causing regressions. Consider this code: + +```elm +func = \arg -> + greeting = "Hello" + welcome = \name -> "\(greeting), \(name)!" + … + message = welcome "friend" + … +``` + +Suppose I decide to extract the `welcome` function to the top level, so I can reuse it elsewhere: + +```elm +func = \arg -> + … + message = welcome "Hello" "friend" + … + +welcome = \prefix, name -> "\(prefix), \(name)!" +``` + +Without knowing the rest of `func`, we can be confident this change will not alter the code's behavior. In contrast, suppose Roc allowed reassignment. Then it's possible something in the `…` parts of the code could have modified `greeting` before it was used in the `message =` declaration. For example: + +```elm +func = \arg -> + greeting = "Hello" + welcome = \name -> "\(greeting), \(name)!" + … + if someCondition then + greeting = "Hi" + … + else + … + … + message = welcome "friend" + … +``` + +In this example, if we didn't read the whole function to see that `greeting` was later sometimes (but not always) changed from `"Hello"` to `"Hi"`, we might not have realized that changing it to `message = welcome "Hello" "friend"` would cause a regression due to having the greeting always be `"Hello"`. Because Roc disallows reassignment, this particular regression can't happen, and so the code can be confidently rearranged without checking the rest of the function. + +Even if Roc disallowed reassignment but allowed shadowing, a similar regression could happen if the `welcome` function were shadowed between when it was defined here and when `message` later called it in the same scope. Because Roc allows neither shadowing nor reassignment, these regressions can't happen, and rearranging code can be done with more confidence. + +In fairness, reassignment has benefits too. For example, using it with [early-exit control flow operations](https://en.wikipedia.org/wiki/Control_flow#Early_exit_from_loops) such as a `break` keyword can be a nice way to represent certain types of logic without incurring extra runtime overhead. Roc does not have these operations; looping is done either with convenience functions like [`List.walkUntil`](https://www.roc-lang.org/builtins/List#walkUntil) or with recursion (Roc implements [tail-call optimization](https://en.wikipedia.org/wiki/Tail_call), including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)), but early-exit operators can potentially make some code easier to follow (and potentially even slightly more efficient) when used in scenarios where breaking out of nested loops with a single instruction is desirable. + +## Managed effects over side effects + +Many languages support first-class [asynchronous](https://en.wikipedia.org/wiki/Asynchronous_I/O) effects, which can improve a system's throughput (usually at the cost of some latency) especially in the presence of long-running I/O operations like network requests. Asynchronous effects are commonly represented by a value such as a [Promise or Future](https://en.wikipedia.org/wiki/Futures_and_promises) (Roc calls these Tasks), which represent an effect to be performed. These values can be composed together, potentially while customizing their concurrency properties and supporting I/O interruptions like cancellation and timeouts. + +Most languages also have a separate system for synchronous effects, namely [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). Having demand for two ways to perform the same effect can lead to a great deal of duplication across a language's ecosystem. + +Instead of having [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), Roc functions exclusively use *managed effects* in which they return descriptions of effects to run, in the form of Tasks. Tasks can be composed and chained together, until they are ultimately handed off (usually via a `main` function or something similar) to an effect runner outside the program, which actually performs the effects the tasks describe. + +Having only (potentially asynchronous) *managed effects* and no (synchronous) *side effects* both simplifies the language's ecosystem and makes certain guarantees possible. For example, the combination of managed effects and semantically immutable values means all Roc functions are [pure](https://en.wikipedia.org/wiki/Pure_function)—that is, they have no side effects and always return the same answer when called with the same arguments. + +## Pure functions + +Pure functions have a number of implementation benefits, such as [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) and being trivial to [memoize](https://en.wikipedia.org/wiki/Memoization). They also have testing benefits; for example, Roc tests which either use simulated effects (or which do not involve tasks at all) never flake. They either consistently pass or consistently fail. Because of this, their results can be cached, so `roc test` can skip re-running them unless their source code (including dependencies) changed. (This caching has not yet been implemented, but is planned.) + +Roc does support [tracing](https://en.wikipedia.org/wiki/Tracing_(software)) via the `dbg` keyword, an essential [debugging](https://en.wikipedia.org/wiki/Debugging) tool which is unusual among side effects in that—similarly to opportunistic mutation—using it should not affect the behavior of the program. As such, it usually does not impact the guarantees of pure functions aside from potentially impeding optimizations. (An example of an exception to this would be if a program sent its `dbg` traces to log files which the program itself then read back in. This is not recommended.) + +Pure functions are notably amenable to compiler optimizations. Roc already takes advantage of them to implement [function-level dead code elimination](https://elm-lang.org/news/small-assets-without-the-headache). Here are some other examples of optimizations that will benefit from this in the future; these are planned, but not yet implemented: + +- [Loop fusion](https://en.wikipedia.org/wiki/Loop_fission_and_fusion), which can do things like combining consecutive `List.map` calls (potentially intermingled with other operations that traverse the list) into one pass over the list. +- [Compile-time evaluation](https://en.wikipedia.org/wiki/Compile-time_function_execution), which basically takes [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to its natural limit: anything that can be evaluated at compile time is evaluated then. This saves work at runtime, and is easy to opt out of: if you want evaluation to happen at runtime, you can instead wrap the logic in a function and call it as needed. +- [Hoisting](https://en.wikipedia.org/wiki/Loop-invariant_code_motion), which moves certain operations outside loops to prevent them from being re-evaluated unnecessarily on each step of the loop. It's always safe to hoist calls to pure functions, and in some cases they can be hoisted all the way to the top level, at which point they become eligible for compile-time evaluation. + +## Get started + +If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](https://www.roc-lang.org/tutorial)! diff --git a/www/wip_new_website/content/index.md b/www/wip_new_website/content/index.md index da9df3de674..af6ff72d1e6 100644 --- a/www/wip_new_website/content/index.md +++ b/www/wip_new_website/content/index.md @@ -1,35 +1,49 @@ + -# Roc - -A work-in-progress programming language that aims to be fast, friendly, and functional. - - -- [tutorial](/wip/tutorial.html) - -- [install](/wip/install.html) - -- [help / group chat](https://roc.zulipchat.com), we're friendly! - -## Goals
-

Fast

-

Delightful software runs fast. The Roc compiler should run fast, and it should be able to produce programs that run fast too.

-

What does fast mean here?

+

Fast

+

Roc code is designed to build fast and run fast. It compiles to machine code or WebAssembly.

+

What does fast mean here?

Friendly

-

Roc aims to be a user-friendly language with a friendly community of users. This involves the set of tools Roc includes, and also the spirit of the community of Roc programmers.

-

What does friendly mean here?

+

Roc's syntax, semantics, and toolset are designed to feel user-friendly and helpful.

+

What does friendly mean here?

Functional

-

Roc is a purely functional programming language. The language is built on a small set of simple primitives, which together give you a toolset that's more than the sum of its parts.

+

+ Roc has a small number of simple language primitives. It's a single-paradigm functional language.

What does functional mean here?

@@ -37,14 +51,13 @@ A work-in-progress programming language that aims to be fast, friendly, and func ## Try Roc -
Loading REPL WebAssembly module…please wait!
-
diff --git a/www/wip_new_website/content/install.md b/www/wip_new_website/content/install.md index 7ce61eec318..e6cb3e9e30b 100644 --- a/www/wip_new_website/content/install.md +++ b/www/wip_new_website/content/install.md @@ -1,5 +1,13 @@ # Install Roc +Roc is a work in progress. It doesn't have a numbered release yet, but it does have nightly builds that you can download. + +There are currently a few OS-specific issues: +* macOS: There are no known compatibility issues, but the compiler doesn't run as fast as it does on Linux or Windows, because we don't (yet) do our own linking like we do on those targets. (Linking works similarly on Linux and Windows, but the way macOS does it is both different and significantly more complicated.) +* Windows: There are some known Windows-specific compiler bugs, and probably some other unknown ones because more people have tried out Roc on Mac and Linux than on Windows. +* Linux: The nightlies are built with glibc, so they aren't usable on distros that don't use (dynamically linked) glibc, like Alpine or NixOS. In the future we plan to build Linux releases with [musl libc](https://wiki.musl-libc.org/) to address this, but this requires [building LLVM from source with musl](https://wiki.musl-libc.org/building-llvm.html). +* Other operating systems: Roc has not been built on any other operating systems. Building from source on them might work, but hasn't been tried. + - [Linux x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/linux_x86_64.md) @@ -16,7 +24,7 @@ - Script `roc myApp.roc` -- Develop `roc dev` +- Develop `roc dev` - Test `roc test` - Run `roc run` - Build `roc build` @@ -29,7 +37,7 @@ You can include packages using an URL: ```roc app "hello" - packages { + packages { # basic-cli platform pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br", # json package @@ -45,9 +53,9 @@ app "hello" A full Roc package manager will be developed in the future. -