2017-08-31 35 views
3

根據樣式指南 - 是否存在一個經驗法則?Scala中的類別類型應該使用什麼規則?context boundimplicit ev表示法?是否在Scala中使用上下文綁定或隱式ev

這兩個例子做結合的相同

上下文具有更簡潔函數簽名,但需要val評價與implicitly呼叫:

def empty[T: Monoid, M[_] : Monad]: M[T] = { 
    val M = implicitly[Monad[M]] 
    val T = implicitly[Monoid[T]] 
    M.point(T.zero) 
} 

implicit ev方法自動插入類型類成函數參數,但污染方法簽名:

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = { 
    M.point(T.zero) 
} 

大部分的圖書館我che cked(例如"com.typesafe.play" %% "play-json" % "2.6.2")使用implicit ev

你在使用,爲什麼?

回答

2

這是基於非常的意見,但一個原因之實踐的使用隱含參數列表直接爲您執行更少的搜索隱。

當你

def empty[T: Monoid, M[_] : Monad]: M[T] = { 
    val M = implicitly[Monad[M]] 
    val T = implicitly[Monoid[T]] 
    M.point(T.zero) 
} 

這個被編譯器脫入

def empty[T, M[_]](implicit ev1: Monoid[T], ev2: Monad[M]): M[T] = { 
    val M = implicitly[Monad[M]] 
    val T = implicitly[Monoid[T]] 
    M.point(T.zero) 
} 

所以現在implicitly方法需要做另一個隱含搜索查找範圍ev1ev2

這不太可能,這具有顯着的運行時開銷,但它可能會影響在某些情況下,你編譯時的性能。

相反,如果你做

def empty[T, M[_]](implicit T: Monoid[T], M: Monad[M]): M[T] = 
    M.point(T.zero) 

你直接訪問從第一隱含搜索MT

同時,(這是我個人的看法)我喜歡身體更短,在簽名一些樣板的價格。

我知道大多數圖書館,使隱含參數的大量使用使用這種風格時,他們需要訪問實例,所以我想我只是變得更加熟悉的符號。


獎金,如果你決定反正綁定的情況下,它通常是一個好主意,以提供對搜索隱式實例的類型類的apply方法。這允許你寫在這裏這種技術

def empty[T: Monoid, M[_]: Monad]: M[T] = { 
    Monad[M].point(Monoid[T].zero) 
} 

更多信息:https://blog.buildo.io/elegant-retrieval-of-type-class-instances-in-scala-32a524bbd0a7

+0

我完全同意,但我想補充一個小精度:隱式搜索是通過執行編譯器在編譯時,所以絕對沒有運行時性能的影響。結論:這實際上就像你說「基於意見」一樣。 –

+0

沒有運行時性能影響,但編譯時性能在scala代碼庫中有點問題,所以我總是傾向於留意它;)也可能有一點運行時間的開銷,因爲你正在做一個額外的方法調用(隱含''),但是JVM可能會嵌入那個 –

+0

謝謝!用'apply'方法真的很好,檢查了文章。雖然沒有在'scalaz'中找到,但是 –

1

注意,在做同樣的,你的2個例子相同的頂部。上下文邊界只是用於添加隱式參數的語法糖。

我是機會主義者,儘可能使用上下文綁定,即當我沒有隱式函數參數時。當我已經有一些,不可能使用上下文綁定,我沒有其他選擇,只是添加到隱式參數列表。

注意,你不需要定義val S作爲你這樣做,這只是罰款(但我認爲你應該去什麼使代碼更易於閱讀):

def empty[T: Monoid, M[_] : Monad]: M[T] = { 
    implicitly[Monad[M]].point(implicitly[Monoid[T]].zero) 
} 
+0

謝謝,我明白它是如何工作的,問題只是關於代碼風格。這是一個口味的問題,我相信,但想與社區檢查:) –

3

一個警告你當使用implicitly時,需要注意使用依賴類型的函數。我會引用這本書「宇航員指導不成形」。它着眼於從無形的Last型類檢索的最後一個類型的HList的:

package shapeless.ops.hlist 

trait Last[L <: HList] { 
    type Out 
    def apply(in: L): Out 
} 

,並說:

的隱式地從scala.Predef方法有這種行爲(這 行爲意味着失去內部類型成員信息)。在 類型最後的實例與含蓄召見比較:

implicitly[Last[String :: Int :: HNil]] 
res6: shapeless.ops.hlist.Last[shapeless.::[String,shapeless 
     .::[Int,shapeless.HNil]]] = [email protected] 

與Last.apply召喚一個實例的類型:

Last[String :: Int :: HNil] 
res7: shapeless.ops.hlist.Last[shapeless.::[String,shapeless 
     .::[Int,shapeless.HNil]]]{type Out = Int} = [email protected] 

類型傳喚通過隱含沒有Out類型成員,這是一個重要的警告,並且通常爲什麼你會使用不使用上下文邊界和implicitly的召喚者模式。


除此之外,通常我會發現它是一個風格問題。是的,implicitly可能會稍微增加編譯時間,但是如果您有一個隱含的豐富應用程序,那麼在編譯時您很可能不會「感覺到」兩者之間的差異。

而在更個人的筆記中,有時寫implicitly[M[T]]會感覺「醜陋」而不是使方法簽名更長一些,並且當您使用命名字段顯式聲明隱式時,讀者可能會更清楚。

+0

謝謝!將檢查這本書,不知道這個警告 –

+0

@EugeneZhulkov偉大的書一般爲類型級別編程和無形的介紹。 –

1

FP庫通常給你的語法擴展類型類:

import scalaz._, Scalaz._ 
def empty[T: Monoid, M[_]: Monad]: M[T] = mzero[T].point[M] 

我用這種風格儘可能地。這給我的語法使用標準庫的方法一致,也讓我寫/ for -comprehensions超過通用Functor小號Monad小號


如果不可能,我的同伴對象上使用特殊apply

import cats._, implicits._ // no mzero in cats 
def empty[T: Monoid, M[_]: Monad]: M[T] = Monoid[T].empty.pure[M] 

我用simulacrum爲我自己的類型類提供了這些。


我訴諸implicit ev語法情況下,勢必方面是不夠的(如多個類型參數)

+0

哦,謝謝你!完全忘了'mzero [T] .point [M]'。但我的問題更籠統,不僅關於fp庫,還關於所有類型類。 –

+0

@EugeneZhulkov,是的,我描述了我的偏好,沒有風格指南。很多庫實際上提供了語法豐富(例如'spire'中的運算符)或具有隱式參數的常規方法(例如'play-json'中的'Json.toJson(instance)')允許您使用上下文邊界,所以函數定義並且實現簡短且可讀。對於自定義的,它是關於添加一個單一的註釋特性。 –

相關問題