Code Monkey home page Code Monkey logo

Comments (9)

gcanti avatar gcanti commented on July 19, 2024 1

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.

gcanti avatar gcanti commented on July 19, 2024

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.

mikearnaldi avatar mikearnaldi commented on July 19, 2024

Also if you want to reuse schemas you can simply extract them they are just plain values

from effect.

steffanek avatar steffanek commented on July 19, 2024

@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.

gcanti avatar gcanti commented on July 19, 2024

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.

steffanek avatar steffanek commented on July 19, 2024

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.

steffanek avatar steffanek commented on July 19, 2024

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.

gcanti avatar gcanti commented on July 19, 2024

Ok, would you mind submitting a pull request with some documentation covering the use case?

from effect.

steffanek avatar steffanek commented on July 19, 2024

Sure, I will try to push something this weekend!

from effect.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.