Suggestion: Use Seq[T] instead of Option[Seq[T]] in UploadDtos#348
Suggestion: Use Seq[T] instead of Option[Seq[T]] in UploadDtos#348ggVGc wants to merge 6 commits into
Conversation
ggVGc
left a comment
There was a problem hiding this comment.
Reasonings behind the main changes.
| extra <- memb.role.extra; | ||
| l2 <- dobj.specificInfo.toOption; | ||
| cols <- l2.columns | ||
| l2 <- dobj.specificInfo.toOption |
There was a problem hiding this comment.
Side note, but this usage of Either.toOption is why I don't really like the Either type in general. It signifies that one value (the Left one) is more important or correct than the other one. So, in that sense it is essentially an "Option with an error message".
But, our usage of it here does not actually carry that meaning.
So, I would prefer a compound type like SpatioTemporalMeta | StationTimeSeriesMeta instead, and that the uses of Either.fold be replaced by just pattern matches.
There was a problem hiding this comment.
It sounds like it could be a nice improvement. I cannot think of a reason why we are using Either other than union types didn't exist when this code was written.
| stationTs.columns.fold("")(_.collect{ | ||
| case v if v.valueType.unit.isDefined => v.label | ||
| }.mkString(" (", ", ", ")")) | ||
| if (stationTs.columns.nonEmpty) |
There was a problem hiding this comment.
The original code wants to produce an empty string if there is no content, and the .mkString(...) call would not do that with an empty list. This check preserves original behaviour.
| } | ||
|
|
||
| val variableMeasured = asOptArray(dobj.specificInfo.fold(_.variables, _.columns))( | ||
| val variableMeasured = asOptArray(dobj.specificInfo.fold((_.variables), _.columns))( |
There was a problem hiding this comment.
JSON serialization is one case there Option[Seq[T]] could differ from Seq[T], producing a null or [].
However, here we are using asOptArray which converts an empty list to JsNull as seen here
So, even though we are changing from an Option to a Seq here, the end result will be the same during serialization.
| for valTypeLookup <- valTypeLookupV; varName <- vars do | ||
| if valTypeLookup.lookup(varName).isEmpty then errors += | ||
| s"Variable name '$varName' is not compatible with dataset specification ${dsSpec.self.uri}" | ||
| if (spTempMeta.variables.nonEmpty) { |
There was a problem hiding this comment.
I am not entirely sure if this empty check is necessary, but including it assures we are keeping existing behaviour.
| addInstrDeplInfo(stationUri, interval, columns) | ||
| columnsOptV.sinkOption.map: columnsOpt => | ||
| StationTimeSeriesMeta(acq, prod, nRows, lblCoverage, columnsOpt) | ||
| StationTimeSeriesMeta(acq, prod, nRows, lblCoverage, columnsOpt.getOrElse(Nil)) |
There was a problem hiding this comment.
The option value columnsOpt here comes from parseJsonStringArray, where the None case signifies a parsing failure (a swallowed exception).
Hence, I don't see that there would be any semantic difference between Option and empty Seq here either.
| <br> | ||
|
|
||
| @for(variables <- varMetas){ | ||
| @if(varMetas.nonEmpty){ |
There was a problem hiding this comment.
Preserving original behaviour, so we do not show any preview if there are no items. There is a slight difference here from previous behaviour, which I argue is actually a bug-fix.
Previously, we could have a Some(Seq.empty), which would mean we show this section but without any items, which I believe is not the intention. With this change, we only ever show the section if there are items.
|
|
||
| def values: Try[Option[Seq[DatasetVar]]] = if(elems.isEmpty) Success(None) else Try{ | ||
| Some(elems.map(_.varInfo.get).toIndexedSeq) | ||
| def values: Try[Seq[DatasetVar]] = if(elems.isEmpty) Success(Nil) else Try{ |
There was a problem hiding this comment.
Here we clearly see that there is no semantic change from our perspective between Option and Seq, since we are explicitly turning the empty case into a None in the previous version.
| if (selsectedVars.nonEmpty) { | ||
| datasetSpec match { | ||
| case None => () | ||
| case Some(dataset) => { | ||
| whenDone(getVariables(dataset)) { variables => | ||
| varInfoForm.list = variables | ||
| varInfoForm.setValues(selsectedVars.flatMap(uri => | ||
| varInfoForm.list.find(_.uri.toString.split('/').last == uri) | ||
| )) | ||
| } | ||
| } |
There was a problem hiding this comment.
Another nonEmpty check I am not entirely sure is needed, but added to make sure we preserve prior behaviour.
I also changed the map call, since we are ignoring the result value and hence should use .foreach, but I personally think an explicit pattern match is much clearer.
| val nRowsQ = nRows.fold("")(nr => s"&nRows=$nr") | ||
| val varsQ = varnames.fold(""){vns => | ||
| val varsJson = encodeURIComponent(Json.toJson(vns).toString) | ||
| val varsQ = if (varnames.nonEmpty) { |
There was a problem hiding this comment.
Emptiness check to make sure we produce empty string instead of $varnames= when there are no vars.
jonathanthiry
left a comment
There was a problem hiding this comment.
In general, I agree with the idea. I don't know for sure why it was decided to use Option[Seq[T]] to start with. As you are hinting in your comments, these changes would require proper testing of the impacted code paths to avoid introducing any bugs, as it is not easy to judge this at a glance.
| extra <- memb.role.extra; | ||
| l2 <- dobj.specificInfo.toOption; | ||
| cols <- l2.columns | ||
| l2 <- dobj.specificInfo.toOption |
There was a problem hiding this comment.
It sounds like it could be a nice improvement. I cannot think of a reason why we are using Either other than union types didn't exist when this code was written.
Jonathan-Schenk
left a comment
There was a problem hiding this comment.
The changes you suggest make sense and improve the clarity of the code. I think it is a good idea to apply them after testing that nothing breaks because of them, although it seems that all current behaviors would be maintained.
| } | ||
|
|
||
| val variableMeasured = asOptArray(dobj.specificInfo.fold(_.variables, _.columns))( | ||
| val variableMeasured = asOptArray(dobj.specificInfo.fold((_.variables), _.columns))( |
There was a problem hiding this comment.
| val variableMeasured = asOptArray(dobj.specificInfo.fold((_.variables), _.columns))( | |
| val variableMeasured = asOptArray(dobj.specificInfo.fold(_.variables, _.columns))( |
| whenDone(getVariables(dataset)) { variables => | ||
| varInfoForm.list = variables | ||
| varInfoForm.setValues(Some(varUris.flatMap(uri => varInfoForm.list.find(_.uri.toString.split('/').last == uri)))) | ||
| if (selsectedVars.nonEmpty) { |
There was a problem hiding this comment.
This block can be unindented. The selsectedVars could also be renamed to selectedVars to remove the typo.
Wrapping a
SeqinOptionhas the same semantic meaning in almost every case I can think of, and in the cases where it does not, I think an alternative and more descriptive solution should probably be used.The changes in this PR should be functionally equivalent to the previous version, while simplifying the logic using these data types, but I have not tested anything in practice yet, so there may of course be edge cases I have not thought of.
I have left comments at relevant places for why I believe these changes are functionally equivalent and valid, though.
Note: Based on #341 since it touches same files.
Option[Seq[]]that should probably also be changed as part of this PR.Testing TODO
master, verifying existing behavior. Then the tests can be merged into this one.meta/upload/subforms. Most likely, the best way to test it is to manually check that the interface still works as expected.