I try to use const all the time, but often I find myself in a position, where I have to use let.
For example:
let value
try {
value = couldFail()
} catch(e) {
value = etc()
}
doSomethingWith(value)
Is there a general way to restructure such things?
-- Edit --
An idea I had:
const tryCatch = (a, b) => {
try { return a() }
catch(e) { return b(e) }
}
const value = tryCatch(couldFail, etc)
Could this bring any problems?
You cannot use const since the value may be assigned first in the try clause and later in the catch clause. If you absolutely want your variable to be const, you can create a temporary variable that you assign in your try and catch clause, and add a finally clause where you assign the temporary variable to your const variable.
Related
On Swift, we have try? when the error handling is just little of importance, and we just want to silence the whole ordeal and just "give me null if it fails".
Is there such thing in Flutter? I tried to find it, but I can only find the usual try ... catch clause on the documentation. I mean, sure I can make it like this:
dynamic result = null;
try { result = json.decode(response.body); } catch (e) { }
But I'm trying to find something more like this if it exists:
var result = try? json.decode(response.body);
This will have the added value of not having to manually type the variable type beforehand and let the lint/editor/compiler do that by simply using var (though in this specific case the type result might be dynamic).
There isn't a Dart language feature to do it. I'm not familiar with how try? works in Swift, but one potential problem for such a construct in Dart is that without specifying what you catch, you could catch logical errors (such as failed assertions).
If you don't mind an extra level of function calls, you could write a function to make it (arguably) a bit more convenient (and this also would give you control over what to catch):
T? doOrDoNot<T>(T Function() closure) {
try {
return closure();
} on Exception {
return null;
}
}
var result = doOrDoNot(() => json.decode(response.body));
how can i catch an 'as' typecast exception in flutter. For example this causes an expection as the cast wasn't successful.
final success = mapJson['success'] as String;
In Swift we can use a guard let or an if let statement. Is there something similar for flutter/dart?
Extending the Answer of #Christopher you can even catch specific exceptions using the on block and execute exception specific code:
try {
// ...
} on SomeException catch(e) {
//Handle exception of type SomeException
print(e)
} catch(e) {
//Handle all other exceptions
print(e)
} finally {
// code that should always execute; irrespective of the exception
}
You can use a try-catch block to catch all exceptions in nearly any situation. You can read more about them here and from many other places online.
Example usage:
void main() {
int x = 3;
var posVar;
try{
posVar = x as String;
}
catch(e) {
print(e);
}
print(posVar);
}
This print outs
TypeError: 3: type 'JSInt' is not a subtype of type 'String'
null
on DartPad and will be different in a real environment. The code in the try block throws an exception that is caught and can be handled in the catch block.
The Swift guard-let and if-let are used to avoid null values (nil in Swift) and either assign the non-null value to a variable, or execute the else branch (which must contain a control-flow operation in the guard case).
Dart has other patterns for doing the same thing, based on type promotion. Here I'd do:
final success = mapJson['success'];
if (success is String) {
... success has type `String` here!
}
With the (at time of writing yet upcoming) Null Safety feature's improved type promotion, you can even write:
final success = mapJson['success'];
if (success is! String) return; // or throw or another control flow operation.
... success has type `String` here!
You should not make the code throw and then catch the error (it's not an Exception, it's an Error, and you should not catch and handle errors). The "don't use try/catch for control flow" rule from other languages also applies to Dart.
Instead do a test before the cast, and most likely you won't need the cast because the type check promotes.
Make use of Null-aware operator to avoid unwanted Null and crash.
this is short an alternative to try catch (which is more powerful).
Null-aware operator works like guard let or if let in swift.
??
Use ?? when you want to evaluate and return an expression IFF another expression resolves to null.
exp ?? otherExp
is similar to
((x) => x == null ? otherExp : x)(exp)
??=
Use ??= when you want to assign a value to an object IFF that object is null. Otherwise, return the object.
obj ??= value
is similar to
((x) => x == null ? obj = value : x)(obj)
?.
Use ?. when you want to call a method/getter on an object IFF that object is not null (otherwise, return null).
obj?.method()
is similar to
((x) => x == null ? null : x.method())(obj)
You can chain ?. calls, for example:
obj?.child?.child?.getter
If obj, or child1, or child2 are null, the entire expression returns null. Otherwise, getter is called and returned.
Ref: http://blog.sethladd.com/2015/07/null-aware-operators-in-dart.html
Also Check Soundness in dart
https://dart.dev/guides/language/type-system
I'm building a didactic compiler, and I'd like to check if the function will always return a value. I intend to do this in the semantic analysis step (as this is not covered by the language grammar).
Out of all the flow control statements, this didactic language only has if, else, and while statements (so no do while, for, switch cases, etc). Note that else if is also possible. The following are all valid example snippets:
a)
if (condition) {
// non-returning commands
}
return value
b)
if (condition) {
return value
}
return anotherValue
c)
if (condition) {
return value1
} else {
return value2
}
// No return value needed here
I've searched a lot about this but couldn't find a pseudoalgorithm that I could comprehend. I've searched for software path testing, the white box testing, and also other related Stack Overflow questions like this and this.
I've heard that this can be solved using graphs, and also using a stack, but I have no idea how to implement those strategies.
Any help with pseudocode would be very helpful!
(and if it matters, I'm implementing my compiler in Swift)
If you have a control flow graph, checking that a function always returns is as easy as checking that the implicit return at the end of the function is unreachable. So since there are plenty of analyses and optimizations where you'll want a CFG, it would not be a bad idea to construct one.
That said, even without a control flow graph, this property is pretty straight forward to check assuming some common restrictions (specifically that you're okay with something like if(cond) return x; if(!cond) return y; being seen as falling of the end even though it's equivalent to if(cond) return x; else return y;, which would be allowed). I also assume there's no goto because you didn't list it in your list of control flow statements (I make no assumptions about break and continue because those only appear within loops and loops don't matter).
We just need to consider the cases of what a legal block (i.e. one that always reaches a return) would look like:
So an empty block would clearly not be allowed because it can't reach a return if it's empty. A block that directly (i.e. not inside an if or loop) contains a return would be allowed (and if it isn't at the end of the block, everything after the return in the block would be unreachable, which you might also want to turn into an error or warning).
Loops don't matter. That is, if your block contains a loop, it still has to have a return outside of the loop even if the loop contains a return because the loop condition may be false, so there's no need for us to even check what's inside the loop. This wouldn't be true for do-while loops, but you don't have those.
If the block directly contains an if with an else and both the then-block and the else-block always reach a return, this block also always reaches a return. In that case, everything after the if-else is unreachable. Otherwise the if doesn't matter just like loops.
So in pseudo code that would be:
alwaysReturns( {} ) = false
alwaysReturns( {return exp; ...rest} ) = true
alwaysReturns( { if(exp) thenBlock else elseBlock; ...rest}) =
(alwaysReturns(thenBlock) && alwaysReturns(elseBlock)) || alwaysReturns(rest)
alwaysReturns( {otherStatement; ...rest} ) = alwaysReturns(rest)
So, after 5 hours thinking how to implement this, I came up with a decent solution (at least I haven't been able to break it so far). I actually spent most of the time browsing the web (with no luck) than actually thinking about the problem and trying to solve it on my own.
Below is my implementation (in Swift 4.2, but the syntax is fairly easy to pick up), using a graph:
final class SemanticAnalyzer {
private var currentNode: Node!
private var rootNode: Node!
final class Node {
var nodes: [Node] = []
var returnsExplicitly = false
let parent: Node?
var elseNode: Node!
var alwaysReturns: Bool { return returnsExplicitly || elseNode?.validate() == true }
init(parent: Node?) {
self.parent = parent
}
func validate() -> Bool {
if alwaysReturns {
return true
} else {
return nodes.isEmpty ? false : nodes.allSatisfy { $0.alwaysReturns }
}
}
}
/// Initializes the components of the semantic analyzer.
func startAnalyzing() {
rootNode = Node(parent: nil)
currentNode = rootNode
}
/// Execute when an `if` statement is found.
func handleIfStatementFound() {
let ifNode = Node(parent: currentNode)
let elseNode = Node(parent: currentNode)
// Assigning is not necessary if the current node returns explicitly.
// But assigning is not allowed if the else node always returns, so we check if the current node always returns.
if !currentNode.alwaysReturns {
currentNode.elseNode = elseNode
}
currentNode.nodes += [ ifNode, elseNode ]
currentNode = ifNode
}
/// Execute when an `else` statement is found.
func handleElseStatementFound() {
currentNode = currentNode.elseNode
}
/// Execute when a branch scope is closed.
func handleBranchClosing() {
currentNode = currentNode.parent! // If we're in a branch, the parent node is never nil
}
/// Execute when a function return statement is found.
func handleReturnStatementFound() {
currentNode.returnsExplicitly = true
}
/// Determine whether the function analyzed always returns a value.
///
/// - Returns: whether the root node validates.
func validate() -> Bool {
return rootNode.validate()
}
}
Basically what it does is:
When it finds an if statement is create 2 new nodes and point the current node to both of them (as in a binary tree node).
When the else statement is found, we just switch the current node to the else node created previously in the if statement.
When a branch is closed (e.g. in an if statement's } character), it switches the current node to the parent node.
When it finds a function return statement, it can assume that the current node will always have a return value.
Finally, to validate a node, either the node has an explicit return value, or all of the nodes must be valid.
This works with nested if/else statements, as well as branches without return values at all.
I'm learning Swift and I'm testing the following code
var value: String;
do {
value = try getValue()
} catch {
value = "Default Value"
}
which can be shortened to
let value = (try? getValue()) ?? "Default Value"
It works but I feel I may be missing a more obvious solution.
Your solution is just great, and extremely elegant.
I presume we would like to avoid saying var in the first line and initializing the variable later. In general, the way to initialize a value immediately with a complex initializer is to use a define-and-call construct:
let value: String = {
do {
return try getValue()
} catch {
return "Default Value"
}
}()
And you would probably want to do that if the catch block was returning error information that you wanted to capture.
However, in this case, where you are disregarding the nature of the error, your expression is much more compact, and does precisely what you want done. try? will return an Optional, which will either be unwrapped if we succeed, or, if we fail, will return nil and cause the alternate value ("Default Value") to be used.
struct Type
{
auto opBinary(string op)(Type other) const {
return Type(); // #1 return type is Type
return typeof(this)(); // #2 return type is const(Type)
}
}
unittest
{
Type t1, t2;
auto t3 = t1 + t2;
}
In t1.opBinary!("+")(t2), t1 becomes a const, while t2 stays non-const. Should opBinary's return type be Type or const(Type), and why?
const(T) is a supertype, so maybe it should return a const, but I've hardly seen this in practice. Things also become rather complicated when dealing with a hierarchy of types and functions that use those types or are used by those types.
Since the return value here is a new object, I'd say make it non-const. Being new, it can be modified safely, so no reason to unnecessarily restrict it with const.
If you were returning a part of the existing object, you'll want to use inout instead of const. inout means the constness of the object will also go to the return value.
inout(Type) opBinary(string op)(Type other) inout {
return whatever;
}
Now if you use a const(Type) object, the return will be const as well, and if it is called on a mutable object, the return is also mutable.
Should return type be T or const(T)? Any will do.
Which one is better? Depends on your intended behavior.
const qualifier on opBinary only means that hidden "this" argument is const. Nothing more, nothing else. It does not imply anything about the return type. It all boils down to pretty simple choice:
struct Type
{
int a;
auto opBinary(string op)(Type other) const
if (op == "+")
{
return Type(this.a + other.a);
}
}
void main()
{
Type first, second;
(first + second).a = 42; // Do you want to allow this?
// Yes -> return non-const
// No -> return const
}
If you want to preserve qualifiers of arguments, use either inout (see Adams answer) or manual check for qualifiers for more complicated choices.
With either choice remember automatic type deduction:
auto third = first + second;
third.a = 42; // Error if returns const
Type fourth = first + second;
fourth.a = 42; // Fine in both cases, struct was copied
In the end it is about you intention as a type designer how class/struct should behave.