Alpha

What is Effect


Pretty much everything interesting that a computer can do is interacting with the outside world.


Interacting with the outside world is unpredictable, we call programs that interact with the outside world effectful.


Effect is a library that gives predictability to such programs by empowering you with a set of composable building blocks to model the patterns that you'll need on a daily basis.


Recognising Side Effects


When writing programs in TypeScript we are used to code that looks like:

ts
1
const greet = (name: string) => {
2
const greeting = `hello ${name}`
3
console.log(greeting)
4
return greeting
5
}
ts
1
const greet = (name: string) => {
2
const greeting = `hello ${name}`
3
console.log(greeting)
4
return greeting
5
}

When such function is invoked like greet("Michael") the message hello Michael is printed out in the console.


Whenever any state external to the function scope is mutated (in this case the console state) a function is said to contain side effects.


Another way of looking at the same issue is through the lenses of referential transparency, a pure function (a function that doesn't contain side effects) has the property of respecting evaluation substitution, that means if I have something like f(g(x), g(x)) it is safe to refactor to const y = g(x); f(y, y); functions with side effects violate this property, for exameple the following two programs yield different outputs:

ts
1
const program1 = () => {
2
console.log(`length: ${greet('Michael') + greet('Michael')}`)
3
}
4
 
5
const program2 = () => {
6
const x = greet('Michael')
7
console.log(`length: ${x + x}`)
8
}
ts
1
const program1 = () => {
2
console.log(`length: ${greet('Michael') + greet('Michael')}`)
3
}
4
 
5
const program2 = () => {
6
const x = greet('Michael')
7
console.log(`length: ${x + x}`)
8
}

Namely program1 will print twice the message hello Michael and program2 will print only once.


Code that contains side effects is hard to maintain, it can't be safely refactored and it's fundamentally hard to write in a safe and fast manner.


Programming with Effect


Turning side effects into effects allows you to deal with such programs in a fully safe manner, let's start by writing the same program with Effect.

ts
1
import * as E from '@effect/core/io/Effect'
2
import { pipe } from '@tsplus/stdlib/data/Function'
3
 
4
const greet = (name: string) =>
5
pipe(
6
E.succeed(`hello ${name}`),
7
E.tap((greeting) => E.logInfo(greeting)),
8
)
9
 
10
const program1 = pipe(
11
greet('Michael'),
12
E.zipWith(greet('Michael'), (x, y) => x + y),
13
E.tap((message) => E.logInfo(`length: ${message}`)),
14
)
15
 
16
const greetMichael = greet('Michael')
17
 
18
const program2 = pipe(
19
greetMichael,
20
E.zipWith(greetMichael, (x, y) => x + y),
21
E.flatMap((message) => E.logInfo(`length: ${message}`)),
22
)
ts
1
import * as E from '@effect/core/io/Effect'
2
import { pipe } from '@tsplus/stdlib/data/Function'
3
 
4
const greet = (name: string) =>
5
pipe(
6
E.succeed(`hello ${name}`),
7
E.tap((greeting) => E.logInfo(greeting)),
8
)
9
 
10
const program1 = pipe(
11
greet('Michael'),
12
E.zipWith(greet('Michael'), (x, y) => x + y),
13
E.tap((message) => E.logInfo(`length: ${message}`)),
14
)
15
 
16
const greetMichael = greet('Michael')
17
 
18
const program2 = pipe(
19
greetMichael,
20
E.zipWith(greetMichael, (x, y) => x + y),
21
E.flatMap((message) => E.logInfo(`length: ${message}`)),
22
)

When executed the result of program1 and the one of program2 will be exactly the same, namely twice the message hello Michael will be printed out in the console followed by a message containing the length of the concatenated strings.


Executing Effects


Effect can execute programs in various ways, the simplest one is the execution to a Promise<Value> that may be used for interop purposes.

ts
1
import * as E from '@effect/core/io/Effect'
2
import { pipe } from '@tsplus/stdlib/data/Function'
3
 
4
const program = pipe(E.logInfo('Hello'), E.zipRight(E.logInfo('World')))
5
 
6
E.unsafeRunPromise(program)
7
.then(() => {
8
console.log('Execution Completed')
9
})
10
.catch(() => {
11
console.log('Execution Failed')
12
})
ts
1
import * as E from '@effect/core/io/Effect'
2
import { pipe } from '@tsplus/stdlib/data/Function'
3
 
4
const program = pipe(E.logInfo('Hello'), E.zipRight(E.logInfo('World')))
5
 
6
E.unsafeRunPromise(program)
7
.then(() => {
8
console.log('Execution Completed')
9
})
10
.catch(() => {
11
console.log('Execution Failed')
12
})

The Effect Type


The Effect type contains 3 type parameters, the full type looks like Effect<Services, Errors, Success> and they respectively indicate:


  • Services: the list of requirements that your program needs
  • Errors: the list of errors that your program may encounter
  • Success: the value that your program returns

As we will see in the next chapters those type parameters are leveraged by Effect to provide the best possible developer experience when dealing with dependency injection and error handling for your everyday usage.