Open gcanti opened 1 day ago
Would using Effect.gen
and Random
be a better example?
I'm not sure what the goal is here, but at best, this is just as cumbersome as using the native code.
Proposal:
const main = () => {
const value = Math.random()
console.log(`Got value ${value}`)
}
main()
import { Effect, Random } from "effect"
const main = Effect.gen(function* (_) {
const value = yield* Random.next
console.log(`Got value ${value}`)
})
Effect.runSync(main)
Why not use Effect.gen
?
I'm not sure what the goal is here, but at best, this is just as cumbersome as using the native code.
Proposal:
import { Effect } from "effect"
const main = Effect.gen(function* (_) {
yield* Effect.sleep("1 second")
console.log("Hello, World!")
})
Effect.runPromise(main)
Why not use Effect.gen
and Random
?
Proposal:
class CustomError extends Error {
- constructor(readonly value: number) {}
+ constructor(readonly value: number) {
+ super()
+ }
}
+// The return type `number` doesn't account
+// for the possibility of throwing an error
-// Return type of `number` doesn't reflect the
-// fact that the function can throw
const maybeFail = (): number => {
const value = Math.random()
if (value > 0.5) {
throw new CustomError(value)
}
return value
}
const main = () => {
try {
const value = maybeFail()
console.log(`Got value ${value}`)
} catch (error) {
if (error instanceof CustomError) {
console.error(`Oops! Got value ${error.value}`)
} else {
console.error("No idea what happened!")
}
}
}
main()
import { Effect, Random, Console } from "effect"
class CustomError extends Error {
constructor(readonly value: number) {
super()
}
}
const maybeFail: Effect.Effect<
number,
CustomError // Ensures type safety
> = Effect.gen(function* (_) {
const value = yield* Random.next
if (value > 0.5) {
throw new CustomError(value)
}
return value
})
const main = Effect.gen(function* (_) {
const value = yield* maybeFail
console.log(`Got value ${value}`)
}).pipe(
Effect.catchAll((error) =>
Console.error(`Oops! Got value ${error.value}`)
)
)
Effect.runPromise(main)
Why not use Effect.gen
?
Proposal:
import { Effect } from "effect"
const main = Effect.gen(function* (_) {
yield* Effect.sleep("1 second")
console.log("Hello")
}).pipe(
Effect.timeoutFail({
duration: "500 millis",
onTimeout: () => "Aborted!"
})
)
Effect.runPromise(main)
p-retry
?Effect.gen
?Tag
is unnecessary.Proposal:
import { FetchHttpClient, HttpClient } from "@effect/platform"
import { Effect } from "effect"
const getUser = (id: number, retries = 3) =>
HttpClient.get(`/users/${id}`).pipe(
Effect.andThen((response) => response.json),
Effect.scoped,
Effect.retry({ times: retries }),
Effect.provide(FetchHttpClient.layer)
)
const main = Effect.gen(function* () {
const user = yield* getUser(1)
console.log("Got user", user)
})
Effect.runPromise(main)
p-map
?Proposal:
const getUser = (id: number) =>
Promise.resolve({ id, name: `user ${id}` })
async function forEach<A, B>(
items: Array<A>,
concurrency: number,
f: (a: A) => Promise<B>
): Promise<Array<B>> {
let index = 0
const results: Array<B> = new Array(items.length)
async function process(index: number) {
const next = items[index]!
results[index] = await f(next)
}
async function worker() {
while (index < items.length) {
await process(index++)
}
}
await Promise.all(Array.from({ length: concurrency }, worker))
return results
}
const ids = Array.from({ length: 10 }, (_, i) => i)
async function main() {
const users = await forEach(ids, 3, (id) => getUser(id))
console.log("Got users", users)
}
main()
or (using p-map
)
import pMap from "p-map"
const getUser = (id: number) =>
Promise.resolve({ id, name: `user ${id}` })
const ids = [1, 2, 3, 4, 5]
async function main() {
const users = await pMap(ids, getUser, { concurrency: 3 })
console.log("Got users", users)
}
main()
import { Effect } from "effect"
const getUser = (id: number) => Effect.succeed({ id, name: `user ${id}` })
const ids = [1, 2, 3, 4, 5]
const main = Effect.gen(function* () {
const users = yield* Effect.forEach(ids, getUser, { concurrency: 3 })
console.log("Got users", users)
})
Effect.runPromise(main)
The example is too long and difficult to follow, remove it?
Just a small improvement.
Proposal:
import { Schema } from "@effect/schema"
const User = Schema.Struct({
username: Schema.String
})
Schema.decodeUnknownSync(User)({
username: "john_doe"
})
// extract the inferred type
-type User = Schema.Schema.Type<typeof User>
+type User = typeof User.Type
It's redundant and has fewer downloads than Zod, remove it? https://npmtrends.com/superjson-vs-yup-vs-zod
It's redundant and has fewer downloads than Zod, remove it? https://npmtrends.com/superjson-vs-yup-vs-zod
Just a small improvement.
Proposal:
import { Effect, Schedule, Stream } from "effect"
-const counts = Stream.fromSchedule(Schedule.spaced(1000)).pipe(
+const counts = Stream.fromSchedule(Schedule.spaced("1 second")).pipe(
Stream.take(5),
Stream.map((x) => x * 2),
Stream.runCollect
)
Effect.runPromise(counts).then((x) => console.log(x))
This example is too long and complicated, remove it?
Switch to neverthrow
, which has more downloads: https://npmtrends.com/@mobily/ts-belt-vs-neverthrow-vs-ts-results
Proposal:
import { existsSync, readFileSync } from "fs"
import { Ok, Err, Result } from "neverthrow"
function readFile(path: string): Result<string, "invalid path"> {
if (existsSync(path)) {
return new Ok(readFileSync(path, "utf8"))
} else {
return new Err("invalid path")
}
}
It's more complex than native alternatives, remove it?
It's more complex than native alternatives, remove it?
Here are the snippets under "Let's see some example code." I will add further considerations in the comments that follow.
Original Code
# Basics ## Sync code ```ts const main = () => { console.log("Hello, World!") } main() ``` Effect ```ts import { Console, Effect } from "effect" const main = Console.log("Hello, World!") Effect.runSync(main) ``` ## Async code ```ts const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) const main = async () => { await sleep(1000) console.log("Hello, World!") } main() ``` Effect ```ts import { Console, Effect } from "effect" const main = Effect.sleep(1000).pipe( Effect.andThen(Console.log("Hello, World!")) ) Effect.runPromise(main) ``` ## Error Handling ```ts class CustomError extends Error { constructor(readonly value: number) {} } // Return type of `number` doesn't reflect the // fact that the function can throw const maybeFail = (): number => { const value = Math.random() if (value > 0.5) { throw new CustomError(value) } return value } const main = () => { try { const value = maybeFail() console.log(`Got value ${value}`) } catch (error) { if (error instanceof CustomError) { console.error(`Oops! Got value ${error.value}`) } else { console.error("No idea what happened!") } } } main() ``` Effect ```ts import { Console, Effect } from "effect" class CustomError { readonly _tag = "CustomError" constructor(readonly value: number) {} } const maybeFail: Effect.Effect< number, CustomError // type safety > = Effect.sync(() => Math.random()).pipe( Effect.andThen((value) => value > 0.5 ? Effect.fail(new CustomError(value)) : Effect.succeed(value) ) ) const main = maybeFail.pipe( Effect.andThen((value) => Console.log(`Got value ${value}`)), Effect.catchTag("CustomError", (error) => Console.error(`Oops! Got value ${error.value}`) ) ) Effect.runPromise(main) ``` ## Interruption ```ts const sleep = (ms: number, signal: AbortSignal): Promise => new Promise((resolve, reject) => { const timeout = setTimeout(resolve, ms) signal.addEventListener("abort", () => { clearTimeout(timeout) reject("Aborted!") }) }) async function main() { await sleep(1000, AbortSignal.timeout(500)) console.log("Hello") } main() ``` Effect ```ts import { Console, Effect } from "effect" const main = Effect.sleep(1000).pipe( Effect.andThen(Console.log("Hello")), Effect.timeoutFail({ duration: 500, onTimeout: () => "Aborted!" }) ) Effect.runPromise(main) ``` ## Retry ```ts async function getUser(id: number, retries = 3) { try { const response = await fetch(`/users/${id}`) if (!response.ok) throw new Error() return await response.json() } catch (error) { if (retries === 0) { throw error } return getUser(id, retries - 1) } } async function main() { const user = await getUser(1) console.log("Got user", user) } main() ``` Effect ```ts import { FetchHttpClient, HttpClient } from "@effect/platform" import { Console, Effect, Layer } from "effect" const makeUsers = Effect.gen(function* () { const client = (yield* HttpClient.HttpClient).pipe( HttpClient.filterStatusOk ) const findById = (id: number) => client.get(`/users/${id}`).pipe( Effect.andThen((response) => response.json), Effect.scoped, Effect.retry({ times: 3 }) ) return { findById } as const }) class Users extends Effect.Tag("Users")< Users, Effect.Effect.Success