natefaubion / purescript-run

An extensible-effects implementation
MIT License
158 stars 14 forks source link

How to express duplicate instances for one DSL? #22

Closed julian-becker closed 5 years ago

julian-becker commented 5 years ago

First of all: Thanks alot for this great library!! I just read through the introduction and it seems to be an superb solution to the expression problem. However, trying to get a deeper understanding of it and I'm a bit stuck :-/ I'm just getting started with PureScript and functional programming in general, so please bear with me...

I'm wondering how to express a situation where I have multiple instances that are adressed with the same DSL: Taking your example from the README.md, imagine I want to have dinner with 2 persons, and both persons essentially have the same TalkF DSL, so I want to speak to each of them individually, and I listen to each of their responses. How would I express this with the facilities of this library?

Would I have to duplicate the commands like so, or is there a way to abstract over the SProxy-types?

_talkA = SProxy :: SProxy "talkA"
_talkB = SProxy :: SProxy "talkB"

speakA :: forall r. String -> Run (talkA :: TALK | r) Unit
speakA str = Run.lift _talkA (Speak str unit)

speakB :: forall r. String -> Run (talkB :: TALK | r) Unit
speakB str = Run.lift _talkB (Speak str unit)

Essentially, what I would like to do is something like

speakTo :: forall r. (person :: Person) -> String -> Run (person :: TALK | r) Unit
speakTo person str = Run.lift person (Speak str unit)

program :: forall r. Run (john :: TALK, johnsFriend :: TALK | r) Unit
program = do
  speakTo _john $ "Hey John, who's your friend?"
  friendsName <- listenTo _john
  speakTo _johnsFriend $ "Nice meeting you, " <> friendsName
  ...

but I can't figure out how to make this typecheck... Any pointers would be much appreciated.

JordanMartinez commented 5 years ago

To address your immediate issue, speakTo has the wrong type signature. I think it needs to be something like:

speakTo :: forall r s. IsSymbol s => SProxy s -> String -> Run (s :: TALK | r) Unit
speakTo person str = Run.lift person (Speak str unit)

-- and then you'd write something like
_john :: SProxy "john"
_john = SProxy

-- Your "program" code doesn't need to be changed

The issue here is the type-level programming that isn't working out correctly.

For context, see my overview of type-level programming here:


To provide a better deeper understanding of how Run actually works, Run is just a wrapper around the Free monad and a Variant-based way of combining multiple data types together. Due to how Run is defined, it uses type-level programming (specifically, type-level Strings in this case) to determine which "label" in your row kind to use.

A fast overview of Free that I came across recently is explained by Nate himself here: https://www.youtube.com/watch?v=eKkxmVFcd74&t=18

However, I overview Free by walking through the FP paper that brought greater attention to it here: https://github.com/JordanMartinez/purescript-jordans-reference/tree/latestRelease/21-Hello-World/08-Application-Structure/src/03-Free To understand how we get from Free to Run, see the 3rd folder.

A full working program that was written using Run can be found here:

natefaubion commented 5 years ago

You might also look at how the provided effects do it. For example, for STATE: https://github.com/natefaubion/purescript-run/blob/a16680c267cdc24a589021ca3a8986511cc5ef3f/src/Run/State.purs#L77-L86

natefaubion commented 5 years ago

I'm going to close this, but you might consider https://discourse.purescript.org/ for questions like this. It's a more central resource and will get more visibility as a general question rather than as an issue on the repo.

julian-becker commented 5 years ago

Thank you so much for your quick support, guys! Will definitely checkout the purescript discourse... I agree that it probably would have been a better place to ask a question like this.