Skip to content

Commit babb256

Browse files
committed
Changes:
- Revert to Scala LTS version `3.3.5` - Simplify `scalafixAll` and `scalafmt` - Merge modules `basic`, `standard`, `advanced` and `magic` into one module `language-feature` - Merge `types`, `domain`, `errors` and `Main` under one object - Upgrade `neotype` to version `0.3.11` - Update README.md hyperlinks to source code.
1 parent 18d5647 commit babb256

File tree

36 files changed

+525
-578
lines changed

36 files changed

+525
-578
lines changed

.scalafix.conf

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ rules = [
33
]
44

55
OrganizeImports {
6-
groupedImports = Explode
6+
expandRelative = true
77
groups = [
88
"re:(java?|scala)\\.",
99
"dagmendez",
1010
"*"
1111
]
12-
removeUnused = false
1312
}

.scalafmt.conf

+4-22
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ version = "3.8.6"
22

33
maxColumn = 150
44

5-
runner {
6-
dialect = scala3
7-
}
5+
align.preset = more
6+
docstrings.style = Asterisk
7+
rewrite.rules = [RedundantBraces]
8+
runner.dialect = scala3
89

910
fileOverride {
1011
"glob:**.sbt" {
@@ -14,22 +15,3 @@ fileOverride {
1415
runner.dialect = scala213
1516
}
1617
}
17-
18-
align {
19-
arrowEnumeratorGenerator = true
20-
ifWhileOpenParen = false
21-
openParenCallSite = true
22-
openParenDefnSite = true
23-
preset = more
24-
}
25-
26-
docstrings.style = Asterisk
27-
28-
rewrite {
29-
rules = [RedundantBraces]
30-
redundantBraces.maxLines = 1
31-
}
32-
33-
optIn {
34-
breaksInsideChains = true
35-
}

01-basic/src/main/scala/dagmendez/basic/Main.scala

-27
This file was deleted.

01-basic/src/main/scala/dagmendez/basic/domain.scala

-10
This file was deleted.

01-basic/src/main/scala/dagmendez/basic/typeAliases.scala

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dagmendez.a
2+
3+
object Basic:
4+
5+
/*
6+
* 1. Type Aliases
7+
*/
8+
9+
type Name = String
10+
type IBAN = String // International Bank Account Number
11+
type Balance = Int
12+
13+
/*
14+
* 2. Domain
15+
*/
16+
17+
// The account holder is the person who signs the contract for said account with the bank
18+
final case class AccountHolder(firstName: Name, middleName: Option[Name], lastName: Name, secondLastName: Option[Name])
19+
20+
final case class Account(accountHolder: AccountHolder, iban: IBAN, balance: Balance)
21+
22+
@main def run(): Unit =
23+
24+
val firstName: Name = "John"
25+
val middleName: Name = "Stuart"
26+
val lastName: Name = "Mill"
27+
28+
val holder = AccountHolder(
29+
firstName,
30+
Some(middleName),
31+
lastName,
32+
None
33+
)
34+
35+
// Example of IBAN from the United Kingdom
36+
val iban: IBAN = "GB33BUKB20201555555555"
37+
val balance: Balance = -10
38+
val account = Account(holder, iban, balance)
39+
40+
println(account)
41+
42+
end Basic
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package dagmendez.b
2+
3+
object Standard:
4+
5+
/*
6+
* 1. Opaque Tyoes
7+
*/
8+
9+
opaque type Name = String
10+
opaque type IBAN = String
11+
opaque type Balance = Int
12+
13+
/*
14+
* Opaque types can have companion objects.
15+
* This allows us to provide the opaque type with an API.
16+
* Here we will include the apply method so we can build values of the opaque type safely.
17+
*/
18+
19+
object Name:
20+
def apply(name: String): Name = name
21+
22+
object IBAN:
23+
def apply(iban: String): IBAN = iban
24+
25+
object Balance:
26+
def apply(balance: Int): Balance = balance
27+
28+
/*
29+
* 2. Domain
30+
*/
31+
32+
// The account holder is the person who signs the contract for said account with the bank
33+
final case class AccountHolder(firstName: Name, middleName: Option[Name], lastName: Name, secondLastName: Option[Name])
34+
35+
final case class Account(
36+
accountHolder: AccountHolder,
37+
iban: IBAN, // International Bank Account Number
38+
balance: Balance
39+
)
40+
41+
@main def run(): Unit =
42+
43+
/*
44+
* Now we can build values of opaque types using the apply method as if they were case classes.
45+
* Case classes create a new instance during runtime.
46+
* Opaque types apply methods happen during compile time.
47+
*/
48+
49+
val firstName: Name = Name("John")
50+
val middleName: Name = Name("Stuart")
51+
val lastName: Name = Name("Mill")
52+
val iban: IBAN = IBAN("GB33BUKB20201555555555")
53+
val balance: Balance = Balance(123)
54+
55+
val holder: AccountHolder =
56+
AccountHolder(
57+
firstName,
58+
Some(middleName),
59+
lastName,
60+
None
61+
)
62+
63+
val account: Account = Account(
64+
holder,
65+
iban,
66+
balance
67+
)
68+
69+
println(account)
70+
71+
end Standard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package dagmendez.c
2+
3+
import scala.compiletime.codeOf
4+
import scala.compiletime.error
5+
import scala.util.control.NoStackTrace
6+
7+
object Advanced:
8+
9+
/*
10+
* 1. Opaque Tyoes
11+
*/
12+
13+
opaque type Name = String
14+
opaque type IBAN = String // International Bank Account Number
15+
opaque type Balance = Int
16+
17+
/*
18+
* Validations on the apply method are restricted to those that can be evaluated at compile time.
19+
* Most of the API of the basic types (String, Int, etc.) are of no use here.
20+
* Notice that the validations are different in the apply method and the from method.
21+
* Both validations should be equal and defined in a single places.
22+
* `scala.compiletime.codeOf` returns the value of the parameter passed into the inlined method apply
23+
* `scala.compiletime.error` generates a custom compiler error.
24+
* */
25+
26+
object Name:
27+
28+
inline def apply(name: String): Name =
29+
inline if name == ""
30+
then error(codeOf(name) + " is invalid.")
31+
else name
32+
33+
def either(fn: String): Either[InvalidName, Name] =
34+
// Here we can access the underlying type API because it is evaluated during runtime.
35+
if fn.isBlank | (fn.trim.length < fn.length)
36+
then Left(InvalidName(s"First name is invalid with value <$fn>."))
37+
else Right(fn)
38+
39+
object IBAN:
40+
41+
inline def apply(iban: String): IBAN =
42+
inline if iban == ""
43+
then error(codeOf(iban) + " in invalid.")
44+
else iban
45+
46+
def either(iban: String): Either[InvalidIBAN, IBAN] =
47+
if iban.isBlank | iban.contains(" ")
48+
then Left(InvalidIBAN(s"First name is invalid with value <$iban>."))
49+
else Right(iban)
50+
51+
object Balance:
52+
53+
inline def apply(balance: Int): Balance =
54+
inline if balance > 1000000 | balance < -1000
55+
then error(codeOf(balance) + " in invalid.")
56+
else balance
57+
58+
def either(balance: Int): Either[InvalidBalance, Balance] =
59+
if balance > 1000000 | balance < -1000
60+
then Left(InvalidBalance(s"First name is invalid with value <$balance>."))
61+
else Right(balance)
62+
63+
/*
64+
* 2. Domain
65+
*/
66+
67+
// The account holder is the person who signs the contract for said account with the bank
68+
final case class AccountHolder(firstName: Name, middleName: Option[Name], lastName: Name, secondLastName: Option[Name])
69+
final case class Account(accountHolder: AccountHolder, iban: IBAN, balance: Balance)
70+
71+
/*
72+
* 3. Errors
73+
*/
74+
75+
// We add two custom error classes to handle invalid values
76+
final case class InvalidName(message: String) extends RuntimeException(message) with NoStackTrace
77+
final case class InvalidIBAN(message: String) extends RuntimeException(message) with NoStackTrace
78+
final case class InvalidBalance(message: String) extends RuntimeException(message) with NoStackTrace
79+
80+
@main def run(): Unit =
81+
82+
object HappyApply:
83+
private val firstName: Name = Name("John")
84+
private val middleName: Name = Name("Stuart")
85+
private val lastName: Name = Name("Mill")
86+
private val iban: IBAN = IBAN("GB33BUKB20201555555555")
87+
private val balance: Balance = Balance(-300)
88+
89+
private val account: Account =
90+
Account(
91+
AccountHolder(
92+
firstName,
93+
Some(middleName),
94+
lastName,
95+
secondLastName = None
96+
),
97+
iban,
98+
balance
99+
)
100+
101+
def print(): Unit = println(account)
102+
103+
object UnhappyApply:
104+
private val firstName: Name = Name("John") // Comment this one an uncomment next line
105+
// private val firstName: Name = Name("") // Uncomment and won't compile
106+
private val middleName: Name = Name("Stuart")
107+
private val lastName: Name = Name("Mill")
108+
private val iban: IBAN = IBAN("GB33BUKB20201555555555")
109+
private val balance: Balance = Balance(-1000)
110+
111+
private val account: Account =
112+
Account(
113+
AccountHolder(
114+
firstName,
115+
Some(middleName),
116+
lastName,
117+
secondLastName = None
118+
),
119+
iban,
120+
balance
121+
)
122+
123+
def print(): Unit = println(account)
124+
125+
object HappyFrom:
126+
private val firstName: Either[InvalidName, Name] = Name.either("John")
127+
private val middleName: Either[InvalidName, Name] = Name.either("Stuart")
128+
private val lastName: Either[InvalidName, Name] = Name.either("Mill")
129+
private val iban: Either[InvalidIBAN, IBAN] = IBAN.either("GB33BUKB20201555555555")
130+
private val balance: Either[InvalidBalance, Balance] = Balance.either(0)
131+
132+
private val account: Either[RuntimeException & NoStackTrace, Account] =
133+
for
134+
fn <- firstName
135+
mn <- middleName
136+
ln <- lastName
137+
ib <- iban
138+
bl <- balance
139+
yield Account(AccountHolder(fn, Some(mn), ln, secondLastName = None), ib, bl)
140+
141+
assert(account.isRight)
142+
143+
def print(): Unit = println(account)
144+
145+
object UnhappyFrom:
146+
147+
// Play with any field that would crash the validation and return Left
148+
private val firstName: Either[InvalidName, Name] = Name.either("John")
149+
private val middleName: Either[InvalidName, Name] = Name.either("Stuart ") // This returns Left.
150+
private val lastName: Either[InvalidName, Name] = Name.either("Mill")
151+
private val iban: Either[InvalidIBAN, IBAN] = IBAN.either("GB33BUKB20201555555555")
152+
private val balance: Either[InvalidBalance, Balance] = Balance.either(-5000) // This returns Left.
153+
154+
private val account: Either[RuntimeException & NoStackTrace, Account] =
155+
for
156+
fn <- firstName
157+
mn <- middleName
158+
ln <- lastName
159+
ib <- iban
160+
bl <- balance
161+
yield Account(AccountHolder(fn, Some(mn), ln, secondLastName = None), ib, bl)
162+
163+
assert(account.isLeft)
164+
165+
def print(): Unit = println(account)
166+
167+
HappyApply.print() // Compiles
168+
UnhappyApply.print() // Won't compile
169+
HappyFrom.print() // Right
170+
UnhappyFrom.print() // Left

0 commit comments

Comments
 (0)