Skip to content

Conversation

@hkir-dev
Copy link
Contributor

In relation with the issue: INCATools/dead_simple_owl_design_patterns#71
and relatedly updated ontology schema, implementations to add multi-clause printf and internal_vars.

With this implementation:

  • internal_vars that enable manipulating vars (applying some functions upon) and introducing as new internal variables. Currently 'join' function is implemented that joins list_var or data_list_var elements with a specified separator.
  • multi_clause_printf that enable defining print format string as multiple interrelated sub-strings (clauses). Aim is to:
    • Clauses to define optional parts of the print format string
    • Define clause dependencies (sub-clauses) whose existence depends on the existence of the parent clause.

Backing usecase is obophenotype/brain_data_standards_ontologies#98

@hkir-dev hkir-dev requested review from balhoff and dosumis June 15, 2021 15:47
@dosumis
Copy link

dosumis commented Jun 16, 2021

@balhoff - would be great if you could take a look soon. Thanks!

@balhoff
Copy link
Member

balhoff commented Jun 18, 2021

Thanks! I'll try to get it done. Awesome to get a contribution!

Copy link
Member

@balhoff balhoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hkir-dev I just made a few quick style comments, before trying out the functionality. One general thing, please use camelCase variables everywhere, except specifically in DOSDP fields that get turned to JSON.

def replaced(text: String, vars: Option[List[String]], bindings: Option[Map[String, SingleValue]], quote: Boolean): Option[String] = {
def replaced(text: Option[String], vars: Option[List[String]], multi_clause: Option[MultiClausePrintf], bindings: Option[Map[String, SingleValue]], quote: Boolean): Option[String] = {
if(text.isDefined) {
return replace_text(text, vars, bindings, quote)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All returns removed from code.

None
}

private def replace_text(text: Option[String], vars: Option[List[String]], bindings: Option[Map[String, SingleValue]], quote: Boolean) = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like text should be String, not Option[String] (handle missing text outside of this method). Also, I like to add type signatures to method definitions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the new schema text or multi_clause can be used to define printf formats.

    oneOf:
      - required: [ text, vars ]
      - required: [ multi_clause ]

Text is not a mandatory field now, we can use multi_clause instead of text.

Comment on lines 101 to 118
if(text.isDefined) {
return replace_text(text, vars, bindings, quote)
} else if (multi_clause.isDefined) {
val replaced_texts = for {
multi_clauses <- multi_clause.toSeq
printf_clauses <- multi_clauses.clauses.toSeq
printf_clause <- printf_clauses
printf_clause_text <- replaced(Some(printf_clause.text), printf_clause.vars, None, bindings, quote)
sub_clauses = printf_clause.sub_clauses.getOrElse(List.empty[MultiClausePrintf])
sub_clauses_texts = sub_clauses.flatMap(mc => replaced(None, None, Some(mc), bindings, quote))
clause_text = (printf_clause_text :: sub_clauses_texts).mkString(multi_clauses.sep.getOrElse(" "))
} yield clause_text
val result = replaced_texts.filter(_.nonEmpty).mkString(multi_clause.get.sep.getOrElse(" "))
val trimmed = result.trim
if (trimmed.isEmpty) return None else return Some(trimmed)
}
None
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the optionality of text should be handled outside of this method, and you could break this into two separate methods? I would not use isDefined (they should remove it from the Option type); I prefer an expression-oriented style.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaceMultiClause method extracted. isDefined bassed logic replaced with flatMap and list operations.

annotations: Option[List[Annotations]],
axiom_type: AxiomType,
text: String,
text: Option[String],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this changed in the spec? Just want to make sure!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 80 to 86
val internalVarBindings = (for {
internalVars <- dosdp.internal_vars.toSeq
internalVar <- internalVars
function <- internalVar.apply.toSeq
value = function.apply(Some(dataListBindings.getOrElse(internalVar.input, listVarBindings.getOrElse(internalVar.input, MultiValue(Set.empty[String])))))
if value.isDefined
} yield internalVar.var_name -> SingleValue(value.get) ).toMap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, avoid isDefined and get (I didn't test this):

val internalVarBindings = (for {
        internalVars <- dosdp.internal_vars.toSeq
        internalVar <- internalVars
        function <- internalVar.apply.toSeq
        value <- function.apply(Some(dataListBindings.getOrElse(internalVar.input, listVarBindings.getOrElse(internalVar.input, MultiValue(Set.empty[String])))))
      } yield internalVar.var_name ->  SingleValue(value) ).toMap

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended logic worked perfectly, changed accordingly.


final case class JoinFunction(join: Join) extends Function {
override def apply(input_var:Option[Binding]): Option[String] = {
val joined_values = input_var.getOrElse(MultiValue(Set.empty[String])).asInstanceOf[MultiValue].value.mkString(join.sep)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be written without casting (asInstanceOf)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asInstanceOf based logic re-implemented with collect { case a: MultiValue => a }


final case class RegexFunction(regex: RegexSub) extends Function {
override def apply(input_var:Option[Binding]): Option[String] = {
val applied_value = input_var.getOrElse(SingleValue("")).asInstanceOf[SingleValue].value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be written without casting (asInstanceOf)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asInstanceOf based logic re-implemented with collect { case a: SingleValue => a }

@hkir-dev hkir-dev requested a review from balhoff June 28, 2021 13:39
@balhoff
Copy link
Member

balhoff commented Jul 1, 2021

Looks great, thank you for also writing tests!

@balhoff balhoff merged commit f5e2050 into master Jul 1, 2021
@balhoff balhoff deleted the multi_clause_printf branch July 1, 2021 17:42
@dosumis dosumis mentioned this pull request Jul 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants