Modelling data with swift enums - swift

This is more of a software design than a coding question, but then isn't that always the actual hard problem ? Anyway, I'm trying to model Minecraft's NBT Format in order to read / write its save files.
I'm pretty sure using enums is the best choice here : There are a limited number of cases, which shouldn't be alterable by the user, and all need to be dealt with. Also, as you will see below, one problematic case is very hard to model any other way.
However, NBT data can be nested, of course, and can also contain lists of any single NBT type. That includes lists of lists, where things like [[Int],[Double]] are legal (the contained types are both lists, NBT doesn't enforce anything past that point). Basically, a list can only contain a single tag type, but lists of anything are all considered the same type.
And yes, the duplicate way of storing some types of arrays is part of the spec. A [Int8] can be stored either as byteArray or List<Byte>, that much is expected. Hence also the previous use of the word List instead of Array : those are two different but similar things in NBT.
I have coded this for now, which works perfectly well, but I fear probably has some glaring design flaws I'm not experienced enough to notice :
public enum Tag : Equatable {
case end
case byte(Int8)
case short(Int16)
case int(Int32)
case long(Int64)
case float(Float)
case double(Double)
case byteArray([Int8])
case string(String)
case list(TagList)
case compound([String : Tag])
case intArray([Int32])
case longArray([Int64])
}
public enum TagList : Equatable {
case end
case byte([Int8])
case short([Int16])
case int([Int32])
case long([Int64])
case float([Float])
case double([Double])
case byteArray([[Int8]])
case string([String])
case list([List])
case compound([[String : Tag]])
case intArray([[Int32]])
case longArray([[Int64]])
}
Is there a saner way to do this ? I'm really not a fan of repeating basically the same enum cases twice, but because of the Tag.List.list case they can't be merged (because for there to be a list of list, lists must be defined apart from the rest).

I don't know much about the domain, but can you do something like:
public enum TagStyle<T> {
.value(T)
.list([T])
}
public enum Tag : Equatable {
case end
case byte(TagStyle<Int8>)
case short(TagStyle<Int16>)
case int(TagStyle<Int32>)
case long(TagStyle<Int64>)
case float(TagStyle<Float>)
case double(TagStyle<Double>)
case byteArray(TagStyle<Data>)
case string(TagStyle<String>)
case list(TagStyle<List>)
case compound(TagStyle<[String : Tag]>)
case intArray(TagStyle<[Int32]>)
case longArray(TagStyle<[Int64]>)
}

Related

Scala match case with multiple branch with if

I have a match case with if and the expression is always the same.
I put some pseudo code:
value match {
case A => same expression
case B(_) if condition1 => same expression
case _ if condition2 => same expression
...
case _ => different expression //similar to an else
}
The match contains both case object (case A) matching and case class(case B(_))
Is it the best practice?
Try to explain this code in words. "This function returns one of two values. The first is returned if the input is A. Or if the input is of type B and a condition holds. Oh, or if a different condition holds. Otherwise, it's the other value". That sounds incredibly complex to me.
I have to recommend breaking this down at least a bit. At minimum, you've got two target expressions, and which one is chosen depends on some predicate of value. That sounds like a Boolean to me. Assuming value is of some trait type Foo (which A.type and B extend), you could write
sealed trait Foo {
def isFrobnicated: Boolean = this match {
case A => true
case B(_) if condition1 => true
case _ => condition2
}
}
...
if (value.isFrobnicated) {
same expression
} else {
different expression
}
Now the cognitive load is split into two different, smaller chunks of code to digest, and presumably isFrobnicated will be given a self-documenting name and a chunk of comments explaining why this distinction is important. Anyone reading the bottom snippet can simply understand "Oh, there's two options, based on the frobnication status", and if they want more details, there's some lovely prose they can go read in the isFrobnicated docs. And all of the complexity of "A or B if this or anything if that" is thrown into its own function, separate from everything else.
If A and B don't have a common supertype that you control, then you can always write a standalone function, an implicit class, or (if you're in Scala 3) a proper extension method. Take your pick.
Depending on your actual use case, there may be more that can be done, but this should be a start.

What is the meaning of the word "case" in scala's "case class"?

I understand that case class causes the compiler to augment a class with boilerplate to implement a certain useful pattern ("plain and immutable data-holding objects that should exclusively depend on their constructor arguments").
But the word "case" itself has no meaning to me in this context. I'm accustomed to its use as part of switch statements in C#, but that seems to have no relevance to Scala's use of the word.
I find it easier to program when I can attach words to particular meanings. Right now my mental model is case => boilerplate, in the same way that it could be blurg => boilerplate. It's a working mental model, but ambiguity makes it easy to misunderstand or to forget altogether.
So what does the word case have to do with what the compiler actually does?
I'm not looking for "what was in the mind of the designers" but rather "what's a rational way to relate this term to its meaning, given general knowledge of the language's design."
In my opinion, the term case comes from case analysis which is a reasoning technique enabled by special structures called algebraic data types. By itself case in case class might not make much sense, but when it forms a part of a sealed structure, which is how Scala defines ADTs, for example
sealed trait Number
case object Zero extends Number
case class Succ(v: Number) extends Number
then we see there are two forms of constructing Numbers, namely using Zero and Succ constructors. Hence whenever we have to think about Numbers, we at least know there are two different cases to consider. For example, say we want to define addition on Numbers, then its definition will have to handle two cases, perhaps, like so
def sum(a: Number, b: Number): Number =
a match {
case Zero => b
case Succ(v) => Succ(sum(v, b))
}
where
sum(Succ(Zero), Succ(Zero)) == Succ(Succ(Zero)) // 1 + 1 = 2
sum(Succ(Succ(Zero)), Succ(Zero)) == Succ(Succ(Succ(Zero))) // 2 + 1 = 3
sum(Succ(Zero), Succ(Succ(Zero))) == Succ(Succ(Succ(Zero))) // 1 + 2 = 3
sum(Zero, Succ(Succ(Zero))) == Succ(Succ(Zero)) // 0 + 2 = 2
sum(Succ(Succ(Zero)), Zero) == Succ(Succ(Zero)) // 2 + 0 = 2
Note how Scala in order to define ADT uses terms like class, object, trait etc., which appear to be from the object-oriented paradigm, however ADTs conceptually have little in common with class hierarchies found in OO. Personally I find this confusing, but we must remember Scala is both functional and OO language, which might be a reason for such terminological spillover. In some other more "pure" languages case class of ADT is represented simply by a vertical bar |, say like so
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
My suggestion would be to try not to be a "slave to words" but instead words should serve you. What is important is the meaning behind the words or terms, not the words themselves. Do not build mental models around the terms, instead build mental models around the heavy concepts they are struggling to carry across feeble bridges of communication. In my opinion, the concept case is trying to communicate is that of ADT.
C# has a switch / case language feature which allows controlling program flow by matching an input to a set of possible values.
public static GetEmail(string name)
{
switch (name)
{
case "bill":
return "bill#example.com";
case "jane":
return "jane#example.com";
default:
return null;
}
}
Here, case roughly means "in the case that the given value is equal to this one, do X."
Scala's match / case feature is sort of like C#'s feature.
def getEmail(name: String): Option[String] = {
name match {
case "bill" => Option("bill#example.com")
case "jane" => Option("jane#example.com")
case _ => None
}
}
But it's much more powerful. It is designed to evaluate "matches" against things farm more complex than strings. To take advantage of this powerful matching feature, you define immutable data-holding classes with case class.
Here is a trivial, but hopefully helpful, example of a case class and its use with match / case:
case class Person(name: String, hasEmail: Boolean)
object EmailHelper {
def getEmail(person: Person): Option[String] = {
person match {
case Person(_, false) => None
case Person("bill", true) => Option("bill#example.com")
case Person("jane", true) => Option("jane#example.com")
case _ => None
}
}
}
In short, case class enforces a set of constraints which make a class usable with match / case.

Implicits over type inference for object transformation

Part of a current project involves converting from types coupled to a database and a generic type used when serializing the results out to clients via Json, the current implementation in Scala uses type inference to correctly perform the transformation, using Scala's TypeTag:
def Transform[A: TypeTag](objects:Seq[A]):Seq[Children] = typeOf[A] match {
case pc if pc =:= typeOf[ProductCategory] =>
TransformProductCategory(objects.asInstanceOf[Seq[ProductCategory]])
case pa if pa =:= typeOf[ProductArea] =>
TransformProductArea(objects.asInstanceOf[Seq[ProductArea]])
case pg if pg =:= typeOf[ProductGroup] =>
TransformProductGroup(objects.asInstanceOf[Seq[ProductGroup]])
case psg if psg =:= typeOf[ProductSubGroup] =>
TransformProductSubGroup(objects.asInstanceOf[Seq[ProductSubGroup]])
case _ =>
throw new IllegalArgumentException("Invalid transformation")
}
The types used as input are all case classes and are defined internally within the application, for example:
case class ProductCategory(id: Long, name: String,
thumbnail: Option[String],
image:Option[String],
sequence:Int)
This approach, although suitable at the moment, doesn't feel functional or scalable when potentially more DB types are added. I also feel using asInstanceOf should be redundant as the type has already been asserted. My limited knowledge of implicits suggests they could be used instead to perform the transformation, and remove the need for the above Transform[A: TypeTag](objects:Seq[A]):Seq[Children] method altogether. Or maybe there is a different approach I should have used instead?
You can define a trait like this:
trait Transformer[A] {
def transformImpl(x: Seq[A]): Seq[Children]
}
Then you can define some instances:
object Transformer {
implicit val forProduct = new Transformer[ProductCategory] {
def transformImpl(x: Seq[ProductCategory]) = ...
}
...
}
And then finally:
def transform[A: Transformer](objects:Seq[A]): Seq[Children] =
implicitly[Transformer[A]].transformImpl(objects)
Preferably, you should define your implicit instances either in Transformer object or in objects corresponding to your category classes.
I'm not sure how your program is supposed to work exactly, nor do I know if any of your Transforms are self-made types or something you pulled from a library, however I may have a solution anyway
Something match is really good with, is case classes
So rather than manually checking the type of the input data, you could maybe wrap them all in case classes (if you need to bring data with you) or case objects (if you don't)
That way you can do something like this:
// this code assumes ProductCategory, ProductArea, etc.
// all extends the trait ProductType
def Transform(pType: ProductType): Seq[Children] = pType match {
case ProductCategory(objects) => TransformProductCategory(objects)
case ProductArea(objects) => TransformProductArea(objects)
case ProductGroup(objects) => TransformProductGroup(objects)
case ProductSubGroup(objects) => TransformProductSubGroup(objects)
}
By wrapping everything in a case class, you can specify exactly which type of data you want to bring along, and so long as they (the case classes, not the data) all inherit from the same class/trait, you should be fine
And since there's only a little handful of classes that extend ProductType you don't need the default case, because there is no default case!
Another benefit of this, is that it's infinitely expandable; just add more cases and case classes!
Note that this solution requires you to refactor your code a LOT, so bare that in mind before you throw yourself into it.

create type from pattern matching in scala

I'm new using Scala and what I need is to create like a dynamic type based on some pattern matching function, like
type defType = "value1" match {
case "value0" => typeOf[String]
case "value1" => typeOf[Integer]
case _ => typeOf[Double]
}
val test5 : defType = 4
This is just an example given that I'd to work with more complex structures, but gives the idea of what I want to do.
I probably don't understand what you're doing, but this seems problematic to me:
val test5 : defType = 4
The compiler needs to be able to know defType at compile time, otherwise it can't type check. Based on that, I think what you want to do is do this pattern matching at compile time by utilizing Scala macros.
However, I have a feeling that's not the right solution either just because this whole scenario seems very strange. If you give more detail about what the bigger picture is of what you're trying to do maybe we can suggest a better solution.
I fear this is not possible, for fundamental conceptual reasons. As type rules are checked at compile time, it is not possible to derive types based on values that might be unknown until runtime.
That being said, you might be able to tackle your design problem by defining a family of case classes, each being a wrapper for a value of a specific type. By giving these classes a common base class, you can store a value of any type you wish into the same variable and extract it through type-safe pattern matching:
class Base
case class AString(val value: String) extends Base
case class AnInt(val value: Int) extends Base
case class ADouble(val value: Double) extends Base
val a : Base = "value1" match {
case "value0" => AString(…)
case "value1" => AnInt(…)
case "value0" => ADouble(…)
}
a match {
case AString(s) => …
case AnInt(i) => …
case ADouble(i) => …
…
}

How to pattern match multiple values in Scala?

Let's say I want to handle multiple return values from a remote service using the same code. I don't know how to express this in Scala:
code match {
case "1" => // Whatever
case "2" => // Same whatever
case "3" => // Ah, something different
}
I know I can use Extract Method and call that, but there's still repetition in the call. If I were using Ruby, I'd write it like this:
case code
when "1", "2"
# Whatever
when "3"
# Ah, something different
end
Note that I simplified the example, thus I don't want to pattern match on regular expressions or some such. The match values are actually complex values.
You can do:
code match {
case "1" | "2" => // whatever
case "3" =>
}
Note that you cannot bind parts of the pattern to names - you can't do this currently:
code match {
case Left(x) | Right(x) =>
case null =>
}
The other answer correctly says that currently there is no way to pattern-match multiple alternatives while extracting values at the same time.
I'd like to share a coding pattern with you that comes close to doing this.
Scala allows you to pattern-match alternatives without extracting values, e.g. case Dog(_, _) | Cat(_, _) => ... is legal. Using this, you can simply extract the values yourself within the case block.
Here's a somewhat contrived example:
abstract class Animal
case class Dog(age: Int, barkLevel: Int) extends Animal
case class Cat(apparentAge: Int, cutenessLevel: Int) extends Animal
val pet: Animal = Dog(42, 100)
// Assume foo needs to treat the age of dogs and the apparent age
// of cats the same way.
// Same holds for bark and cuteness level.
def foo(pet: Animal): Unit = pet match {
case animal#(Dog(_, _) | Cat(_, _)) =>
// #unchecked suppresses the Scala warning about possibly
// non-exhaustiveness even though this match is exhaustive
val (agelike, level) = (animal: #unchecked) match {
case Dog(age, barkLevel) => (age, barkLevel)
case Cat(apparentAge, cutenessLevel) => (apparentAge, cutenessLevel)
}
???
}
Assume that ??? actually stands for doing something that is equal for dogs and cats. Without this coding pattern, you would need to have two cases, one for dogs and one for cats, forcing you to duplicate code or at least to outsorce code into a function.
Generally, the coding pattern above is suitable if you have sibling case classes that share fields that behave identically only for some algorithms. In those cases, you cannot extract those fields to a common superclass. Still, you would like to pattern-match in a uniform way on those fields in the algorithms that treat them equally. This you can do as shown above.