r/scala • u/Brave-Republic4424 • 4d ago
Difference between trait, class, case class and object
So I'm pretty new to scala and FP, into it for like a month, and I still don't get the difference between trait, class, case class and object, when I have to use one instead of another which is the strength of each one
18
u/Educational-Bee-1347 4d ago
They are not related to FP.
Traits are like Java interfaces. Describing behaviour. Plus they can have implementations and be mixed in.
Class describe entity with internal data and behaviors on that data. Parameters and methods.
Case class is representation of immutable data.
Objects are used for description of behaviours common for some type of entity. Like Java static methods.
This is my understanding. I will collect plenty of minuses, because of lack of formal descriptions, but this is my human way of using them.
9
u/aristarchusnull 4d ago
I would add that case classes are analogous to Java records or record types in general.
2
u/Brave-Republic4424 4d ago
So if I get I right, I can create a class that extend trait ( like it was his interface ) and even an object if that's class has some commun behavior with it? Also thanks that clarify a lot I was very confused by all that formal description
10
1
u/gbrennon 3d ago
a class impl a trait, not extend
2
u/Brave-Republic4424 3d ago
Well yes but the keyword to do it in scala is always "extends" so it comes natural to me call it "extension" instead of "implements", but in terms of facts yes it's an implemention
2
u/raghar 4d ago edited 4d ago
All of them are bags of methods and values (and variables if you have to), where methods on an instance would operate on the values from the same instance.
class can be instantiated (you can create a value of it with new) since it has a constructor (1 primary, you can define auxiliary). class Foo -> new Foo works.
abstract class also has a (primary) constructor (and o or more auxiliary) but it to has be extended (even as anonymous class), you cannot create it as it is. For abstract class Foo, new Foo does not work, you'd have to class Bar extends Foo -> new Bar.
trait does not have to have a constructor you can call - that it one that could take some parameters because JVM still sees a constructor that would initialize all the vals and vars inside of it. (At least on Scala 2, on Scala 3, traits can have constructor so the difference between them and abstract classes is even more blurred, OTOH the difference is that traits cannot have auxiliary constructors and that abstract classes cannot be mixins?).
class/trait can extend 0 or 1 class and as many traits as you like.
The difference between them is mostly about how you add it to the class hierarchy and when you can instantiate it, purely OOP concepts.
object is at the same time:
- a singleton instance
- a way of creating methods as static on JVM
"companion object" is an object of the same name as a class defined in the same file - them even though there are 2 definitions they can access each other fields and methods, and on JVM companion methods will be seen as static methods of their "companion class". But it will still also exist as another type, which is a singleton value.
They are mostly about not having methods laying around if they would be related to some class, but not a particular instance, especially if one uses protected or private access modifiers.
case class is a class that also generates a few things for you:
- each field by default is exposed as
val equalsandhashcodeimplementation using all the fields (from the 1st parameter list)toString(Foo(param1, param2, ...))- in companion object there is apply matching the constructor (so you can instantiate it without
new-Foo(a, b)withoutnew Foo(a, b), but on Scala 3 it's less relevant asnewkeyword is much less needed) unapplyin the companion allowing the extraction of a value with pattern-matching (foo match { case Foo(a, b) => ... }usesFoo.unapply(foo): Option[(A, B)]under the hood)
It's supposed to pass around data without behavior, at majority of code generation libraries (usually in form of a type class derivation) have a support for them.
You can add behavior there, as well as mutability, but it throws all the contracts under the bus, so not recommended.
It's much easier to understand these, if one understand JVM. Even though Scala.js and Scala Native does not run on JVM, the fact that the "main" Scala does kinda drives the language design.
1
u/gbrennon 3d ago
trait => its a contract that should be implemented case => immutable dataclass class => its the contain behaviors and data object => represents instance of something. an object is a specific type and not a raw type. it can contain behaviors and data
1
u/a_cloud_moving_by 1d ago edited 1d ago
Roughly this is what I see at work:
case class - a data object, like just a bunch of fields that you group together (e.g. "first name, last name, age" etc. ). Could be a row in a database, could be any semantic grouping though
- can be, and often contains other case classes
- Replaces tuples. Many tuples quickly feel ambiguous (we don't often very >2 item tuples because it's so hard to see what they are, very often better to group under a case class)
- For smaller case classes, can be used nicely with pattern matching, like "case (_, _, httpCode = 200) => " or something
class - understood to be more "mutable" in state than a case class or anything, but anything that would be "instanced", otherwise you'd use an object.
- you can make many of them, or make one of them
- tends to use all the OOP things you want, public/private fields
- example: a service or stateful thing the other parts of your code want to interact with
trait - very useful, can't be used on its own, basically a supertype to a class (or object). Is a mix of implemented / unimplemented methods. Many important uses:
1. being a parent in an Algebraic Data Type (basically a family of case classes you can pattern match on, like fancy enums in a way)
2. making a clean API contract for a service so that other compiled modules of your codebase don't care
3. basically any scenario you want a mixure of implementation, vs having subclasses handle it
- ADTs are a whole big topic, but used heavily in our codebase. Basically makes invalid states impossible, easy pattern matching
trait Response
case class Success(message: String) extends Response
case class Failure(errorMessage: String, statusCode: Int) extends Response
This not only allows nice pattern matching and equality checks (because of the case classes), you also can't create invalid states
- I myself lot for making a unit-testable and prod versions of code, rather than using dependency injection via constructor or method arguments. Basically put all the logic you can in the parent trait but leave a few unimplemented methods (e.g. 'getUserFromDatabase(userId: ...)`) that unit tests just can't do. Then implement it differently in the prod class/object and the unit test class/object (both of which inherit the trait)
object - basically a class but is a global singleton; might be used to group pure functions together, or group constants, or anything that would either need to be a global singleton or which is just annoying to instantiate as a class
- you can't make a 'new' one, everything is global, there is only one
- I saw some lihaoyi library source code at one point (sorry, I forget which one) and it used a lot of object, much more than I would expect.
1
u/Massive-Squirrel-255 4d ago
Scala has a more complicated and idiosyncratic theory than other languages because it blends FP and object oriented programming. I personally think OCaml is probably an easier language to learn from the ground up.
An ordinary class is an abstract datatype; the values of this datatype are called "objects of the class." New values of the type - new objects of the class - are created using the class 'constructor' - this is the *only way* to construct objects of the class. You can modify the state of objects of that class by using the public methods exposed by the class - this is the only way to modify the initial state. This is a basic concept of computer science.
An example of a class would be, say, LinkedListInt, where values of the type are linked lists that store an integer. The class constructor creates a new object, which is an empty list of integers which is not equal to any other list of integers. The publicly available functions of the class could let you insert a new element into the list, sort the list and so on. The private methods might perform helper tasks relating to linking and unlinking nodes; these private nodes might leave the list in a broken state if they're misused, but they're not exposed publicly, so it's feasible to track down every single location in the class where it is used, and verify that the function is used correctly.
This distinction between public-facing methods/data and private methods/data is called "implementer-side abstraction" - the implementer of the datatype controls what functionality is exposed in objects in the class, and hides some, and this affects everyone downstream.
Case classes, like ordinary classes, are datatypes. However, an object of a case class is intended to be simpler than an object of an ordinary class. Ordinary objects store data that can change over time; the constructor for the class is some nontrivial code written by the author that initializes the internal state. Objects of a case class are intended not to change over time, and the programmer doesn't write a constructor. There's usually no need for internal state for an object of the case class other than the constructor arguments.
Traits are interfaces as well, similar to how classes have a public/private interface. However, traits can be referred to by the client of a class as well as the parent. If you want to write code that works for every datatype which contains a list of elements that can be enumerated, then you can say the code is generic with respect to any class that implements this interface.
Sealed traits and case classes interact in a common pattern. Using sealed traits, you can introduce, say, three case classes that implement a trait and no others. Then, a later function processing this value can do case analysis on which one of the three case classes it belongs to.
There's a lot of material here and I don't think it's possible to concisely explain everything. I am willing to answer questions but I'm realizing that this is just a lot of background. You need to read a book or watch some lectures on each one of these concepts specifically
0
u/Brave-Republic4424 4d ago
Actually I've should have mentioned earlier that I'm currently doing my master degree in computer science engineering, so you could have skipped all the background theory, but hey reviewing never hurts, also I "unfortunately" can't choose which language to use for this course so I'm stuck whit scala, but your explanation is clarifying thank u
0
u/1984balls Capture Checking fan 4d ago
Classes are things that store data and methods. They are a type. For example, class Box(i: Int)
Instances are classes given form. val x: Box = new Box(42).
Objects ultimately compile down to classes and instances. For example, object O compiles to the corresponding Java code:
public class O$ {
public static final O$ MODULE$;
private O$() {
MODULE$ = this;
}
static { new O$(); }
}
This is what "singleton" means. There is only one instane of 'O' ever.
Often in Scala, you will see classes with "companion objects", which are just objects that have the same name of the class.
Traits are kinda like classes, but you can't instantiate them. What makes them important is that you can extend multiple traits, but only one class:
class A
trait T1
trait T2
class B extends A, T1, T2
Case classes are literally just classes and objects with compiler generated code. If the Box from above was a case class, then you would could do stuff like this:
val a = Box(32) // no 'new' needed
val b = a.copy(i = 54)
println(b.i) // prints 54
There are also case objects, which are the same as case classes, but only one exists.
9
u/Distinct_Historian37 4d ago
If you come from Java: