Comments (9)
I don't understand why you haven't actually defined the pickLiterals
function in your examples, it would make things clearer in my opinion
import type * as AST from "@effect/schema/AST"
import * as S from "@effect/schema/Schema"
const pickLiterals =
<A extends AST.LiteralValue, L extends ReadonlyArray<A>>(...literals: L) =>
<I, R>(_schema: S.Schema<A, I, R>): S.Schema<L[number]> => S.literal(...literals)
const FruitCategory = S.literal("sweet", "citrus", "tropical")
type FruitsCategory = S.Schema.To<typeof FruitCategory>
const FruitStatus = S.literal("fresh", "notFresh")
type FruitsStatus = S.Schema.To<typeof FruitStatus>
const fruit = <Self>() =>
<Category extends FruitsCategory, Status extends FruitsStatus>(
category: S.Schema<Category>,
status: S.Schema<Status>
) => S.Class<Self>()({ fruitId: S.UUID, category, status })
export class Fruit extends fruit<Fruit>()(FruitCategory, FruitStatus) {}
export class FreshAndSweetAndCitrusFruit extends fruit<FreshAndSweetAndCitrusFruit>()(
FruitCategory.pipe(pickLiterals("sweet", "citrus")),
FruitStatus.pipe(pickLiterals("fresh"))
) {}
Note that we are not using _schema
, so pickLiterals
is kind of a constrained version of the S.literal
constructor
// const pickLiterals =
// <A extends AST.LiteralValue, L extends ReadonlyArray<A>>(...literals: L) =>
// <I, R>(_schema: S.Schema<A, I, R>): S.Schema<L[number]> => S.literal(...literals)
const pickLiterals = <A extends AST.LiteralValue>() => <L extends ReadonlyArray<A>>(...literals: L) =>
S.literal(...literals)
const pickCategory = pickLiterals<FruitsCategory>()
const pickStatus = pickLiterals<FruitsStatus>()
export class FreshAndSweetAndCitrusFruit2 extends fruit<FreshAndSweetAndCitrusFruit2>()(
pickCategory("sweet", "citrus"),
pickStatus("fresh")
) {}
from effect.
Not sure I understand the use case, this
Schema.literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b"))
just seems a more verbose way to write
Schema.literal("a", "b")
Let's talk about the type-level and let's say we have
type ABC = "a" | "b" | "c"
type EntityA = {
abc: ABC
}
type EntityB = {
ab: "a" | "b"
}
what does it mean "to keep both entities in a relationship"?
from effect.
Also if you want to reuse schemas you can simply extract them they are just plain values
from effect.
@gcanti let's consider another example and let's talk about the type-level:
Let's say I have a model that contain the source of truth that I want to expose to another context:
//A Fruit in General
type FruitsCategory = "sweet" | "citrus" | "tropical"; // source of truth
type FruitsStatus = "fresh" | "notFresh"; // source of truth
type Fruit = {
category: FruitsCategory;
status: FruitsStatus;
};
Now, in another context I want to use FruitsCategory
and FruitsStatus
, for simplicity let's consider that I want to use them to create some sub types.
//A Fruit that is Fresh
type FreshFruit = {
category: FruitsCategory;
status: FruitsStatus; //I would like to narrow down to "fresh",
//so if someone remove "fresh" from FruitsCategory the error will be reflected here
};
//A Fruit that is Fresh and Sweet
type FreshAndSweetFruit = {
category: FruitsCategory; //I would like to narrow down to "sweet",
//so if someone remove "sweet" from FruitsCategory the error will be reflected here
status: FruitsStatus; //I would like to narrow down to "fresh",
//so if someone remove "fresh" from FruitsStatus the error will be reflected here
};
As I used above: FruitsCategory
and FruitsStatus
(the source of truth), and let's say I was able to picked only the literal I was interested in, I can consider they are bound together (with the the source of truth). That's what I meant, when I said "relationship" previously.
If I took now the following example, there is no relationship/dependency.
//A Fruit that is Fresh and Sweet but not a SubType of Fruit:
type FreshAndSweetFruitButNotASubType = {
category: "sweet";
status: "fresh";
};
There is no relationship between them removing either "sweet" from FruitsCategory
or "fresh" from FruitsStatus
will not affect it. So, if I want them to be bound and I'm not doing it programmatically, like this, I'll need to keep my intention in mind, which will undoubtedly lead to an issue
Does this example makes more sense?
from effect.
I would do something like
import * as S from "@effect/schema/Schema"
export type FruitsCategory = "sweet" | "citrus" | "tropical" // source of truth
export type FruitsStatus = "fresh" | "notFresh" // source of truth
export type Fruit<Category extends FruitsCategory, Status extends FruitsStatus> = {
category: Category
status: Status
}
export type FreshFruit = Fruit<FruitsCategory, "fresh">
export type FreshAndSweetFruit = Fruit<"sweet", "fresh">
export const fruit = <Category extends FruitsCategory, Status extends FruitsStatus>(
category: S.Schema<Category>,
status: S.Schema<Status>
) => S.struct({ category, status })
export const freshFruit = fruit(S.literal("sweet", "citrus", "tropical"), S.literal("fresh"))
export const freshAndSweetFruit = fruit(S.literal("sweet"), S.literal("fresh"))
from effect.
Based on this simplified example of FreshFruit
that I provided, your solution work. Still there is one downside, there is no auto-completion when you pass literals in freshFruit
.
Now let's consider a more concrete example. Here I'm using Schema.Class
and both entities will be persisted in their respective table.
const FruitId = Schema.UUID;
const FruitCategory = Schema.struct({
sweet: Schema.literal("sweet"),
citrus: Schema.literal("citrus"),
tropical: Schema.literal("tropical"),
});
const FruitStatus = Schema.struct({
fresh: Schema.literal("fresh"),
notFresh: Schema.literal("notFresh"),
});
class Fruit extends Schema.Class<Fruit>()({
id: FruitId,
category: FruitCategory,
status: FruitStatus,
}) {}
class FreshAndSweetAndCitrusFruit extends Schema.Class<FreshAndSweetAndCitrusFruit>()(
{
fruitId: FruitId,
category: Schema.union(
FruitCategory.pipe(Schema.pluck("sweet", { transformation: false })),
FruitCategory.pipe(Schema.pluck("citrus", { transformation: false }))
),
status: FruitStatus.pipe(Schema.pluck("fresh", { transformation: false })),
}
) {}
My alternative is quite straightforward. However, it could be even simpler if a pickLiteral
function were exposed. It would look as follows (very clean and very short):
const FruitId = Schema.UUID;
const FruitCategory = Schema.literal("sweet", "citrus", "tropical");
const FruitStatus = Schema.literal("fresh", "notFresh");
class Fruit extends Schema.Class<Fruit>()({
id: FruitId,
category: FruitCategory,
status: FruitStatus,
}) {}
class FreshAndSweetAndCitrusFruit extends Schema.Class<FreshAndSweetAndCitrusFruit>()(
{
fruitId: FruitId,
category: FruitCategory.pipe(Schema.pickLiteral("sweet", "citrus")),
status: FruitStatus.pipe(Schema.pickLiteral("fresh")),
}
) {}
I would end the conversation here, if you don't think it's relevant.
from effect.
Yes it would be straightforward and Thanks for it! I'm trying to use functions exposed by Schema at first. Don't you think it would be relevant to expose this utility?
const pickLiterals =
<A extends AST.LiteralValue, L extends ReadonlyArray<A>>(...literals: L) =>
<I, R>(_schema: S.Schema<A, I, R>): S.Schema<L[number]> => S.literal(...literals)
from effect.
Ok, would you mind submitting a pull request with some documentation covering the use case?
from effect.
Sure, I will try to push something this weekend!
from effect.
Related Issues (20)
- Schema.pick using Schema.Class stop working when upgrading Schema to 0.64 HOT 4
- Effect.if evaluates onTrue when condition is false HOT 3
- Add a Schema.config for decoding Configs using a Schema. HOT 1
- From Discord: Improving Performance of Text Reflow with `@effect/pretty` to 80 Characters Width HOT 1
- Wrong `description` in various package.json files
- Format JSDoc HOT 2
- From Discord: Improving `Config.literal` Construction for Specific Literal Types in TypeScript
- Comparison with zod HOT 1
- Schema: consolidate schema names HOT 6
- Logging of objects, akin to default console.log HOT 1
- From Discord: Proposing Namespaces for Schema Combinators to Enhance Clarity and Discoverability HOT 1
- From Discord: Identifying a Potential Bug with Missing Refinement Annotation in Optional String Structure
- From Discord: JSDoc and click-navigation lost when fields defined in Schema Struct or Class HOT 11
- Expose an empty NodeSDK layer
- From Discord: Consolidating `transform` and `transformOrFail` Parameters into an Options Object
- Stream.paginateChunkEffect should have max_iteration_count HOT 4
- From Discord: Accessing Enum Values in TypeScript for Reuse
- Add support for W3C Trace Context propagation
- Auto-instrument HTTP client
- Make Stream.range upper bound optional
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from effect.