I want to handle validation in functional approach. I have user validation logic as below
if user object is null throw an exception
if the user is not active throw an exception
if the user type is super do nothing. If user type is admin do some operation.
In my code snippet I am throwing an exception if generated random number is even, otherwise complete the flow.
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
import java.time.Instant
import java.util.UUID
import java.lang.IllegalStateException
import java.util.Random
fun main() {
val user : User? = User(UUID.randomUUID(),Instant.now(),UserStatus.ACTIVE,UserType.SUPER)
//val user : User? = User(UUID.randomUUID(),Instant.now(),UserStatus.ACTIVE,UserType.NORMAL) // works fine
user?.let{ existing -> existing.takeIf{it.status == UserStatus.ACTIVE}?.let{ activeUser ->
activeUser.takeUnless{ user -> user.userType == UserType.SUPER}?.let{
val number = Random().nextInt(5);
println(number)
if(number %2 == 0) throw IllegalStateException("invalid random number")
}
}?: throw IllegalStateException("User is not active right now")
} ?:throw IllegalStateException("user not created at all")
}
data class User(val uuid:UUID, val created:Instant, val status:UserStatus, val userType:UserType)
enum class UserStatus {
ACTIVE,INACTIVE
}
enum class UserType{
SUPER,NORMAL
}
when generated random number is odd its throwing an error message user is not active right now which is not correct. it should silently complete the function call.Any one help me what's wrong with the code?
In the inner block
existing.takeIf { it.status == UserStatus.ACTIVE }?.let { activeUser ->
activeUser.takeUnless { user -> user.userType == UserType.SUPER }?.let {
val number = Random().nextInt(5);
println(number)
if (number % 2 == 0) throw IllegalStateException("invalid random number")
}
} ?: throw IllegalStateException("User is not active right now")
You have a not null value.
Since you're not chaining the ?. calls. The better idea is to do early return/throw.
val existing = user ?: throw IllegalStateException("user not created at all")
So the nested block can be flattened.
The second exception "User is not active right now" happens when one of the two checks is not met.
The programming concept to describe this kind of business logic is not nullable variables. A regular old if check is much cleaner.
if (existing.status != UserStatus.ACTIVE || existing.userType == UserType.SUPER) {
throw IllegalStateException("User is not active right now")
}
BTW your error message seems wrong.
By "functional approach", I think you mean having the code expression-based. But nullable type is not powerful enough for your use case.
You should be looking for Either or Try. Both of them are available in the Arrow library. They are both monads. Their chaining (flatMap), like the ?.let calls, also require the callback hell.
In some languages, there is syntactic sugar to flatten the callback hell of flatMaps. You can take a look at the Monad Comprehensions, and see if you like that.
If not, no problem. In Kotlin, there is no shame associated with early return/throw.
Be pragmatic.
Related
I have a kotlin class with a method
loadElements(e: Iterable<Int>) {
}
This then constructs a new copy of that Iterable as an ArrayList<Int> within the object.
It is a requirement that all the elements in that ArrayList<Int> be non-negative. It is considered a breach of contract by the caller if that is not met. I've been led to believe that "breach of contract" is something to be tested by require(), whereas check() is for testing logic internal to that method. Is this correct ?
All the examples I have seen, have the require() as the very first lines of code within the method. Is it, however, acceptable to run require() in a loop, like this ?
public fun loadElements(e: Iterable<Int>) {
elementArray.clear()
e.forEach {
require(it>=0)
elementArray.add(it)
moduleCount += it
}
if (elementCount %2 == 1)
elementArray.add(0)
check(elementCount %2 == 0)
computeInternalSizes()
}
Thing is, this means that part of the object's internals may already be set-up by the time the require() breach is detected: i.e., moduleCount will be wrong and computeInternalSizes() will never get called.
Now, of course I could just use a separate pass, with the first one checking for the require() condition, and then doing all the real work thereafter. This would mean that if the input came in as a Sequence<Int>, it would be forced to be terminal and multi-iterable.
If the require() throws, I would like to assume that the program cannot continue because a design error has occurred somewhere. However, if someone traps the resultant exception, and continues, I will end-up with an incoherent object state.
What is best practice for handling conditions where incoming parameter breaches won't be noticed until some significant unrewindable work has been done ?
I tried using a separate pass for checking for non-negativity. This worked perfectly well but, given that it could be coming from a Sequence or similar, I don't want to have to build the whole sequence, and then traverse that sequence again.
I tried using check(). This works, but it just shows up as an inconsistency in object state, rather than flagging up the incoming parameter validation, which is making a breach of contract look like an internal design fault, and just delaying the inevitable.
I've tried putting try/catch/finally all over the place, but this is an excessive amount of code for such a simple thing.
I'm not even sure if a program should attempt recovery if a require() fails.
In general you avoid situations like this, by reducing the scope of mutability in your code.
The difference between require and check is mostly a convention. They throw different Exceptions, namely IllegalArgumentException and IllegalStateException respectively. As the type of the Exceptions suggest, former is suited for validating the (user) input to a method whereas the latter is designed to check intermediate states during the runtime.
Exceptions in Kotlin should be handled as such, being an Exception that should not occur regularly. See also the Kotlin documentation why there are no checked exceptions in Kotlin.
You did not write the name of your surrounding Kotlin class, thus I'll call it Foo for the time being.
Rather than providing a function on Foo, that mutates the internal state of Foo, you could create new instances of Foo based on the Iterable<Int> / Sequence<Int>. This way, you only ever have an Foo object when its in a valid state.
private class Foo(source: Iterable<Int>) {
private val elementArray = ArrayList<Int>()
private val moduleCount: Int
init {
var internalCount = 0
for (count in source) {
require(count > 0)
elementArray.add(count)
internalCount += count
}
moduleCount = internalCount
if (elementArray.size % 2 == 1) {
elementArray.add(0)
}
check(elementArray.size % 2 == 0)
// ...
}
}
Alternatively, if you want / need to keep the interface as described in your question but also avoid the invalid state, you could make use of an intermediate copy.
As you're copying the incoming Iterable<Int> / Sequence<Int> into an ArrayList<Int> I assume you're not working with very large collections.
private class Foo(source: Iterable<Int>) {
private val elementArray = ArrayList<Int>()
private var moduleCount = 0
public fun loadElements(source: Iterable<Int>) {
val internalCopy = ArrayList<Int>()
for (count in source) {
require(count >= 0)
internalCopy.add(count)
}
elementArray.clear()
for (count in internalCopy) {
elementArray.add(count)
moduleCount += count
}
if (elementArray.size % 2 == 1) {
elementArray.add(0)
}
check(elementArray.size % 2 == 0)
// ...
}
}
I have been stuck making this metric converter app for over a week I keep going back to the previous codelabs that explain how to make a tip calculator but when I try to apply the same method to this app that I'm trying to make it doesn't have any errors but the app crashes whenever I try to test it to see if I can get the functionality to work this is my last resort. I've tried to do when statements but I kept getting the error that certain branches would never be reached. if anyone can point me in the right direction as to what I'm doing wrong or what's missing from my code. I'm all ears.
package com.example.metricconversion
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.metricconversion.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
setContentView(binding.root)
binding.conversionTo.setOnClickListener { convertMetric() }
}
fun convertMetric() {
val stringInTextField = binding.howMuchStuffWeTalking.text.toString()
val amountIQ = stringInTextField.toDouble()
val selectedId = binding.unitThatNeedsToBeConverted.checkedRadioButtonId
val selectedId2 = binding.toBeConverted.checkedRadioButtonId
// val grainToQtr = R.id.Grain and R.id.quarter1
//val grainToSt= R.id.Grain and R.id.quarter1
//val grainToLb = R.id.Grain and R.id. pound1
//val grainToTon= R.id.Grain and R.id.Ton1
val st = R.id.Grain and R.id.stone1
// code to convert grain to the other options
//code to convert grain to the other options
when (selectedId and selectedId2) {
R.id.Grain and R.id.Ounce1 -> {
amountIQ / 437.5
}
}
when (selectedId and selectedId2) {
R.id.Grain and R.id.quarter1 -> amountIQ * 0.000005714
}
when (selectedId and selectedId2) {
R.id.Grain and R.id.pound1 -> amountIQ * 0.0001429
}
when (selectedId and selectedId2) {
R.id.Grain and R.id.Ton1 -> amountIQ / 1.4e+7
}
when (selectedId and selectedId2) {
st -> {
amountIQ * 0.0000102
}
}
binding.metricConverted.text=getString(R.string.end_result,convertMetric())
}
}
Problems with your code:
Using and to check two values for equality at the same time is not viable. The and function does a bitwise operation to merge the two numbers together. There are many possible inputs that could merge to the same solution, so you will get false positives. Instead you could use the to infix function to combined the two numbers into a Pair wrapper object so they are both preserved for the comparison.
You have a series of individual when statements, each with only a single condition. It doesn't make sense to use a when statement with a single condition. Usually a single condition would be represented with an if statement instead of when statement. But I'm guessing you didn't mean for these to be separate when statements, based on what I'm seeing.
Your when statements are not actually doing anything. Their branches resolve to a number, but you're not doing anything with that number (not assigning it to a variable or logging it or showing it in the UI, etc.). So they are useless. Your statement at the bottom isn't getting the result of any of the when statements, but is instead recursively calling the same function again. This function doesn't return anything, so that's useless. Even if it did return a number, the recursive call will create an infinite loop, resulting in a stack overflow.
So first, to just fix your code (we can discuss a better way of doing it later):
We replace and with to.
We merge all the when statements into a single statement.
We store the result of the when statement in a variable and use that variable to set the text in the last line of the function. When you use a when statement with a subject (something in parentheses that is compared for each branch) or to get a result, you have to cover every possible case, so an else branch must also be added.
fun convertMetric() {
val stringInTextField = binding.howMuchStuffWeTalking.text.toString()
val amountIQ = stringInTextField.toDouble()
val selectedId = binding.unitThatNeedsToBeConverted.checkedRadioButtonId
val selectedId2 = binding.toBeConverted.checkedRadioButtonId
// code to convert grain to the other options
val result = when (selectedId to selectedId2) {
R.id.Grain to R.id.Ounce1 -> amountIQ / 437.5
R.id.Grain to R.id.quarter1 -> amountIQ * 0.000005714
R.id.Grain to R.id.pound1 -> amountIQ * 0.0001429
R.id.Grain to R.id.Ton1 -> amountIQ / 1.4e+7
R.id.Grain to R.id.stone1 -> amountIQ * 0.0000102
else -> error("Unsupported selection pair")
}
binding.metricConverted.text = getString(R.string.end_result, result.toString())
}
Above, the error() call will crash your app. You need to make sure you cover every possible combination that could occur. During development, this is suitable, but for production you might want to change the behavior so it shows an error message in the UI of the app and doesn't crash.
Now, regarding the overall design, it is quite fragile because you have UI layout details so tightly coupled to your app's behavior. All these formula calculations should probably be defined in a separate class, possibly an Enum class. You could create a Map in your Activity file that links the UI elements to the behavior class(es), and then in your when statement, you could use the UI elements to pull the associated behavior from the map. This would be more maintainable, and make it easier for you to avoid forgetting something as you add/modify functionality. I say this just to get you thinking about it, but it's probably too much for a beginner project right now. I don't have time to explain in detail how I would do all of that for your case.
I want to wrap a throw in a helper-function, for logging purposes and such.
private fun chooseEmailAddress(user: UserProfile): EmailAddress {
val emailAddress = user.emailAddresses.find {
true // some business logic
}
if (emailAddress == null) {
throwAndNotice(CustomError(
message = "No Email Address found.",
))
}
return emailAddress
}
private fun throwAndNotice(err: CustomError) {
NewRelic.noticeError(err)
throw err
}
The problem:kotlin complains about a type-mismatch:
Type mismatch.
Required: Email
Found: Email?
I guess the compiler does not know that throwAndNotice always throws. If I inline the throwAndNotice method, it works, but it leads to duplication in about a dozen methods.
Is there a way I can tell the compiler "the following method always throws"? Or is there another idiomatic way to deal with this issue? I don't want to resort to !!.
Make it return Nothing. This indicates that it will never return (either throw an exception or infinite loop):
private fun throwAndNotice(err: CustomError): Nothing {
NewRelic.noticeError(err)
throw err
}
You can see other examples of doing this in the standard library, like TODO() and error().
Side note (as mentioned by dey in the comments):
The null check can be rewritten using ?: like this:
return emailAddress ?: throwAndNotice(...)
Say I have an API like so:
interface Foo {
val barFlow: Flow<Bar>
}
And I consume it like so:
class FooConsumer(private val foo: Foo) {
init {
CoroutineScope(Dispatchers.IO).launch {
val bar = foo.barFlow.single()
println("Collected bar: $bar)
}
}
}
According to the docs for single a NoSuchElementException can be thrown if the flow is empty. However, this confuses me quite a lot, as a terminal operation on a flow will "await" elements of the flow to be emitted. So how will the call to single know that there were no elements in the flow? Maybe an element just hasn't been emitted yet?
I mean under the hood, the call to single is collecting the source flow before it does the check. Therefore at least 1 item must have been emitted before the check for null is carried out, so that null check should never succeed and a NoSuchElementException should never be thrown (for the case where the flow is of a non nullable type).
So will NoSuchElementException only be a possibility for flows of nullable types?
Here is the source code for single:
/**
* The terminal operator, that awaits for one and only one value to be published.
* Throws [NoSuchElementException] for empty flow and [IllegalStateException] for flow
* that contains more than one element.
*/
public suspend fun <T> Flow<T>.single(): T {
var result: Any? = NULL
collect { value ->
if (result !== NULL) error("Expected only one element")
result = value
}
if (result === NULL) throw NoSuchElementException("Expected at least one element")
#Suppress("UNCHECKED_CAST")
return result as T
}
NoSuchElementException is thrown when the Flow finishes its emission without emitting a single element. One case I can think of right now is when you need to turn a collection into a Flow source. If that collection is empty and you call single on that Flow you will get a NoSuchElementException.
This example may seem absurd but you get the point:
val emptyListFlow = emptyList<Int>().asFlow()
launch {
val data = emptyListFlow.single()
}
In my case, I made a list.first(), where the list was empty
Let me first put the code then explain:
fun main() {
loop# while (true){
println("Input the action (add, remove, import, export, ask, exit):")
val userInput = scanner.nextLine()
when (userInput){
"add" -> add()
"remove" -> remove()
"import" -> import()
"export" -> export()
"ask" -> ask()
"exit" -> {
print("Bye bye!")
break#loop
}
}
}
}
This is the main function where the user chooses what he wants to do. First he adds some cards that contain terms and definitions as pairs, but thats irrelevant. The problem lies after that, when using the function ask().
fun ask() {
println("How many times to ask?")
for (i in 0 until scanner.nextInt()){
for ((key,value) in map){
println("Print the definition of \u0022$key\u0022:")
var userAnswer = (readLine() ?: "exit").toString()
if (userAnswer == value) {
println("Correct answer.")
continue
}
else {
loop# for ((key2,value2) in map){
if(map.containsValue(userAnswer) && userAnswer == value2){
println("Wrong answer. The correct one is \u0022$value\u0022, you've just written the definition of \u0022$key2\u0022")
break#loop
}else if (!map.containsValue(userAnswer)) {
println("Wrong answer. The correct one is \u0022$value\u0022.")
break#loop
}
}
}
}
}
}
The code works and everything, but after doing the ask() function, just when the program loops back to main(), it prints "Input the action (add, remove, import, export, ask, exit):" twice instead of once. A friend of mine told me it could be an empty input left in cache that triggers this, so that it goes once through main() without actually taking user input.
Please, if anyone has experience with this I would really appreciate it to hear and learn about it and how to prevent it, cause it hinders me from finishing the project. Would changing the way how I take in users input help?