将Haskell monad直接翻译成Scala

时间:2013-03-01 02:59:03

标签: scala haskell monads

尝试学习如何编写Scala中的monad,遇到了一些麻烦

鉴于快速代码示例

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

如何在Scala(而不是scalaz)中编写相同的事物?据我所知,它足以在scala中实现map / flatMap方法,但这里返回的是什么?如何在 for 语句中没有自由变量进行绑定?

2 个答案:

答案 0 :(得分:9)

这是一个几乎直接的翻译,我认为应该回答你的问题。它不是完全直接的,因为它不使用在Scala中以模式的形式存在的类型类,因为在当前情况下,它只会在没有真正原因的情况下过于复杂化。

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

此解决方案演示了一种经典的面向对象方法。使用这种方法,您不能将return函数封装在LJ类型中,因为您最终在另一个级别上工作 - 不是在类型类型的类型上,而是在类型的实例上。因此LJ案例类构造函数成为return的对应部分。

答案 1 :(得分:4)

我会认为Nikita的答案是惯用的翻译(在现实世界中应该首选,例如因为理解支持),但它绝对不是最“直接”的翻译。

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 _)
}

请注意,您可以在顶部添加一些语法糖,以便获得一个不错的(>>=)运算符。