簡體   English   中英

接受兩個monadic值並返回單個monadic值的通用函數

[英]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是,它是一些編譯器魔法,將接受任何實現稱為mapflatMapwithFilter函數的類型。 這是相對於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 = _
  , (>>=) = _
}

您經常會聽到“字典”這個詞用於描述基礎數據類型和值。 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM