[Main website]

dok/spec/nested-transactions-semantic

A Dok action can:

  • return a result
  • fail because a logical condition is not meet
  • raise an exception because there is an exceptional external problem

Failing is considered a normal state of computation and not an error, expecially when the computation involves persistent (i.e. rollbackable) values. The fail is managed using a (predictable) nested transaction semantic (TR):

  • all the effects of the actions executed before the fail are rollbacked
  • the caller is informed of the fail
  • the caller can manage the fail using another code path or it can fail itself

Semantic

False conditions

A code block testing a false condition fails.

var b1 False
check b1 == False

var e1 case {
  when b1
  #-NOTE this transaction fail, because `b1` return `False`

  return "a"
} -else {
  return "b"
}
check e1 == "b"

# Different code with same semantic
set e2 if b1 {
  return "a"
} -else {
  return "b"
}

try and case

try tries different branches, rollbacking in case of failure, until there is a branch that succeed. A try without a succesfull branch is not considered a failure.

case is similar to try, but if no branch succeed, then it is considered a failure.

In this example try will return a result.

var r try {
    when 10 > 100
    return "never returned"
  } else {
    when 10 > 50
    return "also this is nevere returned"
  } else {
    when 10 > 5
    return "this is the result"
  }

  assert r.isa(String) && r == "this is the result"

In this example, try has only side effects.

  var i::Int 0
  try {
    inc i
    when i == 1
    when 10 > 100
    #-NOTE failing condition, so everything is rollbacked
  } else {
    assert i == 0
    #-NOTE effects of previous branch are rollbacked

    when 10 > 50
    #-NOTE failing condition
  } else {
    set i 100
  }

  assert i == 100
}

Loops

Seedok/example/loops

Fail info

A failing block of code or function can add more info about the reason of failure. This info can be used for better error-reporting, e.g. in case of user input-validation. Note that on the contrary of exceptions these are logical failures expected in the code, and not exceptional one. They are similar to the the Haskell type Either SomeErrorInfo Result.

Fail info can be expensive to generate, so it will be effectively generated only if one of the caller is using it. Otherwise the compiler will disable its generation.

data Age -like Int

String.to(Age) {
  var r::Int 0
  repeat {
    with ch::Char -in self
    with i from 0
    with b::Int -from 1 -next b * 10

    var c::Int ch.ord - '0'.ord
    inc r b * c

    when c < 0 || c > 9 {
      fail "Unexpected char '${ch}' at position ${i}
      #-NOTE at the end of this code the fail is generated
    }
  }

  when r >= 150 {
    fail "Unreasonable age ${r}"
  }

  return r
}

try {
  "10ab".parseAge
} else {
  do println("Unable to parse age for reason: ${~fail.last}")
}

TODO Describe rolback of actions

TODO the rollback depends from the system TODO the rolback manager can decide the policy: signal an error; create a potentially expensive repair and rollback operation. TODO some value based code can be annotated and managed as non-rollbackable code, so faster code is produced, and in case of rollback an exception will be raised