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 blog to website #132

Closed
wants to merge 4 commits into from
Closed
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
58 changes: 58 additions & 0 deletions components/BlogIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { getPagesUnderRoute } from "nextra/context"
import * as React from "react"
import Link from "next/link"

export declare namespace BlogIndex {
export interface Props {
readonly more?: string
readonly pagesUnder: string
}
}

export const BlogIndex: React.FC<BlogIndex.Props> = ({
more = "Read more",
pagesUnder,
}) => (
<React.Fragment>
{getPagesUnderRoute(pagesUnder)
.filter((page) => page.kind === "MdxPage")
.map((page) => {
const meta = "meta" in page ? page.meta : undefined
const frontMatter = "frontMatter" in page ? page.frontMatter : undefined
return (
<div key={page.route} className="mb-10">
<h3>
<Link
href={page.route}
style={{ color: "inherit", textDecoration: "none" }}
className="block font-semibold mt-8 text-2xl "
>
{frontMatter?.series && (
<span className="align-middle text-xs nx-bg-primary-100 nx-font-semibold nx-text-primary-800 dark:nx-bg-primary-100/10 dark:nx-text-primary-600 inline-block py-1 px-2 uppercase rounded mb-1 mr-2">
Series{" "}
</span>
)}
<span>{meta?.title || frontMatter?.title || page.name}</span>
</Link>
</h3>
<p className="opacity-80 mt-6 leading-7">
{frontMatter?.description}{" "}
<span className="inline-block">
<Link
href={page.route}
className="text-[color:hsl(var(--nextra-primary-hue),100%,50%)] underline underline-offset-2 decoration-from-font"
>
{more + " →"}
</Link>
</span>
</p>
{frontMatter?.date ? (
<p className="opacity-50 text-sm mt-6 leading-7">
{frontMatter.date}
</p>
) : null}
</div>
)
})}
</React.Fragment>
)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"clean": "rm -rf .next node_modules/.cache"
},
"dependencies": {
"@effect/schema": "^0.25.0",
"@stackblitz/sdk": "^1.9.0",
"next": "13.3.1",
"nextra": "^2.7.1",
Expand Down
4 changes: 4 additions & 0 deletions pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
"theme": {
"layout": "full"
}
},
"blog": {
"title": "Blog",
"type": "page"
}
}
12 changes: 12 additions & 0 deletions pages/blog.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Blog
searchable: false
---

<h1 className="text-4xl tracking-tighter text-center font-extrabold md:text-5xl mt-8 pb-6">
Effect Blog
</h1>

import { BlogIndex } from "@/components/BlogIndex"

<BlogIndex pagesUnder="/blog" />
10 changes: 10 additions & 0 deletions pages/blog/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"*": {
"theme": {
"pagination": false,
"sidebar": false
}
},
"generators-are-amazing": "Generators Are Amazing",
"type-safe-errors": "Type-Safe Errors"
}
159 changes: 159 additions & 0 deletions pages/blog/generators-are-amazing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
title: Generators Are Amazing
description: 'Exploring the usage of generators and their advantage over plain async-await'
searchable: false
series: false
---

In this article we'll first briefly discuss programming styles and what problems async/await resolves we'll then take it further and see how the same problems can be solved for custom types different from `Promise<A>` and why it is important.

## Imperative vs Declarative

When learning to program the very first style of programming we encounter is usually the imperative style, we are thought to think of programs as series of instructions (statements) that are executed by our language one by one and one after another.

A simple example of this style can be:

```ts
const program = () => {
const a = Math.random()
const b = Math.random()
const c = a + b
console.log(`${a} + ${b} = ${c}`)
}
program()
```

In this program we are:
1) defining two constants `a` and `b`
2) computing their sum and calling the result of such computation `c`
3) we are printing a message to show our computation as `a + b = c`

We can see here how we are progressively giving instructions to our machine and how we are expecting every step to have access to all the context from the previous.

This programming model is very powerful and it maps precisely to how we are used to think about algorithms and procedures in real life.

Life's good until we face the reality, when we introduce concurrency in our code, namely asyncronious concurrency.

Let's assume generating random numbers and computing their sum being expensive operations and being asyncronious, let's see what options we are presented with.

Using `Promise<A>` we can write our program like:

```ts
const random = () =>
new Promise<number>((onValue) => {
// pretend we schedule something
setTimeout(() => {
// call onValue when we are done
onValue(Math.random())
}, 1000)
})

const computeSum = (a: number, b: number) =>
new Promise<number>((onValue) => {
setTimeout(() => {
onValue(a + b)
}, 1000)
})

const program = () =>
random().then((a) =>
random().then((b) =>
computeSum(a, b).then((c) => console.log(`${a} + ${b} = ${c}`))
)
)

program()
```

We can easily see how this style leads to contrived and hard to read code, all of our operations are either nested or they lose access to any previous steps. This style of programming is commonly known as "functional" or "declarative", our program isn't any longer a sequence of instructions, it's rather more similar to a chain of function calls.

To solve this issue in `JS` we are gifted with `async / await` when we use `Promise<A>` we can use custom syntax to represent our program in a way that is familiar to us and very similar to our initial version, using this syntax our program becomes:

```ts
const random = () =>
new Promise<number>((onValue) => {
// pretend we schedule something
setTimeout(() => {
// call onValue when we are done
onValue(Math.random())
}, 1000)
})

const computeSum = (a: number, b: number) =>
new Promise<number>((onValue) => {
setTimeout(() => {
onValue(a + b)
}, 1000)
})

const program = async () => {
const a = await random()
const b = await random()
const c = await computeSum(a, b)
console.log(`${a} + ${b} = ${c}`)
}
```

## Limits of Promises & Async-Await

- Sync & Async code looks different, writing utilities that work with both sync and async functions becomes impossible
- Changing a single nested operation from sync to async bubbles up to the rest of our program
- We don't have any idea about what errors our code can raise, both in sync & async code
- We cannot represent any higher order behaviour such as `repeat(5)(random())` because the promise content is invoked as soon as the promise is constructed, we need to resort to use `() => Promise<A>` for example saying `repeat(5)(random)()`

## Custom Result Types

To resolve some of the issues folks have been investigating usage of custom types, for example if we ignore the problem of async code and only care about sync execution we could leverage a result type like `Either<Err, Ok>` and our code would become:

```ts
import * as Either from "@effect/data/Either"

const random = (): Either.Either<"RandomGenerationError", number> => {
const n = Math.random()
if (n > 0.9) {
return Either.left("RandomGenerationError")
} else {
return Either.right(n)
}
}

const computeSum = (a: number, b: number): Either.Either<"ComputeSumError", number> => {
const n = a + b
if (n > 0.9) {
return Either.left("ComputeSumError")
} else {
return Either.right(n)
}
}

const program = (): Either.Either<"ComputeSumError" | "RandomGenerationError", void> => {
const a = random()
if (Either.isLeft(a)) {
return Either.left(a.left)
}
const b = random()
if (Either.isLeft(b)) {
return Either.left(b.left)
}
const c = random()
if (Either.isLeft(c)) {
return Either.left(c.left)
}
console.log(`${a.right} + ${b.right} = ${c.right}`)
return Either.right(void 0)
}

const result = program()

if (Effect.isLeft(result)) {
console.error(result.left)
}
```

While the code is now very precise on stating what may occur in the non-happy path we are back to code that looks different from what we are used to write, now at every step of our program we need to locally deal with the case the operation fails and in case of failures manually do early returns. This pattern is similar to writing Go code, where every statement returns an optional error, while it is easy to reason about mentally it leads to extreme verbosity of code, and even worse inference isn't really helping too much, if we are not annotating types manually we find ourselves with large union types that are impossible to read and deal with.

Going back to code that looks normal can be done using generators, the same code as above can be rewrote as:

// add back

## Using Generators with Effect
10 changes: 10 additions & 0 deletions pages/blog/type-safe-errors.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: Type-Safe Errors
description: 'Exploring a set of alternative options to represent errors in a type-safe way'
searchable: false
series: true
---

import { BlogIndex } from "@/components/BlogIndex"

<BlogIndex pagesUnder="/blog/type-safe-errors" />
8 changes: 8 additions & 0 deletions pages/blog/type-safe-errors/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"*": {
"theme": {
"pagination": true
}
},
"naive-approach": "A naive approach"
}
Loading