-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: instances for cats #437
Comments
I thought about that too but I'm a bit apprehensive about this move because Output isn't really an equivalent of IO, not in the "global monad" sense - Output has Pulumi-oriented semantics and while it prooooobably would pass discipline tests for a Monad, Functor and Applicative it has some funny semantics related to dry runs (previews). In previews there are actually two different types of Outputs: static ones that are just like IO and if they say they are def doThings[F[_]: Async: LiftOutput]: F[Unit] = // assuming we can lift Output to F using a typeclass
for
uuid <- UUIDGen.randomUUID[F]
// this is resource constructor, it returns a static Output, behaves like IO
bucket <- s3.Bucket("my-bucket", s3.BucketArgs(name=s"my-bucket-$uuid").lift[F]
// properties on resources are computed Outputs so this will short-circuit in dry run and break plan
bucketName <- bucket.name.lift[F]
_ <- uploadAFile[F]("my-file", bucketName, "./my-file.html")
yield ()
def uploadAFile[F[_]: Async: LiftOutput](
name: NonEmptyString,
bucketName: String,
path: Path
): F[s3.BucketObject] =
s3.BucketObject(name,
s3.BucketObjectArgs(
bucket = bucketName,
key= name,
source=pulumi.FileAsset(path.toString),
etag=std.filemd5(input=path)
)
).lift[F] but this would - in dry run - completely skip the part where a file is uploaded to the bucket where without a flatMap this operation would show up in the plan shown as a result of dry run / preview. There's also another problem - Besom is not written in tagless final style and therefore one can't put There's also ongoing work to make the distinction between computed Outputs and static Outputs known on type level and to inform users about the possibility of broken plan in dry run due to resource constructor being called in a flatMap on a computed Output and it is possible that it will introduce separation between these two types of Outputs by splitting them into two separate types and that would force us to do even more magic with cats instances I'm afraid. If you have another use case for those instances in mind please do tell! I'm also thinking about converting this issue to a discussion given that this is quite an important thing and we would really like to make it very obvious how Outputs work and discussions are probably easier to locate and pin than issues. |
Just to be clear - this is what I mean by Outputs being the final types and IOs/ZIOs/Futures being subsumed into them: def doThings: Output[Unit] = {
// notice `p""` interpolator:
val bucket = s3.Bucket("my-bucket",
s3.BucketArgs(name= p"my-bucket-${Output.eval(UUIDGen.randomUUID[IO])}"
)
// no flatMap, bucket name property is being passed as Output, direct syntax available via lifting
uploadAFile("my-file", bucket.name, "./my-file.html").void
}
def uploadAFile(
name: NonEmptyString,
bucketName: Output[String],
path: Path
): Output[s3.BucketObject] =
s3.BucketObject(name,
s3.BucketObjectArgs(
bucket = bucketName,
key= name,
source=pulumi.FileAsset(path.toString),
etag=std.filemd5(input=path)
)
) info about lifting: https://virtuslab.github.io/besom/docs/lifting |
Thanks for the elaborate response! I have to admit I haven't tried running the Monad laws on My immediate concrete use case was usage of the (btw unrelated but it seems like both Intellij and Metals are struggling with type inference, not sure if it's work in progress or if it's just due to current scala 3 integration state of things) |
To follow up on this, have you considered representing the On the plus side, I'm thinking a |
Hey, I've been thinking about this a lot lately. I've considered using a reader monad in the design stage but I wasn't able to come up with a solution to a pretty important invariant we have to never break. Context carries around a reverse semaphore called internally |
Thanks for the detailed answer! From your description, I understand one challenge is to implement a kind of thread barrier for the completion of all the asynchronous tasks. My suggestion of using a reader was also a question regarding the feasibility of separating the formulation of the besom program from its execution in such a way that the pulumi instructions could be treated as pure values. But I'm not familiar with your internals, so feel free to discard 😄 So with a definition a bit like this: case class BesomProgram[A](run: Output[Context => A]):
def map(f: A => B) = ???
def flatMap(f: A => ContextReader[B]) = ??? then your program could be
so that you could do
Essentially all accesses to pulumi would somehow have to be tracked into |
Such a formulation doesn't seem to prevent our two biggest problems as far as I can see:
2nd would be actually much easier to deal with (because in usual pure FP for-comp-based monadic api you just flatMap stuff and that's how you control evaluation count) if not for the 1st! Current state of the SDK is extremely similar to other Pulumi SDKs and that's by design (to allow users of other SDKs to understand Besom by looking at the code). Divergences from behavior or shape of behavior of other SDKs is very small (there's just a single behavioral difference caused by our need to memoize resource constructor calls, other than that we return resources in Outputs and other SDKs return resources unwrapped so a single syntactic difference). We could potentially diverge a bit more if it meant radical improvement in usability. I was thinking about autoderived a) let's assume a field of type String on some resource that gets populated during runtime This is just one option but it's fairly easy to see that it's going to be troublesome. The other aforementioned option is to detect usage of resource constructors inside of Output#flatMap (and this is the direction we're exploring the most). This would push users towards the style we currently promote (kinda like direct-style, with Outputs used to transform properties that are then fed to Inputs of other resources) and that's also similar to other SDKs. |
For 🐱 lovers out there, how about including cats instances in the besom-cats package?
allows for compact syntax like
role >> roleBinding >> service
(the tailrec might need an actual implementation, which probably requires access to internals)
The text was updated successfully, but these errors were encountered: