我编写了下面的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吗?
答案 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
的东西是它有点编译魔术会接受任何实现名为map
,flatMap
和withFilter
的函数的类型。这与Haskell相反,在Haskell中我们可以添加(或让编译器推断)Monad
约束来让do
符号起作用。
为了扩展@JoePallas给出的答案,可以使这项工作成功。实际上,以下实现实质上是GHC如何实现类型类。 cats
和scalaz
库为您提供所有这些东西,但这就是香肠的制作方式:
首先定义我们需要的接口:
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抽象的原因是值彼此独立。