How to write tail-recursive functions when working inside monads












13















In general I have problems figuring out how to write tailrecursive functions when working 'inside' monads. Here is a quick example:



This is from a small example application that I am writing to better understand FP in Scala. First of all the user is prompted to enter a Team consisting of 7 players. This function recursively reads the input:



import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._

case class Player (name: String)
case class Team (players: List[Player])

/**
* Reads a team of 7 players from the command line.
* @return
*/
def readTeam: IO[Team] = {
def go(team: Team): IO[Team] = { // here I'd like to add @tailrec
if(team.players.size >= 7){
IO(println("Enough players!!")) >>= (_ => IO(team))
} else {
for {
player <- readPlayer
team <- go(Team(team.players :+ player))
} yield team
}
}
go(Team(Nil))
}

private def readPlayer: IO[Player] = ???


Now what I'd like to achieve (mainly for educational purposes) is to be able to write a @tailrec notation in front of def go(team: Team). But I don't see a possibility to have the recursive call as my last statement because the last statement as far as I can see always has to 'lift' my Team into the IO monad.



Any hint would be greatly appreciated.










share|improve this question





























    13















    In general I have problems figuring out how to write tailrecursive functions when working 'inside' monads. Here is a quick example:



    This is from a small example application that I am writing to better understand FP in Scala. First of all the user is prompted to enter a Team consisting of 7 players. This function recursively reads the input:



    import cats.effect.{ExitCode, IO, IOApp}
    import cats.implicits._

    case class Player (name: String)
    case class Team (players: List[Player])

    /**
    * Reads a team of 7 players from the command line.
    * @return
    */
    def readTeam: IO[Team] = {
    def go(team: Team): IO[Team] = { // here I'd like to add @tailrec
    if(team.players.size >= 7){
    IO(println("Enough players!!")) >>= (_ => IO(team))
    } else {
    for {
    player <- readPlayer
    team <- go(Team(team.players :+ player))
    } yield team
    }
    }
    go(Team(Nil))
    }

    private def readPlayer: IO[Player] = ???


    Now what I'd like to achieve (mainly for educational purposes) is to be able to write a @tailrec notation in front of def go(team: Team). But I don't see a possibility to have the recursive call as my last statement because the last statement as far as I can see always has to 'lift' my Team into the IO monad.



    Any hint would be greatly appreciated.










    share|improve this question



























      13












      13








      13


      7






      In general I have problems figuring out how to write tailrecursive functions when working 'inside' monads. Here is a quick example:



      This is from a small example application that I am writing to better understand FP in Scala. First of all the user is prompted to enter a Team consisting of 7 players. This function recursively reads the input:



      import cats.effect.{ExitCode, IO, IOApp}
      import cats.implicits._

      case class Player (name: String)
      case class Team (players: List[Player])

      /**
      * Reads a team of 7 players from the command line.
      * @return
      */
      def readTeam: IO[Team] = {
      def go(team: Team): IO[Team] = { // here I'd like to add @tailrec
      if(team.players.size >= 7){
      IO(println("Enough players!!")) >>= (_ => IO(team))
      } else {
      for {
      player <- readPlayer
      team <- go(Team(team.players :+ player))
      } yield team
      }
      }
      go(Team(Nil))
      }

      private def readPlayer: IO[Player] = ???


      Now what I'd like to achieve (mainly for educational purposes) is to be able to write a @tailrec notation in front of def go(team: Team). But I don't see a possibility to have the recursive call as my last statement because the last statement as far as I can see always has to 'lift' my Team into the IO monad.



      Any hint would be greatly appreciated.










      share|improve this question
















      In general I have problems figuring out how to write tailrecursive functions when working 'inside' monads. Here is a quick example:



      This is from a small example application that I am writing to better understand FP in Scala. First of all the user is prompted to enter a Team consisting of 7 players. This function recursively reads the input:



      import cats.effect.{ExitCode, IO, IOApp}
      import cats.implicits._

      case class Player (name: String)
      case class Team (players: List[Player])

      /**
      * Reads a team of 7 players from the command line.
      * @return
      */
      def readTeam: IO[Team] = {
      def go(team: Team): IO[Team] = { // here I'd like to add @tailrec
      if(team.players.size >= 7){
      IO(println("Enough players!!")) >>= (_ => IO(team))
      } else {
      for {
      player <- readPlayer
      team <- go(Team(team.players :+ player))
      } yield team
      }
      }
      go(Team(Nil))
      }

      private def readPlayer: IO[Player] = ???


      Now what I'd like to achieve (mainly for educational purposes) is to be able to write a @tailrec notation in front of def go(team: Team). But I don't see a possibility to have the recursive call as my last statement because the last statement as far as I can see always has to 'lift' my Team into the IO monad.



      Any hint would be greatly appreciated.







      scala io monads tail-recursion cats-effect






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Feb 2 at 10:50







      Florian Baierl

















      asked Feb 2 at 10:28









      Florian BaierlFlorian Baierl

      7031828




      7031828
























          1 Answer
          1






          active

          oldest

          votes


















          18














          First of all, this isn't necessary, because IO is specifically designed to support stack-safe monadic recursion. From the docs:




          IO is trampolined in its flatMap evaluation. This means that you can safely call flatMap in a recursive function of arbitrary depth, without fear of blowing the stack…




          So your implementation will work just fine in terms of stack safety, even if instead of seven players you needed 70,000 players (although at that point you might need to worry about the heap).



          This doesn't really answer your question, though, and of course even @tailrec is never necessary, since all it does is verify that the compiler is doing what you think it should be doing.



          While it's not possible to write this method in such a way that it can be annotated with @tailrec, you can get a similar kind of assurance by using Cats's tailRecM. For example, the following is equivalent to your implementation:



          import cats.effect.IO
          import cats.syntax.functor._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = cats.Monad[IO].tailRecM(Team(Nil)) {
          case team if team.players.size >= 7 =>
          IO(println("Enough players!!")).as(Right(team))
          case team =>
          readPlayer.map(player => Left(Team(team.players :+ player)))
          }


          This says "start with an empty team and repeatedly add players until we have the necessary number", but without any explicit recursive calls. As long as the monad instance is lawful (according to Cats's definition—there's some question about whether tailRecM even belongs on Monad), you don't have to worry about stack safety.



          As a side note, fa.as(b) is equivalent to fa >>= (_ => IO(b)) but more idiomatic.



          Also as a side note (but maybe a more interesting one), you can write this method even more concisely (and to my eye more clearly) as follows:



          import cats.effect.IO
          import cats.syntax.monad._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = Team(Nil).iterateUntilM(team =>
          readPlayer.map(player => Team(team.players :+ player))
          )(_.players.size >= 7)


          Again there are no explicit recursive calls, and it's even more declarative than the tailRecM version—it's just "perform this action iteratively until the given condition holds".





          One postscript: you might wonder why you'd ever use tailRecM when IO#flatMap is stack safe, and one reason is that you may someday decide to make your program generic in the effect context (e.g. via the finally tagless pattern). In this case you should not assume that flatMap behaves the way you want it to, since lawfulness for cats.Monad doesn't require flatMap to be stack safe. In that case it would be best to avoid explicit recursive calls through flatMap and choose tailRecM or iterateUntilM, etc. instead, since these are guaranteed to be stack safe for any lawful monadic context.






          share|improve this answer





















          • 1





            Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

            – Florian Baierl
            Feb 2 at 12:36











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54492133%2fhow-to-write-tail-recursive-functions-when-working-inside-monads%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          18














          First of all, this isn't necessary, because IO is specifically designed to support stack-safe monadic recursion. From the docs:




          IO is trampolined in its flatMap evaluation. This means that you can safely call flatMap in a recursive function of arbitrary depth, without fear of blowing the stack…




          So your implementation will work just fine in terms of stack safety, even if instead of seven players you needed 70,000 players (although at that point you might need to worry about the heap).



          This doesn't really answer your question, though, and of course even @tailrec is never necessary, since all it does is verify that the compiler is doing what you think it should be doing.



          While it's not possible to write this method in such a way that it can be annotated with @tailrec, you can get a similar kind of assurance by using Cats's tailRecM. For example, the following is equivalent to your implementation:



          import cats.effect.IO
          import cats.syntax.functor._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = cats.Monad[IO].tailRecM(Team(Nil)) {
          case team if team.players.size >= 7 =>
          IO(println("Enough players!!")).as(Right(team))
          case team =>
          readPlayer.map(player => Left(Team(team.players :+ player)))
          }


          This says "start with an empty team and repeatedly add players until we have the necessary number", but without any explicit recursive calls. As long as the monad instance is lawful (according to Cats's definition—there's some question about whether tailRecM even belongs on Monad), you don't have to worry about stack safety.



          As a side note, fa.as(b) is equivalent to fa >>= (_ => IO(b)) but more idiomatic.



          Also as a side note (but maybe a more interesting one), you can write this method even more concisely (and to my eye more clearly) as follows:



          import cats.effect.IO
          import cats.syntax.monad._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = Team(Nil).iterateUntilM(team =>
          readPlayer.map(player => Team(team.players :+ player))
          )(_.players.size >= 7)


          Again there are no explicit recursive calls, and it's even more declarative than the tailRecM version—it's just "perform this action iteratively until the given condition holds".





          One postscript: you might wonder why you'd ever use tailRecM when IO#flatMap is stack safe, and one reason is that you may someday decide to make your program generic in the effect context (e.g. via the finally tagless pattern). In this case you should not assume that flatMap behaves the way you want it to, since lawfulness for cats.Monad doesn't require flatMap to be stack safe. In that case it would be best to avoid explicit recursive calls through flatMap and choose tailRecM or iterateUntilM, etc. instead, since these are guaranteed to be stack safe for any lawful monadic context.






          share|improve this answer





















          • 1





            Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

            – Florian Baierl
            Feb 2 at 12:36
















          18














          First of all, this isn't necessary, because IO is specifically designed to support stack-safe monadic recursion. From the docs:




          IO is trampolined in its flatMap evaluation. This means that you can safely call flatMap in a recursive function of arbitrary depth, without fear of blowing the stack…




          So your implementation will work just fine in terms of stack safety, even if instead of seven players you needed 70,000 players (although at that point you might need to worry about the heap).



          This doesn't really answer your question, though, and of course even @tailrec is never necessary, since all it does is verify that the compiler is doing what you think it should be doing.



          While it's not possible to write this method in such a way that it can be annotated with @tailrec, you can get a similar kind of assurance by using Cats's tailRecM. For example, the following is equivalent to your implementation:



          import cats.effect.IO
          import cats.syntax.functor._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = cats.Monad[IO].tailRecM(Team(Nil)) {
          case team if team.players.size >= 7 =>
          IO(println("Enough players!!")).as(Right(team))
          case team =>
          readPlayer.map(player => Left(Team(team.players :+ player)))
          }


          This says "start with an empty team and repeatedly add players until we have the necessary number", but without any explicit recursive calls. As long as the monad instance is lawful (according to Cats's definition—there's some question about whether tailRecM even belongs on Monad), you don't have to worry about stack safety.



          As a side note, fa.as(b) is equivalent to fa >>= (_ => IO(b)) but more idiomatic.



          Also as a side note (but maybe a more interesting one), you can write this method even more concisely (and to my eye more clearly) as follows:



          import cats.effect.IO
          import cats.syntax.monad._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = Team(Nil).iterateUntilM(team =>
          readPlayer.map(player => Team(team.players :+ player))
          )(_.players.size >= 7)


          Again there are no explicit recursive calls, and it's even more declarative than the tailRecM version—it's just "perform this action iteratively until the given condition holds".





          One postscript: you might wonder why you'd ever use tailRecM when IO#flatMap is stack safe, and one reason is that you may someday decide to make your program generic in the effect context (e.g. via the finally tagless pattern). In this case you should not assume that flatMap behaves the way you want it to, since lawfulness for cats.Monad doesn't require flatMap to be stack safe. In that case it would be best to avoid explicit recursive calls through flatMap and choose tailRecM or iterateUntilM, etc. instead, since these are guaranteed to be stack safe for any lawful monadic context.






          share|improve this answer





















          • 1





            Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

            – Florian Baierl
            Feb 2 at 12:36














          18












          18








          18







          First of all, this isn't necessary, because IO is specifically designed to support stack-safe monadic recursion. From the docs:




          IO is trampolined in its flatMap evaluation. This means that you can safely call flatMap in a recursive function of arbitrary depth, without fear of blowing the stack…




          So your implementation will work just fine in terms of stack safety, even if instead of seven players you needed 70,000 players (although at that point you might need to worry about the heap).



          This doesn't really answer your question, though, and of course even @tailrec is never necessary, since all it does is verify that the compiler is doing what you think it should be doing.



          While it's not possible to write this method in such a way that it can be annotated with @tailrec, you can get a similar kind of assurance by using Cats's tailRecM. For example, the following is equivalent to your implementation:



          import cats.effect.IO
          import cats.syntax.functor._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = cats.Monad[IO].tailRecM(Team(Nil)) {
          case team if team.players.size >= 7 =>
          IO(println("Enough players!!")).as(Right(team))
          case team =>
          readPlayer.map(player => Left(Team(team.players :+ player)))
          }


          This says "start with an empty team and repeatedly add players until we have the necessary number", but without any explicit recursive calls. As long as the monad instance is lawful (according to Cats's definition—there's some question about whether tailRecM even belongs on Monad), you don't have to worry about stack safety.



          As a side note, fa.as(b) is equivalent to fa >>= (_ => IO(b)) but more idiomatic.



          Also as a side note (but maybe a more interesting one), you can write this method even more concisely (and to my eye more clearly) as follows:



          import cats.effect.IO
          import cats.syntax.monad._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = Team(Nil).iterateUntilM(team =>
          readPlayer.map(player => Team(team.players :+ player))
          )(_.players.size >= 7)


          Again there are no explicit recursive calls, and it's even more declarative than the tailRecM version—it's just "perform this action iteratively until the given condition holds".





          One postscript: you might wonder why you'd ever use tailRecM when IO#flatMap is stack safe, and one reason is that you may someday decide to make your program generic in the effect context (e.g. via the finally tagless pattern). In this case you should not assume that flatMap behaves the way you want it to, since lawfulness for cats.Monad doesn't require flatMap to be stack safe. In that case it would be best to avoid explicit recursive calls through flatMap and choose tailRecM or iterateUntilM, etc. instead, since these are guaranteed to be stack safe for any lawful monadic context.






          share|improve this answer















          First of all, this isn't necessary, because IO is specifically designed to support stack-safe monadic recursion. From the docs:




          IO is trampolined in its flatMap evaluation. This means that you can safely call flatMap in a recursive function of arbitrary depth, without fear of blowing the stack…




          So your implementation will work just fine in terms of stack safety, even if instead of seven players you needed 70,000 players (although at that point you might need to worry about the heap).



          This doesn't really answer your question, though, and of course even @tailrec is never necessary, since all it does is verify that the compiler is doing what you think it should be doing.



          While it's not possible to write this method in such a way that it can be annotated with @tailrec, you can get a similar kind of assurance by using Cats's tailRecM. For example, the following is equivalent to your implementation:



          import cats.effect.IO
          import cats.syntax.functor._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = cats.Monad[IO].tailRecM(Team(Nil)) {
          case team if team.players.size >= 7 =>
          IO(println("Enough players!!")).as(Right(team))
          case team =>
          readPlayer.map(player => Left(Team(team.players :+ player)))
          }


          This says "start with an empty team and repeatedly add players until we have the necessary number", but without any explicit recursive calls. As long as the monad instance is lawful (according to Cats's definition—there's some question about whether tailRecM even belongs on Monad), you don't have to worry about stack safety.



          As a side note, fa.as(b) is equivalent to fa >>= (_ => IO(b)) but more idiomatic.



          Also as a side note (but maybe a more interesting one), you can write this method even more concisely (and to my eye more clearly) as follows:



          import cats.effect.IO
          import cats.syntax.monad._

          case class Player (name: String)
          case class Team (players: List[Player])

          // For the sake of example.
          def readPlayer: IO[Player] = IO(Player("foo"))

          /**
          * Reads a team of 7 players from the command line.
          * @return
          */
          def readTeam: IO[Team] = Team(Nil).iterateUntilM(team =>
          readPlayer.map(player => Team(team.players :+ player))
          )(_.players.size >= 7)


          Again there are no explicit recursive calls, and it's even more declarative than the tailRecM version—it's just "perform this action iteratively until the given condition holds".





          One postscript: you might wonder why you'd ever use tailRecM when IO#flatMap is stack safe, and one reason is that you may someday decide to make your program generic in the effect context (e.g. via the finally tagless pattern). In this case you should not assume that flatMap behaves the way you want it to, since lawfulness for cats.Monad doesn't require flatMap to be stack safe. In that case it would be best to avoid explicit recursive calls through flatMap and choose tailRecM or iterateUntilM, etc. instead, since these are guaranteed to be stack safe for any lawful monadic context.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Feb 2 at 14:04

























          answered Feb 2 at 12:02









          Travis BrownTravis Brown

          118k9295570




          118k9295570








          • 1





            Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

            – Florian Baierl
            Feb 2 at 12:36














          • 1





            Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

            – Florian Baierl
            Feb 2 at 12:36








          1




          1





          Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

          – Florian Baierl
          Feb 2 at 12:36





          Wow thank you very much for this detailed answer. I'll definetly use iterateUntilM in the future - much easier to read.

          – Florian Baierl
          Feb 2 at 12:36




















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54492133%2fhow-to-write-tail-recursive-functions-when-working-inside-monads%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Human spaceflight

          Can not write log (Is /dev/pts mounted?) - openpty in Ubuntu-on-Windows?

          張江高科駅