-
-
Notifications
You must be signed in to change notification settings - Fork 115
Initial prototype of interactive tutorials #677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jducoeur
wants to merge
5
commits into
main
Choose a base branch
from
jducoeur/tutorials_prototype
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+695
−1
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| {% | ||
| laika.title = Tutorials | ||
| %} | ||
|
|
||
| # Typelevel Tutorials | ||
|
|
||
| Welcome! These tutorials are hands-on: each lesson explains an idea on the left side | ||
| of the page and gives you a live Scala editor on the other. Edit the code, press | ||
| **Run**, and see what happens. | ||
|
|
||
| @:style(bulma-notification bulma-is-warning bulma-is-light) | ||
| This section is an early prototype. The lessons below exist to show that the | ||
| machinery works; their content is simply a few vibe-coded example pages. They | ||
| should be replaced by human-curated tracks that say what we want to say. | ||
| @:@ | ||
|
|
||
| ## Cats | ||
|
|
||
| [Cats](https://typelevel.org/cats/) is the foundation of the Typelevel | ||
| ecosystem: a library of type classes and data types for functional programming | ||
| in Scala. | ||
|
|
||
| @:navigationTree { | ||
| entries = [ | ||
| { target = "/tutorials/cats", excludeRoot = true, excludeSections = true } | ||
| ] | ||
| } | ||
|
|
||
| ## Cats Effect | ||
|
|
||
| [Cats Effect](https://typelevel.org/cats-effect/) builds on Cats to describe | ||
| side effects as values, and gives you a runtime to execute them. | ||
|
|
||
| @:navigationTree { | ||
| entries = [ | ||
| { target = "/tutorials/cats-effect", excludeRoot = true, excludeSections = true } | ||
| ] | ||
| } | ||
|
|
||
| These tutorials were inspired by [scalatutorials](https://scalatutorials.com), which teaches | ||
| the Scala language itself. If you're just starting out, we recommend check that out first, | ||
| then come back here to help dive into Typelevel! | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| laika.title = Cats Effect | ||
| laika.html.template = /tutorials/tutorial.template.html | ||
| laika.navigationOrder = [io.md, unsafe-run.md] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| {% | ||
| laika.title = IO | ||
| scastie.code = io.sc | ||
| %} | ||
|
|
||
| An `IO[A]` is a *description* of a computation that may perform side effects and | ||
| eventually produce an `A`. Building one runs nothing at all: `IO(println("hi"))` | ||
| is a value you can pass around, store in a list, or throw away. | ||
|
|
||
| That is the whole idea. Once effects are values, the ordinary tools work on | ||
| them: you can `map` and `flatMap` them into bigger programs, combine them in a | ||
| `for` comprehension, and repeat one simply by using it twice. | ||
|
|
||
| Notice in the editor that nothing is printed while the `IO`s are being built. | ||
| The description is inert until somebody runs it — which is the subject of the | ||
| next lesson. | ||
|
|
||
| **Try it:** reorder the steps in the `for` comprehension, or build an `IO` and | ||
| never use it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| //> using dep org.typelevel::cats-effect:3.6.3 | ||
|
|
||
| import cats.effect.IO | ||
|
|
||
| // building an IO performs no effect: this line prints nothing | ||
| val hello: IO[Unit] = IO(println("hello")) | ||
|
|
||
| // bigger programs are built out of smaller ones | ||
| val program: IO[Unit] = | ||
| for | ||
| _ <- hello | ||
| _ <- IO(println("and again")) | ||
| n <- IO(21 * 2) | ||
| _ <- IO(println(s"the answer is $n")) | ||
| yield () | ||
|
|
||
| // still nothing has happened; `program` is only a description | ||
| println("built the program, and nothing has run yet") | ||
| println(program) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| {% | ||
| laika.title = Running an IO | ||
| scastie.code = unsafe-run.sc | ||
| %} | ||
|
|
||
| Descriptions have to be executed eventually. In a real application that happens | ||
| once, at the edge of the world: you extend `IOApp`, hand it your program, and | ||
| Cats Effect runs it on its own runtime. | ||
|
|
||
| The `unsafeRun*` methods let you do it by hand. They are called *unsafe* not | ||
| because they are broken, but because they break the property that makes `IO` | ||
| worth having: they actually perform effects, and they can block or throw. Every | ||
| call is a place where functional reasoning stops. | ||
|
|
||
| - `unsafeRunSync()` blocks the current thread until the program finishes. | ||
| - `unsafeRunAndForget()` starts it and returns immediately. | ||
| - `unsafeToFuture()` hands you a `Future` of the result. | ||
|
|
||
| Running the same description twice performs the effects twice: an `IO` is a | ||
| recipe, not a cached result. | ||
|
|
||
| **Try it:** run `program` a second time, or comment out the `unsafeRunSync()` | ||
| call and watch the output disappear. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| //> using dep org.typelevel::cats-effect:3.6.3 | ||
|
|
||
| import cats.effect.IO | ||
| import cats.effect.unsafe.implicits.global | ||
|
|
||
| val program: IO[Int] = | ||
| for | ||
| _ <- IO(println("starting")) | ||
| n <- IO(21 * 2) | ||
| _ <- IO(println(s"finished with $n")) | ||
| yield n | ||
|
|
||
| // nothing above this line has printed anything | ||
| println("about to run") | ||
|
|
||
| val result = program.unsafeRunSync() | ||
| println(s"got back $result") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| laika.title = Cats | ||
| laika.html.template = /tutorials/tutorial.template.html | ||
| laika.navigationOrder = [functor.md, monoid.md] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| {% | ||
| laika.title = Functor | ||
| scastie.code = functor.sc | ||
| %} | ||
|
|
||
| A `Functor` is anything you can `map` over: a container or a context whose | ||
| contents can be transformed without changing the shape of the context itself. | ||
| `List`, `Option` and `Either` are all functors, and so are many types that don't | ||
| look like collections at all. | ||
|
|
||
| Cats captures this as a type class with a single abstract method: | ||
|
|
||
| ```scala | ||
| trait Functor[F[_]]: | ||
| def map[A, B](fa: F[A])(f: A => B): F[B] | ||
| ``` | ||
|
|
||
| Because the type class is abstract in `F`, you can write code once and run it | ||
| against every functor. The `plusOne` method in the editor never mentions `List` | ||
| or `Option`, yet it works with both. | ||
|
|
||
| **Try it:** call `plusOne` with a `Vector`, or map an `Option` that is `None` | ||
| and see what comes back. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| //> using dep org.typelevel::cats-core:2.13.0 | ||
|
|
||
| import cats.Functor | ||
| import cats.syntax.all.* | ||
|
|
||
| // `map` for any functor at all, chosen by the compiler from the type | ||
| def plusOne[F[_]: Functor](fa: F[Int]): F[Int] = | ||
| fa.map(_ + 1) | ||
|
|
||
| println(plusOne(List(1, 2, 3))) | ||
| println(plusOne(Option(41))) | ||
| println(plusOne(Option.empty[Int])) | ||
|
|
||
| // the type class instance is also available directly | ||
| println(Functor[List].map(List("cats", "effect"))(_.toUpperCase)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| {% | ||
| laika.title = Monoid | ||
| scastie.code = monoid.sc | ||
| %} | ||
|
|
||
| A `Monoid` is a type that knows how to combine two of its values, and what an | ||
| "empty" value looks like: | ||
|
|
||
| ```scala | ||
| trait Monoid[A]: | ||
| def empty: A | ||
| def combine(x: A, y: A): A | ||
| ``` | ||
|
|
||
| Combining must be associative, and `empty` must not change anything it is | ||
| combined with. That is enough structure to fold a whole collection without | ||
| writing a fold: Cats gives you `combineAll` for free. | ||
|
|
||
| Numbers, strings, lists and maps are all monoids — and so is any tuple or map | ||
| whose contents are monoids, which is where it starts to pay off. | ||
|
|
||
| **Try it:** combine a `List[Map[String, Int]]` and watch the values under | ||
| duplicate keys get combined rather than overwritten. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| //> using dep org.typelevel::cats-core:2.13.0 | ||
|
|
||
| import cats.Monoid | ||
| import cats.syntax.all.* | ||
|
|
||
| // `combine` (aliased to |+|) and `empty` come from the Monoid instance | ||
| println(1 |+| 2) | ||
| println("type" |+| "level") | ||
| println(Monoid[List[Int]].empty) | ||
|
|
||
| // folding a collection with its monoid | ||
| println(List(1, 2, 3, 4).combineAll) | ||
| println(List("a", "b", "c").combineAll) | ||
|
|
||
| // tuples and maps are monoids when their contents are | ||
| println(List(("cats", 1), ("cats", 2)).combineAll) | ||
| println(List(Map("a" -> 1), Map("a" -> 2, "b" -> 3)).combineAll) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Interactive tutorials (prototype). | ||
| # | ||
| # How a tutorial works: | ||
| # * every lesson is a markdown file whose prose is rendered into the left | ||
| # pane by /tutorials/tutorial.template.html | ||
| # * next to it lives a `.sc` file with the sample code, which tutorials.js | ||
| # loads into an embedded Scastie editor in the right pane (the scala-cli | ||
| # script extension, since the samples are worksheet-style: top-level | ||
| # statements plus `//> using` directives, runnable with `scala run`) | ||
| # * each track is a subdirectory; its directory.conf points the lessons at | ||
| # the tutorial template and fixes their order | ||
| # | ||
| # Restricting the target formats to HTML keeps these pages out of the search | ||
| # index and the RSS feed, so the prototype stays unlisted (nothing on the site | ||
| # links to /tutorials either) until we decide it is ready to announce. | ||
| laika.targetFormats = [html] | ||
|
|
||
| # Defaults for the embedded Scastie editor. Tracks and individual lessons can | ||
| # override any of these. Dependencies are declared with `//> using` directives | ||
| # in the lesson's code file, which is why the target is Scala CLI. | ||
| # | ||
| # The target names Scastie accepts are "scala-cli", "scala3", "jvm" (Scala 2), | ||
| # "typelevel", "js" and "native"; anything else is quietly ignored and you end | ||
| # up with a plain sbt Scala 3 editor where `//> using` does nothing. Leaving | ||
| # scala-version empty takes Scastie's current default. | ||
| scastie { | ||
| target-type = "scala-cli" | ||
| scala-version = "" | ||
| worksheet = true | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| @:embed(/templates/main.template.html) | ||
| <!-- | ||
| Two-pane tutorial lesson: prose on the left, a live Scastie editor on the | ||
| right. The page shares the site-wide main.template.html unchanged, which is | ||
| also why the stylesheet is linked here in the body rather than in <head>. | ||
|
|
||
| The sample code is not inlined: `scastie.code` names a .sc file sitting next | ||
| to the lesson's markdown, and tutorials.js fetches it. That keeps the code | ||
| free of HTML escaping and lets it be edited (and run) as real Scala. | ||
| --> | ||
| <link rel="stylesheet" href="@:target(/tutorials/tutorials.css)"> | ||
|
|
||
| <div class="tutorial-layout"> | ||
|
|
||
| <section class="tutorial-prose"> | ||
| <div class="tutorial-prose-body bulma-content"> | ||
| <h1 class="bulma-title">${cursor.currentDocument.title}</h1> | ||
| ${cursor.currentDocument.content} | ||
| </div> | ||
|
|
||
| <nav class="tutorial-nav" aria-label="lesson navigation"> | ||
| <div class="tutorial-nav-slot"> | ||
| @:for(cursor.previousDocument) | ||
| <a class="bulma-button bulma-is-light" href="${_.path}" rel="prev"> | ||
| <span aria-hidden="true">← </span>${_.title} | ||
| </a> | ||
| @:@ | ||
| </div> | ||
| <div class="tutorial-nav-slot tutorial-nav-centered"> | ||
| <a class="bulma-button bulma-is-light" href="@:target(/tutorials/README.md)" rel="up">All tutorials</a> | ||
| </div> | ||
| <div class="tutorial-nav-slot tutorial-nav-end"> | ||
| @:for(cursor.nextDocument) | ||
| <a class="bulma-button bulma-is-primary" href="${_.path}" rel="next"> | ||
| ${_.title}<span aria-hidden="true"> →</span> | ||
| </a> | ||
| @:@ | ||
| </div> | ||
| </nav> | ||
| </section> | ||
|
|
||
| <section class="tutorial-editor"> | ||
| <div class="tutorial-editor-bar"> | ||
| <span class="tutorial-editor-label">Try it yourself</span> | ||
| <button type="button" id="open-in-scastie" class="bulma-button bulma-is-small bulma-is-light"> | ||
| Open in Scastie | ||
| </button> | ||
| </div> | ||
| <div class="tutorial-editor-pane"> | ||
| <pre id="tutorial-code" class="tutorial-code" data-code-src="${?scastie.code}" | ||
| data-target-type="${?scastie.target-type}" data-scala-version="${?scastie.scala-version}" | ||
| data-worksheet="${?scastie.worksheet}">Loading the editor…</pre> | ||
| </div> | ||
| <div id="tutorial-toast" class="tutorial-toast" role="status" hidden></div> | ||
| </section> | ||
|
|
||
| </div> | ||
|
|
||
| <script defer src="https://scastie.scala-lang.org/embedded.js"></script> | ||
| <script defer src="@:target(/tutorials/tutorials.js)"></script> | ||
| @:@ |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to recommend them, there is also that new learning resources page, which only has one link under tutorials and courses right now!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, good point -- would be totally appropriate to have there IMO. (Although probably in a separate PR, since it's not really related to this prototype.)