If you encompass it this way. Literally nothing stops you from encoding this behavior in Haskell. Because the behavior you described is *if revenue is equal to zero, display it as N/A".
No magic monad will prevent this.
If your data and/or problem is modeled as "if 0 then N/A" no amount of "human nature implicitly explicitly transformed" will help you.
Dealing with non-existent data as opposed to "value = 0" is just as natural in Java, C++ or Javascript as it is in Haskell. And yet, that didn't help.
This is not unique to functional programming languages.
Haskell was definitely a trailblazer for this, but plenty of conventional languages support this now with relative ease.
I don’t know of any mainstream language that has bolted on that level of verification.
typedef struct{int filled; union{void* data;} val;} maybe;To do something like x.get() in Scala when x is empty is a runtime error, not a compile-time error.
But Kotlin? Since when does Kotlin have pattern matching with exhaustive checks? Also it does not have an `Maybe a` / `Option[A]` type out-of-the box.
sealed class Communication
data class Email(val emailAddress: String) : Communication()
data class Shouting(val preferredName: String) : Communication()
object HiveMind : Communication()
fun exhaustiveWhen(comm: Communication) {
// not exhaustively checked (will compile)
when (comm) {
is Email -> println("sending email to ${comm.emailAddress}")
}
// *is* exhaustively checked (error at compile time)
// must add HiveMind or 'else' branch
val result = when (comm) {
is Email -> println("sending email to ${comm.emailAddress}")
is Shouting -> println("HELLO, ${comm.preferredName}!")
}
// *is* exhaustively checked
when (comm) {
is Email -> println("sending email to ${comm.emailAddress}")
is Shouting -> println("HELLO, ${comm.preferredName}!")
}.let { }
}Even the syntax can be made less awkward[1] this look still like a wonky hack to me where the user can do way to much wrong.
[1] https://proandroiddev.com/til-when-is-when-exhaustive-31d69f...
Whether a type is nullable or not is a property of the type. This creates more or less two mirror universes of types, and you can't mix even "the same" types from that incompatible universes.
An Option type is on the other hand side a distinguished parametric type on its own (I wrote `Maybe a` / `Option[A]` for a reason). It can "wrap" arbitrary other types without "infecting" those with the nullability property. Being a proper parametric type makes `Maybe` / `Option` also "just a type constructor". Therefore it can be composed with other types and type constructors according to common rules. `Maybe` / `Option` doesn't require any special rules in the language (which would make the language more difficult to learn and use)!
Also `null` is very different form `Empty` / `None`: A null value can still "sneak in" through arbitrary references and cause run-time errors if one has to interoperate with a "null unsafe" language or system (and that's more or less "the whole universe" out there). A `None` can not "sneak in" as it can be only assigned to something of `Option` type. Sure this doesn't solve the "null problem" as such but one can at least distinguish the cases where the model value is really optional, and the cases where one needs to deal with `null`s coming (potentially) form the outside world. In the one case one will use the Option type throughout the rest of the program, in the other case one will preform some null-check as early as possible and "unwrap" the underlying none-null value to work with it directly thereafter.
/s slightly, maybe more snarky, but seriously, optional types are way overhyped. Null objects are far more useful than optionals. Optionals tend to leak all over the show.
If you wish to reduce "leakiness", you can dispatch on the null-ness of the variable; Swift lets you write `if let x = x { /* x is non-optional in here * / }`, which binds a non-optional value inside the braces, and `guard let x = x else { return } /* x is non-optional now * /` which binds a non-optional value after the braces. So as soon as you consume an optional value, you are free to "un-leak" it and extract its value (if it has one) immediately.
For example consider a network response that in a JS app might be passed around as an opaque object. In a language like Swift this is infeasible, you have to transition from the network serialized version to concrete models at the network layer. If you encounter mismatches in the expectation of what is optional you have to handle it here at the edge of the program. This enables massively safer code and makes reasoning about your program much simpler if anything.
As a concerete example say an API response tells you if a user is verified(for some domain specific definition of "verified"), additionally you want to know when they were verified maybe to give them a hint when they need to do it again. You coudl model this as:
{
"verified": false,
"verified_at": null
}
before they have verified and {
"verified": true,
"verified_at": "2020-02-24T15:31:12Z"
}
after they have verified.In Swift this would then be modelled as
struct Users {
let verified: Bool
let verifiedAt: Date?
}
However this introduces an Optional which complicates code that needs to deal with users who are verified or not. Instead you could model the same thing and avoid this issue as: {
"verification_status": {
"status": "unverified"
}
}
for the unverified case and {
"verification_status": {
"status": "verified",
"at": "2020-02-24T15:31:12Z"
}
}
for the verified case. In Swift this can be nicely modelled without any Optionals using an enum enum VerificationStatus {
case unverified
case verified(Date)
}
struct User {
let verificationStatus: VerificationStatus
}
Better optional value visibility leads to better data modelling.:)
From C! AFAIK C++'s references can not be null!
By the way, now that I know some haskell, I see this edge case sloppiness all the time and it is really annoying. For example, here is something that annoyed me just recently. You go to yahoo finance and there they will show you the revenues of a company as well as the revenue growth from past year. But sometimes this revenue growth number is not available. For example, the company may not have existed past year, or perhaps it was not public past year and thus it did not publicly disclose its revenues. When they cannot compute revenue growth for these reasons, yahoo finance will helpfully show revenue growth as "N/A", i.e., not available.
That seems all nice and logical, but I noticed that yahoo finance tended to show that revenue growth was not available for many established public companies for which they should have the data. I looked at it more closely and noticed with shock that if the revenue growth of a company was about 0%, yahoo finance would still show it as "N/A"! So some programmer just decided to use 0 as not available and just erased a lot of useful information from their system. Now when I see a company that has N/A as revenue growth on yahoo finance I have to go and check whether the revenue growth was zero, or it was truly unavailable. As I said -- really annoying.
This is of course a textbook case for Haskells Maybe concept. You can have a type that is a Maybe Int, and that means it have a value or it can be Nothing. Thus, you encompass the edge case of not having the value available, without using an embarrassing hack that deletes real data. But of course Haskell gives you more power than just using Maybe. You can create a type that can encompass all kinds of edge cases and information about them. For example, you can embed a reason why the revenue growth data was not available, if that is the case.