0
object IO {

  def getHtmlFromWebsiteViaHttp(link: String, apiKey: String = ""): String = {
    Http(link)
      .param("access_token", apiKey)
      .asString
      .body
  }
}

class SongService {
  private def retrieveSongId(songName: String): Option[JsValue] = {
    val formattedSongName = songName.replace(" ", "%20")
    val searchLink = "https://api.genius.com/search?q=" + formattedSongName

    //impure call
    val geniusStringResponse = IO.getHtmlFromWebsiteViaHttp(searchLink, apiKey)

   //Extra processing on geniusStringResponse
  }
}

My current design is I would have a service class which is responsible for getting some information via an external API. Now I understand that it's impossible to have 100% pure functions.

My question: What is the best way to handle situations where you need to connect to an external API in Scala/FP?. The aim is to have the most adequate 'functional programming style' by minimising impure functions

Currently, I am encapsulating all API calls in IO object. Is this suitable enough? I see examples of monads for situations. Should I incorporate a monad style in this case?

bob9123
  • 725
  • 1
  • 9
  • 31
  • *"I am encapsulating all API calls in IO object"* - you mean, you just took your impure functions and moved them into in an `object` called `"IO"`? I think it's a bit of an oversimplification... – Andrey Tyukin Aug 30 '18 at 19:12
  • Right. So if that is not the optimal way of approaching this, how can it be done? – bob9123 Aug 30 '18 at 19:41
  • I'm not sure how this is impure. You are simply getting a value from an external resource. Seems referentially transparent to me. Like @AndreyTyukin said, moving the code to a separate object is just that... moving code around. It doesn't (practically) affect the functioning of your program. – Lasf Aug 30 '18 at 19:56
  • Yes but isn't the action of getting value from an external resource inherently impure? There is no guarantee that the output will be the same with a specified input. (404 error or 301 ok) – bob9123 Aug 30 '18 at 20:11
  • @Lasf It is not referentially transparent, as it could easily give different results due to changes in external state, such as the server you're trying to access going down or the machine losing internet access. Not to mention that the API's song list is probably updated regularly. – Brian McCutchon Aug 31 '18 at 03:50
  • Ok, fair enough. Have a look some of the IO monads out there, like [Cats IO](https://typelevel.org/cats-effect/datatypes/io.html) or Scalaz's. You can wrap `getHtmlFromWebsiteViaHttp` in the IO monad, and then typically thread the IO through your program to the end, where you would run it (once). So, for example, `retrieveSongId` would return also return an IO monad as well: `IO[Option[JsValue]]`. – Lasf Aug 31 '18 at 05:21

1 Answers1

0

This isn't so much an FP problem, as I don't see any problems with your code in terms of FP, but what you should do, in my opinion is use dependency injection, such that, for testing, you can substitute a test class for IO that has a guaranteed response. Something like this:

abstract class IO {
  def getHtmlFromWebsiteViaHttp(link: String, apiKey: String = ""): String
}

class IOImpl extends IO {
  def getHtmlFromWebsiteViaHttp(link: String, apiKey: String = ""): String = {
    Http(link)
      .param("access_token", apiKey)
      .asString
      .body
  }
}

class IOTestImpl extends IO {
  def getHtmlFromWebsiteViaHttp(link: String, apiKey: String = ""): String = ??? //some test HTML
}

And then in your service:

class SongService(io: IO) {
  private def retrieveSongId(songName: String): Option[JsValue] = {
    val formattedSongName = songName.replace(" ", "%20")
    val searchLink = "https://api.genius.com/search?q=" + formattedSongName
    val geniusStringResponse = io.getHtmlFromWebsiteViaHttp(searchLink, apiKey)
   //Extra processing on geniusStringResponse
  }
}

Then when you instantiate your SongService, pass it IOTestImpl in testing and IOImpl otherwise. You might find some relevant information on dependency injection and database access objects.

Lasf
  • 2,536
  • 1
  • 16
  • 35