[英]Generic function that accepts two monadic values and returns a single monadic value
我編寫了下面的Haskell函數,它接受兩個monadic值並將它們組合成一個monadic值(它只是為了說明Haskell類型系統可以支持的通用性(或泛型)的程度)。
combine x y = do
a <- x
b <- y
return (a, b)
我用三種不同的單子測試了它:
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
它按預期工作得很好 。 現在,我想知道Scala的類型系統是否允許我在不影響通用性的情況下編寫上述函數。 但我不太了解斯卡拉(雖然我希望探索它)。 那么有人可以幫助我將此代碼轉換為Scala嗎?
我認為這相當於:
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])
修復了這個問題。
您的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中我們可以在其中添加(或讓編譯器推斷)一個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 = _
, (>>=) = _
}
您經常會聽到“字典”這個詞用於描述基礎數據類型和值。 並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) })
}
}
讓所有這些東西顯而易見有點難看,但它運作得很好。
在函數式編程(以及一般的編程)中,使用您可以找到的最不強大的抽象是一種好習慣。 在你給出的例子中,你實際上並不需要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抽象的原因是這些值是相互獨立的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.