问题
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?
回答1:
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
.
回答2:
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.
来源:https://stackoverflow.com/questions/15149738/direct-translation-of-haskell-monad-into-scala