4

Trying to learn how to program monads in Scala, got some troubles

Given the quick code sample

import Control.Monad

newtype LJ a = LJ { session :: a }

instance Monad LJ where
  return s = LJ s
  (>>=) m f = f ( session m )

instance Functor LJ where
  fmap f m = LJ . f $ session m

type SimpleLJ = LJ String

auth :: String -> String -> SimpleLJ
auth = undefined

readFeed :: String -> SimpleLJ
readFeed = undefined

closeFeed :: String -> SimpleLJ
closeFeed = undefined

proceed = auth "123" "456" >>= readFeed >>= closeFeed

how do I write the same thing in Scala (not scalaz)? As far as I learned, it's enough to implement map/flatMap methods in scala, but what is return here? And how to do binding without free variables in for statement?

jdevelop
  • 12,176
  • 10
  • 56
  • 112

2 Answers2

9

Here's an almost direct translation, which I believe should answer your question. It's not completely direct because it doesn't utilize typeclasses which are present in form of a pattern in Scala, because in the current case it would have only overcomplicated things without a real reason.

case class LJ[A]( session : A ) {
  // See it as Haskell's "fmap"
  def map[B]( f : A => B ) : LJ[B] = 
    LJ( f( session ) )
  // See it as Haskell's ">>="
  def flatMap[B]( f : A => LJ[B] ) : LJ[B] =
    f( session )
}

type SimpleLJ = LJ[String]

def auth( a : String, b : String ) : SimpleLJ = ???

def readFeed( a : String ) : SimpleLJ = ???

def closeFeed( a : String ) : SimpleLJ = ???

def proceed : SimpleLJ = 
  auth("123", "456").flatMap(readFeed).flatMap(closeFeed)

// Same as above but using a for-comprehension, which is 
// used as a replacement for Haskell's "do"-block
def proceed2 : SimpleLJ = 
  for {
    a <- auth("123", "456")
    b <- readFeed(a)
    c <- closeFeed(b)
  } 
  yield c

This solution demonstrates a classical object-oriented approach. With this approach you can't have the return function encapsulated in the LJ type because you end up working on another level - not on a type as with typeclasses, but on the instance of a type. So the LJ case class constructor becomes the counterpart of return.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • 1
    Is it possible to get rid of free vars somehow? Or it could be done with scalaz only? – jdevelop Mar 01 '13 at 13:51
  • 1
    @jdevelop Isn't `auth("123", "456").flatMap(readFeed).flatMap(closeFeed)` what you want? – Nikita Volkov Mar 01 '13 at 13:56
  • You could even drop some of the punctuation and write it like this: `auth("123", "456") flatMap readFeed flatMap closeFeed` – Dan Burton Mar 01 '13 at 18:46
  • Scala's `for` is more comparable to Haskell's `do` in that you save yourself the pain of repeating `flatMap` for every "statement". But as @jdevelop mentioned, I am wondering if when using Scala's `for` each step has to define a free variable. E.g. see this example where some statement define free variables, while others don't. http://en.wikibooks.org/wiki/Haskell/do_Notation#Example:_user-interactive_program – avernet Apr 09 '13 at 01:24
  • 2
    @avernet Scala's syntactical support for monads is weak. If you want to simulate the `>>` behaviour in a for comprehension, you'll have to declare a variable. The standard practice is to name it `_`, e.g.: `for { _ <- a; _ <- b; d <- c } yield d` is equivalent to Haskell's `a >> b >> c` or `do {a; b; c}` – Nikita Volkov Apr 09 '13 at 01:35
  • @NikitaVolkov This is very useful information; I didn't know about the `_` convention. – avernet Apr 09 '13 at 16:49
  • @NikitaVolkov Also, can you avoid the `yield` if you don't care about the result? E.g. the intend being `for { _ <- putStr("foo"); _ <- putStr("bar") }`? – avernet Apr 09 '13 at 19:04
  • @avernet Cannot. It's a syntax error. Remember that Scala also uses "for" for "for loops". Yeah, for for for ) – Nikita Volkov Apr 09 '13 at 19:08
  • @NikitaVolkov Just adding an empty block after the `for` worked with my monad. I.e. `for { _ <- doFoo _ <- doBar }()`. It's a little awkward, from a syntactic perspective, but it works. It would be nice if `for` wasn't a magical construct in Scala, but instead was built on top of something else, so we could implement a custom `for` that would be more appropriate to chaining monads. – avernet Apr 09 '13 at 23:43
  • @avernet You kinda elude the whole point of monads by using them this way, namely, that they must do no side effects and just return a value. Since in your example you simply ignore the final value, your whole construct doesn't make any sense. – Nikita Volkov Apr 09 '13 at 23:52
  • @NikitaVolkov I see your point. In this case, I am using a monadic construct to chain operations. Some operations are produce a result used by latter operations, but I don't care about the final result. Like you don't care about the particular `IO a` you get as a result of the execution of a Haskell program. – avernet Apr 10 '13 at 17:46
  • @avernet `IO a` is a description of computation it is not the computation itself, therefore it is not an alternative to a side-effecting function. So to produce any effect it must be executed, therefore you have to yield even the `IO ()` value, to later execute it. In case of Haskell runtime executes it when you put it in `main`, in case of Scala you have to execute it yourself with an effectful function. So in your case you're accumulating a description of computation, which you never execute. I think [this answer](http://stackoverflow.com/a/15836181/485115) might clear things up for you. – Nikita Volkov Apr 10 '13 at 18:03
4

I would consider Nikita's answer to be the idiomatic translation (which should be preferred in real world situations, e.g. because of for-comprehension support), but it is definitely not the most "direct" one.

class LJ[A](val session : A)

trait Functor[F[_]] {
   def fmap[A,B](fa:F[A])(f:A => B) : F[B]
}

trait Monad[M[_]] {
   def pure[A](a:A):M[A]
   def bind[A,B](ma:M[A])(f:A => M[B]):M[B]
}

object LJFunctor extends Functor[LJ] {
  def fmap[A,B](lj:LJ[A])(f:A => B) = new LJ(f(lj.session))
}

object LJMonad extends Monad[LJ] {
   def pure[A](a:A) = new LJ(a)
   def bind[A,B](lj:LJ[A])(f:A => LJ[B]) = f(lj.session)
}


object MonadTest {

  type SimpleLJ = LJ[String]

  def auth(s:String, t:String):SimpleLJ = null

  def readFeed(s:String):SimpleLJ = null

  def closeFeed(s:String):SimpleLJ = null

  val proceed = LJMonad.bind(LJMonad.bind(auth("123","456"))(readFeed _))(closeFeed _)
}

Note that you could add some syntactic sugar on top in order to get a nice (>>=) operator.

Landei
  • 54,104
  • 13
  • 100
  • 195