PurpleKingdomGames / tyrian

Elm-inspired Scala UI library.
https://tyrian.indigoengine.io/
MIT License
350 stars 26 forks source link

Unable to effectfully create a `Http.send` command #224

Closed TonioGela closed 1 year ago

TonioGela commented 1 year ago

Important things first: thank you for this awesome library. It's a pleasure to use, and it's so well designed that it's usage is immensely intuitive.

<pun> At the moment is the best thing that ever happened to me to pursue my life goal of not learning Javascript at all. </pun>

That said, I noted a little design flaw that is incredibly easy to fix IMHO.

While trying to copy the text of a textArea to send it to the backend of my application more or less in this way:

def copyTextFromTextArea: IO[String] = for
  textArea <- IO.delay(
    document.getElementById("content").asInstanceOf[HTMLTextAreaElement] //YOLO
  )
  content = textArea.value
  _ <- IO.delay(textArea.value = "")
yield content

def save(content: String): Cmd.Run[IO, String, String] = Http.send[IO, String, String](
  Request.post("the_url", Body.plainText(content)),
  Decoder[String](_.body, _ => "")
)

def copyAndSend: IO[ResponseContent]] = for
  text <- copyTextFromTextArea
  resp <- save(text).toTask //compile error here!
yield ResponseContent(resp)

I noticed that Http.send[F,A,Msg] is declared as returning Cmd[F, Msg] while effectively returning an instance of Cmd.Run[F, Msg, Msg].

This signature prevents from calling the toTask helper method defined for Cmd.Run, a thing that could enable funnier paradigms like being able to flatten a structure like F[Cmd.Run[F, _, _]] to either F[_] or Cmd[F,_,_] by simply combining in the right way Cmd.Run.apply and .toTask.

The solution is incredibly simple: redeclaring .send return as Cmd.Run[F, Msg, Msg] here: it doesn't break the test nor the compilation wherever it is already used as we're simply narrowing the return type of a function (that btw is covariant in the return type). [EDIT] @Daenyth made me notice that this modification is binary compatible too, so there's no need to wait for an API breaking change.

One confirmation might come from the fact that casting the Http.send result to Cmd.Run works flawlessly

def copyAndSend: IO[ResponseContent]] = for
  text <- copyTextFromTextArea
  resp <- save(text).asInstanceOf[Cmd.Run[IO, ResponseContent, ResponseContent]].toTask //YOLO
yield ResponseContent(resp)

If you like this solution I can open a PR asap.

Thanks again!