Skip to content
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

Add interactive tutorials to the website #716

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 72 additions & 21 deletions content/tutorials/basics/100-your-first-effect.mdx
Original file line number Diff line number Diff line change
@@ -1,43 +1,94 @@
---
title: Your first Effect
excerpt: Learn the basics of Effect
section: Learn the basics
title: Your First Effect
---

### What is an Effect?

The `Effect` type represents an immutable value that lazily describes a workflow
or job.

In some ways you can think of them like lazy Promises - but Effects are not
actually Promises, of course. They can model synchronous, asynchronous,
The `Effect` type represents an **immutable value** that **lazily describes** a
computation, workflow, or job. In some ways you can think of an `Effect` like a
lazy `Promise`. But `Effect`s are not actually `Promise`s, of course - they are
much more powerful and can be used to model synchronous, asynchronous,
concurrent, and resourceful computations.

To get started, let's implement a "Hello world!" program! In the editor you can
see an example of the `Effect.log` function. This effect will log a message to
the console.
The `Effect` type has three generic type parameters which allow
us to fully describe all aspects of a program. Let's take a closer look:

<div className="flex justify-center">
<code>Effect&lt;Success&comma; Error&comma; Requirements&gt;</code>
</div>

The `Success` type represents the type of value that an effect can succeed with
when executed. If this type parameter is `void`, it means the effect produces no
useful information, while if it is `never`, it means the effect will run forever
(or until it fails).

The `Error` type represents **expected** errors that can occur when executing an
effect. If this type parameter is `never`, it means the effect cannot fail
because there are no values of type `never`.

The `Requirements` type represents the contextual data required by the effect in
order to be executed. This data is stored internally within a specialized
collection called `Context`
(<a href="/docs/other/glossary#context" target="_blank">docs</a>). If this type
parameter is `never`, it means the effect does not have any requirements and
that the underlying `Context` collection is empty.

<Info>
In the Effect ecosystem, you may often encounter the type parameters of the
`Effect` type abbreviated as `A`, `E`, and `R` respectively. This is just
shorthand for the success value of type `A`, `E`rror, and `R`equirements.
Additionally, you may also hear these type parameters referred to as _channels_
of an `Effect`.
</Info>

### Describing Computations

But how do we run it?
We previously mentioned that an `Effect` is actually an immutable and lazy
_description_ of a computation. This means that when you create an `Effect`,
nothing actually happens. You are only _describing_ what you want your program
to do. The fact that an `Effect` is an inherently lazy, immutable description of
a program is a key component to what powers many of the features of the library.

### Running Effect's
Similar to calling a function, to actually execute a program described by an
`Effect`, you must explicitly run the `Effect` using Effect's runtime system.

To get a better understanding of what we mean, let's implement a simple program
that outputs `"Hello, World!"` to the console.

### Running an Effect

In the editor to the right, you can see an example of the `Effect.log` function.
This effect will log the provided message (in this case `Hello, World!`) to the
console, along with some other relevant metadata.

But how do we actually run it?

If you type `Effect.run` into the editor, you will see several different options
to choose from.
to choose from. For this example let's try running our `Effect.log` program with
the `Effect.runPromise` function.

Similar to calling a function, to execute an Effect we need to explicitly run it.
For this example let's try running the `Effect.log` effect with the
`Effect.runPromise` function.
<Idea>
To learn more about the different ways you can execute an `Effect`, checkout
the documentation on <a href="/docs/guides/essentials/running-effects" target="_blank">Running Effects</a>.
</Idea>

To use `Effect.runPromise`:

```ts
Effect.runPromise(/* your effect goes here */)
```

Remember, if you ever get stuck try clicking the "Solve" button.
Additionally, take a look at the type of our `Effect.log` program by hovering
over the `program` variable in the editor. We can see that:

- The program has `void` in the `Success` channel, which means that no value
will be produced by the program
- The program has `never` in the `Error` channel, which means that no expected
errors will be produced by the program
- The program has `never` in the `Requirements` channel, which means that the
program has no other requirements in order to be run

<Idea>
To learn more about running effects, we have a page in the documentation
with more information:<br />
<a href="/docs/guides/essentials/running-effects" target="_blank">Running Effects</a>
Remember, if you ever get stuck try clicking the "Show Solution" button in the
upper right-hand corner of the editor.
</Idea>
169 changes: 169 additions & 0 deletions content/tutorials/basics/200-creating-effects.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Creating Effects
---

### Returning Values

Congratulations on running your first Effect!

However, in the previous section our program had the type `void` in the
`Success` channel of the effect, indicating that the program did not return
any meaningful value.

In this section, we are going to explore how we can create effects that _do_
return meaningful values.

### Modeling Synchronous Computations

#### Effect.succeed

To create an effect that returns the same value every time that it is run, you
can use the `Effect.succeed` constructor. It takes a **pure value** as input and
produces an effect that succeeds with that value.

Here's an example:

```ts
Effect.succeed(42)
```

When run, this effect will always succeed with the value `42`.

#### Effect.sync

If you want to create an effect that executes a synchronous function each time
it is run, you can use the `Effect.sync` constructor. It takes a thunk (i.e. a
function with no arguments) as input, and calls that function each time the
effect is run. This is particularly useful when you need to encapsulate
operations within effect that have side-effects (e.g. logging to the console).

Here's an example:

```ts
Effect.sync(() => Date.now())
```

This effect will return the current date each time. Note that if we used
`Effect.succeed(Date.now())` instead, the date would be fixed to the time when
the effect was created instead of being re-evaluated each time the effect is
run.

<Warning>
The thunk passed to `Effect.sync` should never throw errors.
</Warning>

If, despite warnings, the thunk passed to `Effect.sync` does throw an error, the
Effect runtime will create an `Effect` containing a "defect" (see
<a href="/docs/guides/error-management/unexpected-errors" target="_blank">Unexpected Errors</a>
in the documentation).

#### Effect.try

For situations where you need to perform a synchronous computation that may
fail, you can use the `Effect.try` constructor. This constructor has two
variants that can be used depending upon whether or not you would like to
customize error handling.

For example, let's say we have a function `numberOrFail` that can potentially
throw an `Error`:

```ts
function numberOrFail(num: number) {
if (Math.random() > 0.5) {
throw new Error("failed")
}
return num
}
```

If we do not need to customize error handling at all, we can directly pass our
`numberOrFail` function to `Effect.try`. In this case, any errors thrown by the
function passed to `Effect.try` will be wrapped by an `UnknownException`:

```ts
Effect.try(() => numberOrFail(42))
```

However, if we _do_ need to customize error handling, we can use the other
variant of `Effect.try` which allows us to catch and handle any errors thrown
from our synchronous function:

```ts
Effect.try({
// Try to run our synchronous function
try: () => numberOrFail(42),
// Catch and re-map the error to something possibly more useful
catch: (error) => new Error(`An error occurred: ${error}`)
})
```

This pattern is analagous to a standard try-catch block in JavaScript.

### Modeling Asynchronous Computations

#### Effect.promise

If you want to create an effect from an asynchronous function that returns a
`Promise`, you can use the `Effect.promise` constructor. The resulting effect
will resolve the returned `Promise` and succeed with it's value.

Here's an example:

```ts
Effect.promise(
() => fetch("https://api.github.com/users/octocat")
)
```

This effect will fetch the GitHub user `octocat` and succeed with the response.

<Warning>
The `Promise` returned by the thunk passed to `Effect.promise` should never
reject.
</Warning>

If, despite warnings, the thunk passed to `Effect.promise` does reject, the
Effect runtime will create an `Effect` containing a "defect" (see
<a href="/docs/guides/error-management/unexpected-errors" target="_blank">Unexpected Errors</a>
in the documentation).

#### Effect.tryPromise

For situations where you need to perform an asynchronous computation that may
fail, you can use the `Effect.tryPromise` constructor. Similar to `Effect.try`,
the `Effect.tryPromise` constructor has two variants that can be used depending
upon whether or not you would like to customize error handling.

If you do not need to customize error handling, the syntax is similar to
`Effect.promise`, the difference being that if the promise rejects, the error
will be wrapped into an `UnknownException`:

```ts
Effect.tryPromise(
() => fetch("https://api.github.com/users/octocat")
)
```

However, if you _do_ need to customize error handling, you can use the other
variant of `Effect.tryPromise` which allows for catching and handling the error
returned by the rejected `Promise`:

```ts
Effect.tryPromise({
// Try to run the promise
try: () => fetch("https://api.github.com/users/octocat"),
// Catch and re-map the error to something possibly more useful
catch: (error) => new Error(`An error occurred: ${error}`)
})
```

This is similar to adding a `.catch` handler to a `Promise`.

### Exercise

Using the Effect functions we just learned, complete the TODO's in the editor.

<Idea>
Remember, if you ever get stuck try clicking the "Show Solution" button in the
upper right-hand corner of the editor.
</Idea>
51 changes: 0 additions & 51 deletions content/tutorials/basics/200-returning-values.mdx

This file was deleted.

39 changes: 0 additions & 39 deletions content/tutorials/basics/300-combining-effects.mdx

This file was deleted.

Loading