接受两个monadic值并返回单个monadic值的通用函数

时间:2018-04-13 16:27:28

标签: scala haskell generics monads

我编写了下面的Haskell函数,它接受两个monadic值并将它们组合成一个monadic值(它只是为了说明Haskell类型的通用性(或泛型)的程度 - 系统可以支持)。

combine x y = do
  a <- x
  b <- y
  return (a, b)

我测试了三个不同的monad:

main = do
  putStrLn $ show $ combine (Just 10) (Just 20)  -- Maybe a
  putStrLn $ show $ combine [100] [10, 20]       -- [] a
  a <- combine getLine getLine                   -- IO a
  putStrLn $ show a

正如预期的那样works great。现在,我想知道Scala的类型系统是否允许我在不损害通用性的情况下编写上述函数。但我不太了解斯卡拉(虽然我想探索它)。那么有人可以帮助我将此代码转换为Scala吗?

3 个答案:

答案 0 :(得分:7)

我认为这相当于:

import cats._
import cats.implicits._

def combine[T, F[_]: Monad](fa: F[T], fb: F[T]) = for {
    a <- fa
    b <- fb
  } yield (a, b)

Monad来自图书馆(猫或scalaz)。

combine(Option(10), Option(20))生成Some((10,20))combine(List(100), List(10, 20))生成List((100,10), (100,20))

编辑:上述版本过度约束,因为它要求两个参数类型相同。 def combine[A, B, F[_]: Monad](fa: F[A], fb: F[B])解决了这个问题。

答案 1 :(得分:3)

您的combine函数等同于Scala代码

for { a <- x; b <- y } yield (a,b)

所以你可以尝试定义一个函数:

def combine[M[_],A,B](x: M[A], y: M[B]): M[(A,B)] = 
    for { a <- x; b <- y } yield (a,b)

编译器会抱怨flatMap不是M[A]的成员且map不是M[B]的成员。

具有for的东西是它有点编译魔术会接受任何实现名为mapflatMapwithFilter的函数的类型。这与Haskell相反,在Haskell中我们可以添加(或让编译器推断)Monad约束来让do符号起作用。

为了扩展@JoePallas给出的答案,可以使这项工作成功。实际上,以下实现实质上是GHC如何实现类型类。 catsscalaz库为您提供所有这些东西,但这就是香肠的制作方式:

首先定义我们需要的接口:

trait For[M[_]] {
    def map[A,B](ma: M[A], f: A => B): M[B]
    def flatMap[A,B](ma: M[A],f: A => M[B]): M[B]
    def withFilter[A](ma: M[A],q: A => Boolean): M[A]
}

(我使用名称For并使用与Monad略有不同的界面。)

然后,我们为我们想要支持的每种数据类型提供此特征的隐式实现。以下是Option的示例:

implicit val optionFor = new For[Option] {
  def map[A,B](ma: Option[A], f: A => B): Option[B] = ma.map(f)
  def flatMap[A,B](ma: Option[A],f: A => Option[B]): Option[B] = ma.flatMap(f)
  def withFilter[A](ma: Option[A],q: A => Boolean): Option[A] = ma.withFilter(q).map(a => a)
}

然后我们提供一个隐式转换为可以应用这些操作的类型:

implicit class ForOps[M[_], A](val ma: M[A]) extends AnyVal {
  def map[B](f: A => B)(implicit m: For[M]): M[B] = m.map(ma,f)
  def flatMap[B](f: A => M[B])(implicit m: For[M]): M[B] = m.flatMap(ma, f)
  def withFilter(q: A => Boolean)(implicit m: For[M]): M[A] = m.withFilter(ma,q)
}

最后,我们可以定义combine

def combine[M[_]: For, A, B](ma: M[A], mb: M[B]): M[(A, B)] =
  for { a <- ma; b <- mb } yield (a, b)

语法

def f[T: TC] = ???

是糖的

def f[T](implicit unutterableName: TC[T]) = ???

implicit参数列表(如果未在呼叫站点明确指定)将通过搜索具有正确类型的值/函数自动填写,只要它们本身是implicit。在这种情况下,我们寻找M是monad的证明。在正文中,此值为implicit,并且没有名称可以访问它。隐式搜索仍然可以找到它。 ForOps允许3个for操作使用Monad自动显示在值上。

这实际上是GHC如何实现类型类的明确版本。在最简单的情况下,没有优化:

class Applicative m => Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b

编译到

data Monad m = Monad {
  monadSubApplicative :: Applicative m
  return :: forall a. a -> m a
  (>>=) :: forall a. m a -> (a -> m b) -> m b
}

instance Monad [] where
  return = _
  (>>=) = _

变为

monadList :: Monad []
monadList = Monad {
    monadSubApplicative = applicativeList
  , return = _
  , (>>=) = _
}

你会经常听到&#34;词典&#34;用于描述基础数据类型和值。 combine

combine :: Monad m -> m a -> m b -> m (a, b)
combine (Monad _ return (>>=)) ma mb = ma >>= \a -> mb >>= \b -> return (a, b)

但是,GHC对系统应用了一系列限制,使其更具可预测性并执行更多优化。 Scala牺牲了这一点,以允许程序员执行更有趣的杂技。

为了更好的衡量,像这样的实例:

newtype Compose f g a = Compose { unCompose :: f (g a) }
instance (Functor f, Functor g) => Functor (Compose f g) where
  fmap f (Compose fga) = Compose $ fmap (fmap f) fga
在Scala中使用implicit def,而不是val,将会这样做:

trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] }
final case class Compose[F[_], G[_], A](val get: F[G[A]]) extends AnyVal
object Compose {
  // you usually put these implicits in the associated companions
  // because implicit search is picky about where it looks
  implicit def functor[F[_], G[_]](implicit
    functorF: Functor[F],
    functorG: Functor[G]
    // type lambda: use a type projection on a refinement type
    // to create an anonymous type-level function
    // it's universally accepted as a horrendous abuse of syntax
    // you can use the kind-projector plugin to avoid writing them (directly)
  ) : Functor[({type L[X] = Compose[F, G, X]})#L]
    = new Functor[({type L[X] = Compose[F, G, X]})#L] {
      override def map[A, B](cfga: Compose[F, G, A])(f: A => B): Compose[F, G, B] =
        Compose(functorF.map(cfga.get) { ga => functorG.map(ga)(f) })
    }
}

让所有这些东西都显而易见有点难看,但效果很好。

答案 2 :(得分:0)

在函数式编程(以及一般的编程)中,最好使用你能找到的最不强大的抽象。在你给出的例子中,你实际上并不需要monad的力量。组合功能是来自应用类型类的liftA2。例如:

import Data.Maybe
import Control.Applicative
z= Just 1
y= Just 2
liftA2 (,) z y
> Just (1,2)

在Scala中你有类似的东西。 Scalaz库中使用相同抽象的示例:

import scalaz._, Scalaz._
(Option(1) |@| Option(2))(Tuple2.apply)
> res3: Option[(Int, Int)] = Some((1, 2))

您不需要monad抽象的原因是值彼此独立。