Direct translation of Haskell monad into Scala

不羁岁月 提交于 2020-01-23 07:54:05

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!