Stream: beginners

Topic: Proper functional code in ROC


view this post on Zulip Artur Domurad (Aug 04 2023 at 13:45):

Not sure if it is too early to ask about "idiomatic" ways of writing Roc code, but here we go... :D

I'm playing with a small "End2End Tests" package in Roc that is using the WebDriver (selenium) underneath.
But the raw web client needs the sessionId as a parameter with every request:

runTest = \{} ->
    sessionId <- WebDriver.startSession {} |> await

    _ <- WebDriver.navigateTo sessionId "https://google.pl" |> await

    button1 <- WebDriver.findElement sessionId (Css "#submit-button") |> await

    _ <- WebDriver.clickElement sessionId button1 |> await

Would it be acceptable in Roc to return a record with functions that have the sessionId in closure? like this:

runTest = \{} ->
    r2E <- Roc2End.startSession "https://google.pl" |> await

    button1 <- r2E.findElement (Css "#submit-button") |> await

    _ <- r2E.clickElement button1 |> await

The second snippet is easier to use and easier to read, but it seems like "cheating" and artificially creating "class objects"...

Would this be considered an anti-pattern? Or is it ok?

// btw. I think that in haskell you would use a monad to hide the sessionId

view this post on Zulip Brendan Hansknecht (Aug 04 2023 at 15:50):

I think you can also do something monadic in roc if you want. I think roc random has a similar API to avoid needing to pipe the state around everywhere

view this post on Zulip Brendan Hansknecht (Aug 04 2023 at 15:51):

That said, I think the closures are pretty reasonable as long as the session won't need to get updated.

view this post on Zulip Richard Feldman (Aug 04 2023 at 17:44):

a relevant question here is: do you ever need to run a Task in between any of these? Or can it be just "run all the webdriver steps one after the other, with no Tasks ever being run in between any step" and that's sufficient?

view this post on Zulip Artur Domurad (Aug 04 2023 at 18:10):

in most use cases when using a webdriver you probably want to wait for the result before continuing.
e.g. you need to get the id of the button before you can use it to send a "click":

button1 <- WebDriver.findElement sessionId (Css "#submit-button") |> await

WebDriver.clickElement sessionId button1

or get the value of an input and then decide what to do next.

But to be fair - this is not a great problem to be solved using a pure functional language.
Everything you do with a webdriver is just side effects :grinning:

I'm just using the Http api from the basic-cli platform for this...
But I think that you could create a nice api and a great user experience even for end-to-end tests, by creating a dedicated e2e platform.

Can't wait to see what amazing platforms people will create in the future :heart:

view this post on Zulip Richard Feldman (Aug 04 2023 at 18:43):

oh I think this is a fine use case!

I'm specifically asking about Task because it has implications for what your options are for handling the session

view this post on Zulip Richard Feldman (Aug 04 2023 at 18:46):

waiting for the result before continuing is totally fine, but it matters whether after you get that result you might want to run an arbitrary Task (like writing to a file or something) before doing the next webdriver action

view this post on Zulip Richard Feldman (Aug 04 2023 at 18:48):

personally the way I've written tests like this in the past, I've never needed that, but I'm not sure if the use cases you have in mind are different!

view this post on Zulip Richard Feldman (Aug 04 2023 at 18:48):

(e.g. maybe a particularly customizable scraper might want to do this, I could imagine, but if it's just for e2e tests, maybe not)

view this post on Zulip Artur Domurad (Aug 05 2023 at 09:01):

you're right!
when writing e2e test I don't need to run any tasks between the steps.

not sure if this is what you had on your mind, but I could create pure functions and a pure data structure to write the tests, and then a function e.g. "runTest" would take the data structure and interpret it to make the http calls and run the tasks.
Almost like pushing out the impure code to the edges of my program.
idk, will try to experiment with that.

view this post on Zulip Richard Feldman (Aug 05 2023 at 11:01):

yeah exactly!

view this post on Zulip Richard Feldman (Aug 05 2023 at 11:08):

there are a couple of ways to do that, and they have different tradeoffs in terms of how the API feels to use and how the under-the-hood data structure looks

view this post on Zulip Richard Feldman (Aug 05 2023 at 11:10):

for example, there's a way to make the API look and feel like Task, while having an interpretable structure under the hood, but it looks strange under the hood and I don't think I ever would have guessed how to do it if someone hadn't explained the technique to me, so let me know if that's of interest! :big_smile:


Last updated: Jul 05 2025 at 12:14 UTC