effect icon indicating copy to clipboard operation
effect copied to clipboard

Standard type extraction

Open mikearnaldi opened this issue 1 year ago • 20 comments

In Schema we now added ability to extract types like: typeof Person.Type that makes it really nice to get the types, the same should be applied to things such as tags so one can do typeof Service.Type and to Effect so that one can do typeof Effect.Success etc.

I do feel though that unless we prefix those fake properties somehow it can become confusing, one might argue that defining them in uppercase makes it clear that they are meant to be types and not real values.

This issue is to discuss on a standard.

cc @gcanti @tim-smart @IMax153 @datner @patroza @fubhy

mikearnaldi avatar Jun 12 '24 09:06 mikearnaldi

So far I think that the capitalization is sufficient. Types are generally capitalized. Uppercase generally signifies constants. JSDoc should take care of the rest.

patroza avatar Jun 12 '24 10:06 patroza

If we wanted to be even more pedantic, we could add a Type suffix, so:

Effect.SuccessType
Effect.ErrorType
Effect.ContextType

but to be honest I think this is unnecessary and overly verbose. I agree with @patroza that the capitalization should generally be sufficient, along with a JSDoc explaining what the value is intended to be used for.

IMax153 avatar Jun 12 '24 20:06 IMax153

One other option is a namespace:

typeof someEffect.Types.Success

But I think I prefer .Success etc directly on the type.

tim-smart avatar Jun 13 '24 06:06 tim-smart

One other option is a namespace:

typeof someEffect.Types.Success

lol you mad.. can never have enough namespaces can we, we'd be back at square one: Effect.Effect.Success<typeof X>. then again it might be preferred over pre or suffixes, and it might address mikes concerns.

patroza avatar Jun 13 '24 06:06 patroza

while

typeof someEffect.Types.Success

would be the most obvious, the more nested it is the less benefit it has over Effect.Effect.Success<typeof someEffect> Like, if we just alias it to the module level then compared with an Effect.Success<typeof someEffect> it's barely any different.

It has to be

typeof someEffect.Success

if at all 🤔 (name pending, I only mean the nesting)

datner avatar Jun 15 '24 16:06 datner

I would also offer to consider lifting the stand-alone type accessors from the namespace to the top-level. I often use bare types for polymorphism, and my codebase is full of

const exchangeCtx = yield* Effect.context<
        Layer.Layer.Success<ExchangeLayer>
      >()

However, there is nothing preventing us to have Layer.Success, because there are no conflicts on the top-level namespace. In the end of the day – having a nested namespaces feels cumbersome

dilame avatar Jul 18 '24 02:07 dilame

@dilame I would very confidently place the usage of Effect.context to be in the bottom slots of the "very rare" bracket of utils, so I can't quite imagine how you get a 'codebase full of' it. Regardless, the question at hand is not about eliminating the nested namespace. That would not happen most likely as the purpose of it is to enrich the base type Effect (aka Effect.Effect) with utilities like Effect.Success (aka Effect.Effect.Success). The question is about not needing utilities at all.

In your case, it would mean typeof ExchangeLayer.Success, not Layer.Success<typeof ExchangeLayer>. If you seek the latter, just change

- import { Layer } from "effect"
+ import { Layer } from "effect/Layer"

to get Layer.Success<typeof ExchangeLayer>

datner avatar Jul 20 '24 17:07 datner

I think Effect.Success, Effect.Error and Effect.Context is fine

fubhy avatar Jul 20 '24 18:07 fubhy

@datner it will work when you are relying on runtime entities, but will not work when you use Layer as kind of OOP polymorphism (which i believe is the original intention of layers)

export type ModeLayer = Layer.Layer<
  | ModeKlineImpl
  | ModeOrderCancelImpl
  | ModeOrderCreateImpl
  | ModeOrderFetchImpl
  | ModeOrderListenImpl
  | ModePriceImpl,
  never,
  Layer.Layer.Success<ExchangeLayer>
>;

dilame avatar Jul 20 '24 20:07 dilame

@dilame I honestly have no clue what you're showing me, I don't have code that looks like that.. Remember that effects patterns are still emerging, there are very few blessed ways to use effect and they are largely technically driven and don't force a specific design so it's not odd we'll find different patterns, but I have no idea why would you want / need that code 😅 I'm just saying that it's a different conversation, and currently it's impossible to merge namespaces in typescript (we tried, really hard). Lets refocus please, do you have any opinions regarding typeof myEffect.Succeed? Do you think it should exist?

datner avatar Jul 21 '24 20:07 datner

@datner it will work when you are relying on runtime entities, but will not work when you use Layer as kind of OOP polymorphism (which i believe is the original intention of layers)

export type ModeLayer = Layer.Layer<
  | ModeKlineImpl
  | ModeOrderCancelImpl
  | ModeOrderCreateImpl
  | ModeOrderFetchImpl
  | ModeOrderListenImpl
  | ModePriceImpl,
  never,
  Layer.Layer.Success<ExchangeLayer>
>;

Very much not the intended usage of layers. A layer represent a layer in the sense defined by the onion architecture, so a progressive translation of implementation from most abstract to least. Never seen a layer being manually typed, and also having *Impl in the signature is kind of bad, it should be the generic interface not the specific impl

mikearnaldi avatar Jul 21 '24 20:07 mikearnaldi

Very much not the intended usage of layers. A layer represent a layer in the sense defined by the onion architecture

I believe that OOP polymorphism and Effect Layer are both low-level abstractions aimed at achieving the goals of high-level architectures like Onion, Ports and Adapters, Hexagonal, Whatever. These paradigms emphasize separation of concerns and dependency inversion. Thus, I see Effect Layer as an alternative to OOP polymorphism within these architectural frameworks. They are not opposing concepts but tools that serve the same purpose in maintaining a clean architecture.

dilame avatar Jul 21 '24 20:07 dilame

it's not odd we'll find different patterns, but I have no idea why would you want / need that code 😅

You are so tactful and real at the same time, i like it!

Lets refocus please, do you have any opinions regarding typeof myEffect.Succeed? Do you think it should exist?

I have no doubt that typeof myEffect.Succeed should exist.

Practically speaking, to get the type of myEffect at runtime, we have to write typeof myEffect anyway. If we extract generics using another generic type, the best case would look like this:

import { Success } from 'effect/Effect'

Success<typeof myEffect>

This adds Success and two extra symbols < and >. Typically, imports are import { Effect } from 'effect', making the extraction code even longer.

typeof myEffect.Succeed just adds single dot symbol and requires no extra imports. I vote with three hands for typeof myEffect.Succeed.

dilame avatar Jul 21 '24 21:07 dilame

Very much not the intended usage of layers. A layer represent a layer in the sense defined by the onion architecture

I believe that OOP polymorphism and Effect Layer are both low-level abstractions aimed at achieving the goals of high-level architectures like Onion, Ports and Adapters, Hexagonal, Whatever. These paradigms emphasize separation of concerns and dependency inversion. Thus, I see Effect Layer as an alternative to OOP polymorphism within these architectural frameworks. They are not opposing concepts but tools that serve the same purpose in maintaining a clean architecture.

Even following your logic a sentence like @datner it will work when you are relying on runtime entities semantically has no meaning (makes no sense), code like the one you posted is not common and I'd go as far as to say it's even an anti-pattern. Anyway all of this is fairly off topic, feel free to continue the discussion via discord, this issue only talks about type extractors and I think we reached agreement on the team that type of Effect.Success is a valid option

mikearnaldi avatar Jul 22 '24 05:07 mikearnaldi

Hmm we will have to change the variance in some scenarios to make this work:

image

It also seems to break the Effect.Success assignability where void is expected.

tim-smart avatar Jul 29 '24 03:07 tim-smart

@tim-smart I think this technique is applicable only to covariant and invariant type parameters (as is the case with @effect/schema)

gcanti avatar Jul 29 '24 06:07 gcanti

We might be able to use the same trick as the Iterators to disconnect the type helpers from the type variance. Will have a play around tomorrow.

tim-smart avatar Jul 29 '24 07:07 tim-smart

We might be able to use the same trick as the Iterators to disconnect the type helpers from the type variance. Will have a play around tomorrow.

Not sure that's possible

mikearnaldi avatar Jul 29 '24 07:07 mikearnaldi

An alternative would be to have a general GetType helper to use like: GetType<typeof program>["Success"] which can be made by enhancing the supported types with an extractor symbol that preserves the variance, it's a bit more involved than simply typeof program.Success but it can be made truly general, ideally we could even shorten it to like Type<typeof program>["Success"]

mikearnaldi avatar Jul 30 '24 10:07 mikearnaldi

Maybe just moving them to the top level is the simplest approach?

tim-smart avatar Jul 30 '24 20:07 tim-smart