In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly introduces "what is the role of Trait in Scala". In daily operation, I believe that many people have doubts about the role of Trait in Scala. The editor consulted all kinds of materials and sorted out simple and easy-to-use methods of operation. I hope it will be helpful for you to answer the question of "what is the role of Trait in Scala?" Next, please follow the editor to study!
Inside Scala-1:Partially applied functions
Partially applied function (function that is not fully applied) is a curry mechanism in scala. This article will describe the internal mechanism of partially applied function in scala through a simple example.
/ / Test3.scala package test object Test3 {def sum (x:Int, y:Int, z:Int) = x + y + z def main (args: Array [String]) {val sum1 = sum _ val sum2 = sum (1, _: Int, 3) println (sum1 (1, 2)) println (sum2 (2) List (1, 2) List (1, 2, 3) .foreach (println); List
In this code, sum _ represents a new function of type (Int,Int,Int) = > Int. In fact, Scala generates a new anonymous function (a function object, Function3), and the apply method of this function object calls the sum object method (in this case, a method, not a function).
Sum2 is a function (object) of Int = > Int, whose apply method calls the sum object method.
The next two lines of code need to access println, where println is a method defined in the Predef object, and in scala, a temporary function object is actually generated to wrap the call to the println method. If you study the code generated by scala, you can find that the code generated by println and println _ is duplicated in the code generated so far, which also shows that at present, all your anonymous functions are basically not checked for repeatability. (this may result in larger classes generated by compilation.)
From this, we can see that, although, at the grammatical level, the method (everything that comes out of the def) and the function seem to be the same, in fact, there is a difference between the two at the bottom, the method still can not directly locate, pass value, it is not an object. It is just an entity that is accessible to the underlying JVM. The function is an object at the virtual machine level. For any conversion from a method to a function, Scala automatically generates an anonymous function object to convert accordingly.
So, instead of getting a reference to println (in fact, there is no such accessible object as println at all) when List (println) executes at the bottom, scala automatically generates an anonymous function that calls println.
Of course, when you pass a function, Scala no longer makes unnecessary wrappers, but passes the function object directly.
Inside Scala-2: Curry Functions
Curry, which is common in functional languages, has special support in scala.
Package test
Object TestCurry {def sum (x:Int) (y:Int) (z:Int) = x + y + z def main (args: Array [String]) {val sum1: (Int = > Int = > Int) = sum (1) val sum12: Int = > Int = sum (1) (2) val sum123 = sum (1) (2) (3) println (sum1 (2) (3)) println (sum12 (3) println (sum123)}}
In this example, sum is designed as a curried function, (multi-level function? It is interesting to study the implementation of a function
If you look at the generated sum function code, it is similar to the following
Def sum (x:Int, y:Int: z:Int) = x + y + z is consistent.
And, if you call sum (1) (2) (3), in fact, scala will not produce three function calls, but one sum (1, 1, 2, 3).
That is, if you do not make sum (1), sum (1) (2), etc., then in fact, no additional function handling code will be generated in the above code at all. However, if we need to do some common curry operations, scala provides us with additional grammar-level convenience.
Inside Scala-3: How Trait works
Trait in Scala should be a very powerful but complicated concept. At least with me, I always have some ambiguities about trait. It is better to ask for help than to ask yourself. It is better to explore these questions yourself.
Let's start with a simple example.
Package test
Import java.awt.Point object TestTrait {trait Rectangular {def topLeft: Point def bottomRight: Point def left = topLeft.x def top = topLeft.y def right = bottomRight.x def bottom = bottomRight.y def width = right-left def height = bottom-top} class Rectangle (val topLeft: Point, val bottomRight: Point) extends Rectangular {override def toString = "I am a rectangle"}}
I would like to ask the following questions about this code:
How does Rectangle inherit the behaviors of Rectangular, such as left, right, width, height?
Rectangular corresponds to the interface of Java, so how is the relevant implementation code saved?
In fact, these two issues are related. The most direct way to study this problem is to directly analyze the compiled results of scalac.
This class is compiled to include:
The class TestTrait.class
TestTrait$.class is actually the class of the object object TestTrait. An object is actually subordinate to a class, and scala is suffixed with $.
In this example, the TestTrait object doesn't actually define new properties and methods, so it doesn't contain anything.
TestTrait$Rectangular.class
Corresponding to the trait of Rectangular in the code, this is actually an interface class. It corresponds to all the methods defined in this trait. Including the interface definition of topLeft, bottomRight and subsequent implementation methods such as left, width, etc.
Public interface test.TestTrait$Rectangular extends scala.ScalaObject {public abstract int height (); public abstract int width (); public abstract int bottom (); public abstract int right (); public abstract int top (); public abstract int left (); public abstract java.awt.Point bottomRight (); public abstract java.awt.Point topLeft ();}
TestTrait$Rectangular$class.class
This class is actually an implementation class of trait logic. Because interfaces in JVM do not support any implementation code, scala compiles the relevant logic code into this class
Public abstract class test.TestTrait$Rectangular$class extends java.lang.Object {public static void $init$ (test.TestTrait$Rectangular); / / in this example, there is no initialization related operation for trait: Code: 0: return public static int height (test.TestTrait$Rectangular); / / corresponding to the implementation of the operation height = bottom-top Code: 0: aload_0 1: invokeinterface # 17,1 / / InterfaceMethod test/TestTrait$Rectangular.bottom: () I 6: aload_0 7: invokeinterface # 20,1; / / InterfaceMethod test/TestTrait$Rectangular.top: () I 12: isub 13: ireturn
More methods are not listed here.
First of all, the implementation class is abstract and does not need to be instantiated.
All trait methods actually receive an extra parameter, the this object. Any access to an object, such as an operation like bottom, is actually a direct call to the corresponding operation of the object.
All trait methods are static.
TestTrait$Rectangle.class
This is the code for the class Rectangle.
/ / first, the implementation class inherits the interface defined by trait in the form of implements. The val attribute of the public class test.TestTrait$Rectangle extends java.lang.Object implements test.TestTrait$Rectangular,scala.ScalaObject {/ / class directly corresponds to a private field with the same name and the corresponding read method. What is special about the private final java.awt.Point bottomRight; private final java.awt.Point topLeft; / / scala object is that the corresponding fields are initialized earlier than calling the parent class constructor. That is, the parameters in Class (arg) are the first to be initialized. / / after the constructor, you can see that the initialization code of trait is called. Of course, in our example, trait does not have any initialization behavior. Public test.TestTrait$Rectangle (java.awt.Point, java.awt.Point); Code: 0: aload_0 1: aload_1 2: putfield # 13; / / Field topLeft:Ljava/awt/Point; 5: aload_0 6: aload_2 7: putfield # 15; / / Field bottomRight:Ljava/awt/Point; 10: aload_0 11: invokespecial # 20 / / Method java/lang/Object. "": () V 14: aload_0 15: invokestatic # 26; / / Method test/TestTrait$Rectangular$class.$init$: (Ltest/TestTrait$Rectangular;) V 18: return / / height is inherited from trait, where inheritance is embodied as a call to the trait implementation class, and the object itself is passed to the function public int height () as this Code: 0: aload_0 1: invokestatic # 39; / / Method test/TestTrait$Rectangular$class.height: (Ltest/TestTrait$Rectangular;) I 4: ireturn
Other function implementations are not listed here, which are basically consistent with the height function.
After understanding the above logic, it should be very clear how trait integrates the interface and the interface implementation. I've been wondering: an interface cannot contain implementation code, so how does the implementation code inherit in subclasses every time you compile a class that inherits from trait? Did the compiler make a copy of this logic? If so, not only does it generate a large amount of code, but there is also a problem, that is, the source code of trait is required at compile time. After the above analysis, we finally know that scala actually has a more * solution: that is a trait helper class.
Inside Scala-4: Trait Stacks
This example is taken from section 12.5 of Programming In Scala's book. This article will analyze the internal principle of Stackable Trait from another angle.
Package test
Import scala.collection.mutable.ArrayBuffer object Test7 {abstract class IntQueue {def put (x:Int) def get (): Int} class BasicIntQueue extends IntQueue {privateval buf = new ArrayBuffer [Int] def put (x:Int) {buf + = x} def get () = buf.remove (0)} trait Doubling extends IntQueue {abstract override def put (x:Int) {super.put (2* X)} def main (args: Array [String]) {val queue: IntQueue = new BasicIntQueue with Doubling queue.put (1) queue.put (5) println (queue.get) println (queue.get)}
Let's take a look at this line of code val queue = new BasicIntQue with Doubling,Scala has done a lot of work for this line of code, it is not as simple as a simple operation.
Scala needs to generate a new type. In my environment, this class is called: Test7 $$anon$1. Look at this code:
/ / the new class takes BasicIntQueue as the parent class and implements Doubling, an interface defined by trait.
Public final class test.Test7 $$anon$1 extends test.Test7 $BasicIntQueue implements test.Test7 $Doubling {public test.Test7 $$anon$1 (); Code: 0: aload_0 1: invokespecial # 10; / / Method test/Test7 $BasicIntQueue. "": () V / / parent initialization 4: aload_0 5: invokestatic # 16; / / Method test/Test7 $Doubling$class.$init$: (Ltest/Test7 $Doubling;) V / / trait helper class initialization 8: return public void put (int) Code: 0: aload_0 1: iload_1 2: invokestatic # 21; / / Method test/Test7 $Doubling$class.put: (Ltest/Test7 $Doubling;I) V / / this class uses version 5 provided by Doubling: return public final void test$Test7 $Doubling$$super$put (int); / / the version of super required by Doubling Code: 0: aload_0 1: iload_1 2: invokespecial # 29 / / Method test/Test7 $BasicIntQueue.put: (I) V 5: return}
Let's analyze the implementation of Doubling, the trait.
Public interface test.Test7 $Doubling extends scala.ScalaObject {public abstract void put (int); / / this is the method public abstract void test$Test7 $Doubling$$super$put (int) implemented in trait; / / this is the method that this trait is extra dependent on} / / Doubling this trait auxiliary class public abstract class test.Test7 $Doubling$class extends java.lang.Object {public static void $init$ (test.Test7 $Doubling); Code: 0: return public static void put (test.Test7 $Doubling, int) Code: 0: aload_0 1: iconst_2 2: iload_1 3: imul 4: invokeinterface # 17,2; / / InterfaceMethod test/Test7 $Doubling.test$Test7 $Doubling$$super$put: (I) V / / this is why the super.init method is needed in the Doubling interface. 9: return}
Thus, when dealing with the line of code val queue: IntQueue = new BasicIntQueue with Doubling, the compiler needs to determine the order of classes and Trait. This is also the most complex part of understanding Trait. In the future, I will analyze this problem.
Inside Scala-5: Trait Stacks
Continuing with the previous case, now let's make the chain of Trait a little longer:
Trait Incrementing extends IntQueue {abstract override def put (x: Int) {super.put (x + 1)}} trait Filtering extends IntQueue {abstract override def put (x: Int) {if (x > = 0) super.put (x)}} val queue: IntQueue = new BasicIntQueue with Incrementing with Filtering
What about the new class? What is the order of this when we call the put method of queue? Or take a look at the generated code:
Public final class test.Test7 $anon$1 extends test.Test7 $BasicIntQueue implements test.Test7 $Incrementing,test.Test7 $Filtering {/ / initialization order: first parent, then Incremeting, then Filtering, this order is consistent with the order of the source code. Public test.Test7 $$anon$1 (); Code: 0: aload_0 1: invokespecial # 10; / / Method test/Test7 $BasicIntQueue. "": () V 4: aload_0 5: invokestatic # 16; / / Method test/Test7 $Incrementing$class.$init$: (Ltest/Test7 $Incrementing;) V 8: aload_0 9: invokestatic # 21; / / Method test/Test7 $Filtering$class.$init$: (Ltest/Test7 $Filtering ) V 12: the return / / put method actually uses the put public void put (int) of Filtering, the Trait; Code: 0: aload_0 1: iload_1 2: invokestatic # 34; / / Method test/Test7 $Filtering$class.put: (Ltest/Test7 $Filtering;I) V 5: the parent implementation of return / / Filtering Trait is Incremeting trait public final void test$Test7 $Filtering$$super$put (int) Code: 0: aload_0 1: iload_1 2: invokestatic # 38; / / Method test/Test7 $Incrementing$class.put: (Ltest/Test7 $Incrementing;I) V 5: the parent implementation of return / / incrementing is the parent implementation. Public final void test$Test7 $Incrementing$$super$put (int); Code: 0: aload_0 1: iload_1 2: invokespecial # 26; / / Method test/Test7 $BasicIntQueue.put: (I) V 5: return}
So, to understand this process, analyze it this way: val queue: IntQueue = new BasicIntQueue with Incrementing with Filtering
The first initialization is BasicIntQueue
Overlaying Incrementing,super.put on this basis refers to BasicIntQueue's put method.
Then superimposing Filtering,super.put on the basis of superposition refers to Incrementing's put method.
The result of the superposition is the version of *. Put refers to the put method of Filtering
Therefore, the order of initialization is from left to right, while the visibility of the method is from right to left (it can be understood as the overlay relationship above, after which the trait above has greater priority visibility.
Inside Scala-6:Case Class and pattern matching
This article will try to analyze how Case Class participates in pattern matching. The code in this article still comes from the book Programming In Scala.
Abstract class Expr; case class Var (name: String) extends Expr; case class Number (num: Double) extends Expr; case class UnOp (operator:String, arg: Expr) extends Expr; case class BinOp (operator:String, left: Expr, right: Expr) extends Expr
Here, let's first look at the simplest pattern matching.
Some match {case Var (name) = > println ("a var with name:" + name)}
These lines of code are compiled to be equivalent to:
If (some instanceof Var) {Var temp21 = (Var) some; String name = temp21.name (); if (true) {name = temp22; Predef$.MODULE$.println ((new StringBuilder ()) .append ("a var with name:") .append (name). ToString ();} else {throw new MatchError (some.toString ()) Else {throw new MatchError (some.toString ());}
If you look at the generated code from a point of view, the quality of the code generated by Scala is not high, and that part of the if (true) else has obvious junk code. However, the impact on running efficiency can be almost ignored, but the compiled bytecode has no reason to be a little more.
The pattern match above matches only one type. Therefore, its corresponding java primitive is instanceof detection.
Let's take a step further and look at the following examples:
Some match {case Var ("x") = > println ("a var with name:x")}
This pattern match matches not only the type, but also the constant "x" of the name property in the constructor. Here I will not use the bytecode generated by Fuzhou Scala, but simply translate it:
If (some instanceof Var)-Type checking
Var.name () = "x"-checks whether the name property of the object is equal to "x", and the compiler clearly instructs the field name for each construction parameter of Case Class.
Further, let's look at a more complex pattern matching: nested objects.
Some match {case BinOp ("+", Var ("x"), UnOp ("-", Number (num) = > println ("x -" + num)}
This logic is actually a nesting above:
Some instanceof BinOp
The some.operator = "+" compiler performs special null detection to prevent NPE from appearing in this operation
Some.left instanceof Var
Some.left.name = = "x"
Some.right instanceof UnOp
Some.right.operator =-
Some.right.arg instanceof Number
.
In fact, Scala pattern matching does do a lot of things for us, which makes pattern matching using scala provide us with a very safe (without worrying about a lot of Null checking) and very complex matching operations in many cases. Of course, Scala's pattern matching is relatively simple compared to more complex pattern matching (for example, the rules engine is actually a pattern matching engine).
Here is a simple supplement to several modes in Scala:
1. Wildcard pattern. That means using case _ = > to match everything. Or, case Var (_) to make a local wildcard.
2. Constant matching. For example, Var ("x") above, where "x" is a constant. In addition to literal constants, constants can also use scala variables that begin with uppercase letters, or references in the form of `varname`.
3. Variable matching. A variable match matches virtually any type and is also given a variable name.
4. Constructor matching. Matches a given type and matches its parameters nested. Parameters can be wildcard patterns, constants, variables, or subconstructor matches
5. For the List type, _ * can match all remaining elements.
6. Tuple match. (a _ dint _ b _ c)
7. Type matching. For java objects, because it is not suitable for Scala's Case Class model, you can use types for matching. In this case, the match with the constructor is different.
Let's take a look at the use of pattern matching in commercial applications by taking a look at the logical code I wrote earlier to write an application using scala:
_ req.transType match {case RechargeEcp | RechargeGnete | FreezeToAvailable = > / / recharge transaction assert (_ req.amount > 0, "incorrect amount") case DirectPay | AvailableToFreeze = > / / payment, frozen transaction assert (_ req.amount)
< 0, "金额不正确") case _ =>Assert (false, "invalid transaction type")} val _ account = queryEwAccount (_ req.userId) assert (_ account! = null, "user has not opened an e-wallet") var _ accAvail, _ accFreeze: EWSubAccount = null var _ total: BigDecimal = _ req.amount _ account.subAccounts.find (_ .subTypeCode = = Available) match {case Some (x) = > _ accAvail = x _ total + = x.balance case None= >} _ account.subAccounts.find (_ .subTypeCode = = Freeze) match {case Some (x) = > _ accFreeze = x; _ total + = x.balance case None= >}
This is just a very simple application, imagine using Java's if/else or switch to do the same code, you might as well see how much code will increase? What about readability?
Scala Actor is a concurrent programming mode that draws lessons from the process message mechanism of Erlang. Because the process concept of Erlang does not exist in Java, the Actor of Scala is not as isolated as Erlang. For example, in Erlang, it can effectively terminate a process, not only need not worry about deadlock (there is no lock at all), but also immediately release the memory of the modified process. To some extent, this isolation is closer to the process of the operating system. There is no equivalent alternative in the world of Java.
Beside the point, recently, we have integrated a mechanism similar to operating system timing scheduling in our Open Service Platform, which can execute some tasks regularly, but *, we still decided to put some non-transaction related scheduled tasks, mainly some log analysis classes, administrative batch processing and other scheduled tasks, on the operating system for scheduling. After all, the operating system provides a better virtual machine. There is still limited isolation at the OSGi level, and the operating system really doesn't matter when JVM can provide isolation features like the operating system.
This article will briefly analyze the mechanism of actor to help enhance the understanding of actor.
Package learn.actor object Test1 extends Application {import scala.actors.Actor._ val actor1 = actor {println ("i am in" + Thread.currentThread) while (true) {receive {case msg = > println ("recieve msg:" + msg + "In" + Thread.currentThread) } val actor2 = actor {println ("i am in" + Thread.currentThread) while (true) {receive {case msg: String = > println ("recieve msg:" + msg.toUpperCase + "In" + Thread.currentThread);} actor1! "Hello World" actor2! "Hello World" actor1! "ok" actor2! "ok"}
The result of the operation is:
I am in Threadpool-1 i am in ThreadMain] recieve msg:HELLO WORLD In ThreadMain] recieve msg:Hello World In ThreadThread [pool-1Muthreadly2Magazine 5Magazine] recieve msg:Hello World In ThreadThread [pool-1Kui threadame1Main5Main] recieve msg:OK In ThreadThread [pool-1Muthreadly2Main5Main] recieve msg:ok In Thread5MainThreadThread [pool-1threadly2main]
From this example, actor1 and actor2 are actually two separate Java threads, and any thread can send messages to! Is sent to this thread for processing. Because messages are used to communicate, there is no need to use Java's notify/wait mechanism between threads, which is based on locks. With regard to this point, I will not only conduct an in-depth analysis in this article. If necessary, I will write another post to explain it.
So what is the underlying foundation of Scala Actor? Has nothing to do with Java's notify/wait? We will focus on the three methods of actor:!, receive, react
1. Scala Actor's send (the external caller sends a message to the current actor) and receive (the current actor receives a message). The two operations are synchronized, that is, they cannot be entered at the same time. Objectively speaking, there should be a lot of room for optimization in this area, and the optimistic lock mechanism should be adopted, which may be more efficient. On the one hand, the send/receive operation itself is a very fast operation, even in the case of conflict, the use of optimistic lock can reduce the overhead caused by thread switching, and, in most cases, the possibility of conflict between send operation and receive operation is not very high. That is, to a large extent, send and receive can also have better parallelism, and it is not known whether subsequent versions of scala will be optimized. )
2. When performing the send operation, if the current actor is waiting for the message (meaning that the actor itself is already in receive and react and expecting the message), then the original wait will be executed immediately, otherwise, the message will enter the actor mailbox and wait for the next receive/react processing. This mode is more effective than putting it all in the mailbox. It avoids a synchronization wait on the mailbox.
3. When performing the receive operation, actor will check the mailbox of the object, and if there is a matching message, it will immediately return the message for processing, otherwise it will be in a waiting state (the current thread is blocked, using the wait primitive) when the matching message arrives, it will also use the notify primitive to notify the waiting thread to continue actor processing.
4. React, unlike receive, react never returns. In the programming world of Java, I don't seem to have seen anything like it yet, so how to understand it:
React (f: ParticialFunction [Any,Unit]) first checks the mailbox of actor, extracts the message immediately if there is a message that matches f, and schedules the execution of f in an ExecutionPool. Therefore, the execution of f is certainly not performed in the thread requesting react. The current thread calling react will generate a SuspendActorException, interrupting the normal execution process. (that is, the concept of non-return in the document)
If there is no message in the current mailbox, react registers a Continuation object, registers the waiting message (a function waiting for a given message) and the processing that needs to continue after getting the message in actor, and then the current thread generates a SuspendActorException that interrupts the processing (thus returning the current thread to the thread pool).
When the message arrives (through the send), send checks the Continuation waiting for the message and, if it matches, selects a thread in the thread pool to execute the f function. After f has finished processing a message, normally it will call react again to process the next message, and the process will be repeated again.
It should be said that the design of scala is very ingenious and effective, but it means a new challenge for Java development programmers: what appears to be a function body, in fact, the code in it is not only executed discontinuously (such as closure may be delayed and repeatedly called many times), but may even be executed in different threads.
From this conceptual point of view, the actor of scala does not correspond to the thread of Java, on the contrary, it can be understood as a performer, a non-operating system thread with context, and the semantics is actually closer to a carrier of reality. There are obvious semantic differences between this and the Erlang process. From the above analysis, perhaps if we switch to the optimistic locking mechanism, the concurrency efficiency of Scala can be further improved.
At this point, the study on "what is the role of Trait in Scala" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.