我有一个特点:
trait Crawler {
implicit def system: ActorSystem
implicit def executionContext: ExecutionContext
implicit def materializer: Materializer
// other methods
}
还有一个测试类:
class CrawlerSpec extends AsyncFlatSpec with Matchers with Crawler {
override implicit val system: ActorSystem = ActorSystem("ufo-sightings")
override implicit val executionContext: ExecutionContext = implicitly[ExecutionContext]
override implicit val materializer: Materializer = ActorMaterializer()
// test
}
根据Scalatest doc:
异步样式特征扩展了AsyncTestSuite,它提供了一个 隐式scala.concurrent.ExecutionContext,名为executionContext。
但由于ExecutionContext
为空(由于java.lang.NullPointerException was thrown.
java.lang.NullPointerException
at scala.concurrent.impl.Future$.apply(Future.scala:31)
at scala.concurrent.Future$.apply(Future.scala:494)
无效,因此测试会爆发,但这是另一回事。)
ExecutionContext
为什么隐含的<rant>
Implicit resolution is a nightmare. At the expense of saving a few
keystrokes, it makes code so fragile that removal of a single
import breaks it. There's a reason other statically typed languages like
Haskell or Kotlin don't have it; it's a stinking mess.
</rant>
没有被选中?
//Polling response from URL
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader resStream = new StreamReader(response.GetResponseStream());
//Parsing response to separate out each donation
JObject ParsedJson = JObject.Parse(resStream.ReadToEnd());
var TransactionArray = ParsedJson["donations"].Children();
//Array to store parsed results into usable format
List<DonationsJSON> TransArray = new List<DonationsJSON> { };
foreach (object Transaction in TransactionArray)
{
// clean up parsed string and remove Anonymous ID string
string DonationString = Transaction.ToString().Replace("\r\n", "");
DonationString = DonationString.Substring(DonationString.IndexOf(':') + 1);
// Deserializing string into Donation class and adding to List
DonationsJSON TX = JsonConvert.DeserializeObject<DonationsJSON>(DonationString);
TransArray.Add(TX);
}
答案 0 :(得分:4)
让我们看看这里发生了什么:
trait A {
implicit def executionContext: ExecutionContext
}
您在此声明A
将提供隐含价值。然后
class B extends A {
override implicit val executionContext: ExecutionContext = implicitly[ExecutionContext]
}
那么这里发生了什么?
executionContext
在B
构建期间初始化。implicitly
尝试查找ExecutionContext
类型的值。executionContext
。如此有效地你做了类似的事情:
class B extends A {
val executionContext: ExecutionContext = executionContext
}
您在初始化时创建了循环依赖:您正在使用自身初始化值。所以你通过“getter”获取一个值,返回一个仍然是null
的属性(因为它刚刚被初始化)。
我同意,暗示概念是需要付出很多努力的东西,尽管我不会像你那样对抗它们。这里有一个循环依赖初始化的问题。它不能优雅地失败,除Exception
之外的任何东西都会使程序进入无效状态。
解决方案是在不使用implicit
的情况下初始化implicitly
值。只需手工放一些价值。或者不要使用该特征并从其他地方隐式导入。