Skip to content
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

Can I use polymorphic effects to de facto substitue monad transformers in some cases? #171

Closed
szymon-rd opened this issue Nov 2, 2022 · 8 comments

Comments

@szymon-rd
Copy link

szymon-rd commented Nov 2, 2022

I had a couple of attempts on using polymorphic effects to make an interesting approach at polymorphy:

effect None[A](): A
effect Optional[A] = { None }

effect Done[A](taskId: Int): A
effect Runner {
  def runAsync[A](fn: Unit => A at {}): Int
}

effect Async = { Done, Runner }

def main(): Unit = {
  try { 
    doStuff { generateStuff() }
  } with // ... some implementation
}

def foo(): Int = 42

def generateStuff(): Int / {Optional[Int], Async} = {
  val taskId = do runAsync(foo)
  do Done[Int](taskId)
}

def doStuff { element: => Int / {} }: Int / {} = 
  element + 1

I think that with this snippet you can see what I was aiming at. I want to make the doStuff function capable of performing operations without knowing what are the details of elements - such as the fact that they are computed asynchronously or are optional. I came to the problem that effects can't have lambdas as parameters in any way. Neither in their own params, nor in the function parameters. Is there a way to go around that and achieve something similar to what I was aiming at?
My general goal is to investigate if polymorphic effects can be use to achieve something similiar to monad transformers. It would be great to have a code unaware of the real "context" of execution. Did you perform any research in that area?

@b-studios
Copy link
Collaborator

Hey! Thanks for reaching out :) First of all, let me try to respond to two things:

My general goal is to investigate if polymorphic effects can be use to achieve something similar to monad transformers.

Indeed, this is our motivation. You can use effects to model optional results (like you did with effect None).

I came to the problem that effects can't have lambdas as parameters in any way

From your code it looks like you managed after all. Congratulations, this is a somewhat hidden feature :)

Maybe discussing some more details of your code will further clarify some things.

effect Optional[A] = { None }

I believe this is not doing what you think it does. The A is completely ignored here. So you could as well write it as

effect Optional = { None }

Importantly, effect None[A](): A is syntactic sugar for

effect None {
  def None[A](): A
}

and not for

effect None[A] {
  def None(): A
}

I slightly rewrote your example to be a bit more "Effekt-idiomatic":
https://effekt-lang.org/examples/polymorphism.html

Notice how doStuff does not need to care about the effects of element: it is effect polymorphic. We call this form of polymorphism "contextual effect polymorphism".

A more detailed implementation of async await has been given in another example:

https://effekt-lang.org/examples/async.html

@szymon-rd
Copy link
Author

Thank you! I hoped for something exactly like this.

@szymon-rd
Copy link
Author

Oh. I just noticed that it returned 0, but as far as I understand it should return 1... Why is there a 0?

@szymon-rd szymon-rd reopened this Nov 3, 2022
@b-studios
Copy link
Collaborator

In the example I handle the effect by always aborting with the value 0. You can try to replace 0 with resume(0) to pick up the computation where the effect was triggered.

@szymon-rd
Copy link
Author

szymon-rd commented Nov 3, 2022

Thank you! That makes sense. Can I somehow cast to type A or reason about it in await (in your snippet)? Do you have any idea on how to make this code resume with 0 as A? (or some other solution that makes it work, but keeping it generic, if you can think of something else). I am trying to implement this.

@b-studios
Copy link
Collaborator

Hey, for the given type of type Optional { def none[A](): A} this is not possible and should not be :)
The effect operation none can be applied at any type and resuming with an integer would not be sound.

You can change the effect signature to

effect Optional[A] {
  def none(): A
}

def main(): Int = {
  try {
    doStuff { generateStuff() }
  } with Optional[Int] {
    def none() = resume(0)
  }
}

def generateStuff(): Int / { Optional[Int] } = {
  do none()
}

def doStuff { element: => Int / {} }: Int / {} =
  element() + 1

For a very simple implementation of async in terms of the JavaScript file io, you can look at this example here:

https://github.com/effekt-lang/effekt/blob/master/libraries/js/monadic/io/async.effekt

@b-studios
Copy link
Collaborator

I'm gonna close the issue. Let me know if I can be of any more help :)

@szymon-rd
Copy link
Author

Sure! Thank you for all your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants