package control
This package contains classes for instantiating and controlling an Isabelle process. The main class is Isabelle, see there for documentation. Controlling the process via the classes in this package is quite low-level. See mlvalue.MLValue for a higher level mechanism.
- Source
- package.scala
- Alphabetic
- By Inheritance
- control
- AnyRef
- Any
- Hide All
- Show All
- Public
- All
Type Members
-
class
DefaultExceptionManager extends ExceptionManager
Default implementation of ExceptionManager.
Default implementation of ExceptionManager. Produces messages for all exceptions by pretty printing them in Isabelle with a
Purecontext. Generated exceptions are always IsabelleMLException instances (no exception specific subclasses).See de.unruh.isabelle.pure.Exn.ExceptionManager for an alternative ExceptionManager that supports subclasses.
-
trait
ExceptionManager extends AnyRef
Handles the conversion of exceptions from the Isabelle process (in ML) to Scala/JVM exceptions.
Handles the conversion of exceptions from the Isabelle process (in ML) to Scala/JVM exceptions. An ExceptionManager is always associated with a given Isabelle instance. You can set the ExceptionManager using the de.unruh.isabelle.control.Isabelle.SetupGeneral.exceptionManager option. The current instance can be accessed as Isabelle.exceptionManager. Own implementations can start by subclassing DefaultExceptionManager (but do not have to).
-
class
Isabelle extends FutureValue
A running instance of Isabelle.
A running instance of Isabelle.
The Isabelle process is initialized with some ML code that allows this class to remote control Isabelle. In more detail:
The Isabelle process maintains a map from IDs to values (the "object store"). Those values can be of any type (e.g., integers, terms, functions, etc.). (How this works in a strongly typed language such as ML is described below.) The Isabelle class has functions to operate on the values in the object store (e.g., creating new objects, retrieving the value of an object, performing operations on objects).
The operations provided by this class are very lowlevel. For more convenient and type-safe operations on values in the object store, see de.unruh.isabelle.mlvalue.MLValue.
Operations on objects are asynchronous and return futures.
On the Scala side, the IDs of objects are represented by the class de.unruh.isabelle.control.Isabelle.ID. These IDs ensure garbage collection – if an ID is not referenced any more on the Scala side, it will be removed from the object store in the Isabelle process, too.
To be able to store objects of different types in the object store, even though ML does not support subtyping, we make use of the fact that all exceptions share the same ML type
exn. The object store stores only values of typeexn. To store, e.g., integers, we define an exceptionexception E_Int of int. Then for an integeri,E_Int iis an exception that can be stored in the object store. We can convert the exception back to an integer using the functionfn E_Int i => ithat uses pattern matching. (This will raiseMatchif the given exception is does not contain an integer. This way we achieve dynamic typing of our object store.) The following exceptions are predefined in structureControl_Isabelle:exception E_Function of exn -> exn exception E_Int of int exception E_String of string exception E_Pair of exn * exn
(That structure also exports functions
storeandhandleLineswhich are for internal use only and must not be used in the ML code.)Note that some of the exception again refer to the exn type, e.g.,
E_Pair. Thus, to store a pair of integers, we use the termE_Pair (E_Int 1, E_Int 2).New exceptions for storing other types can be defined at runtime using executeMLCode.
-
case class
IsabelleBuildException(message: String, errors: List[String]) extends IsabelleControllerException with Product with Serializable
Thrown if the build process of Isabelle fails
-
abstract
class
IsabelleControllerException extends Exception
Ancestor of all exceptions specific to Isabelle
-
case class
IsabelleDestroyedException(message: String) extends IsabelleControllerException with Product with Serializable
Thrown if an operation cannot be executed because Isabelle.destroy has already been invoked.
-
case class
IsabelleJEditException(message: String) extends IsabelleControllerException with Product with Serializable
Thrown if running Isabelle/jEdit fails
-
class
IsabelleMLException extends IsabelleControllerException
Thrown in case of an error in the ML process (ML compilation errors, exceptions thrown by ML code)
- case class IsabelleMiscException(message: String) extends IsabelleControllerException with Product with Serializable
-
case class
IsabelleProtocolException(message: String) extends IsabelleControllerException with Product with Serializable
Thrown in case of protocol errors in Isabelle process
-
trait
OperationCollection extends AnyRef
This is a utility trait for handling a common use case when working with Isabelle.
This is a utility trait for handling a common use case when working with Isabelle. We illustrate this with an example:
Say we want to create a library with functions that can operate on floating point numbers on the Isabelle side. (For simplicity, in this example we will provide only on operation: converting strings to reals.) For this, we need to declare an exception type
E_Realfor storing reals (ML code:exception E_Real of real) and we need to compile and store the code converting strings to reals.The very first approach to this problem would be the following:
object Real { private val isabelle : Isabelle = ??? isabelle.executeMLCodeNow("exception E_Real of real") private val fromStringID : Future[Isabelle.ID] = // Converts 'E_String str' into 'E_Real real' isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))") def fromString(string: String)(implicit ec: ExecutionContext) : Future[Isabelle.ID] = for ( strId <- isabelle.storeString(string); fromStr <- fromStringID; real <- isabelle.applyFunction(fromStr, strId)) yield real }
Here
isabelle.executeMLCodeNowsets up the required exception type, andfromStringIDcontains the (ID of the) compiled ML code for converting strings to ints. AndfromStringis the user-facing function converting a string to an ID of a real (on the ML side).The problem here is that to we need an instance of Isabelle to perform those operations. In the example above we simply wrote
val isabelle = ???because we did not know where to get it from. An obvious solution would be to makeReala class with anisabelle: Isabelleparameter. However, that would make it less convenient to useReal: We need to explicitly create theRealand keep track of it. Especially if the code that is responsible for theRealinstance is not the code that creates the Isabelle instance, this might be compilcated.A more user-friendly solution is therefore to keep
Realas an object and to pass the Isabelle instance as an implicit parameter toReal.fromString. However, this meansisabelleis not available outsideReal.fromString, so we have to perform the initialization insidefromString:object Real { def fromString(string: String)(implicit isabelle: Isabelle, ec: ExecutionContext) : Future[Isabelle.ID] = { isabelle.executeMLCodeNow("exception E_Real of real") val fromStringID : Future[Isabelle.ID] = isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))") for ( strId <- isabelle.storeString(string); fromStr <- fromStringID; real <- isabelle.applyFunction(fromStr, strId)) yield real } }
This has two problems, however: First, the initialization code is executed every time when
fromStringis executed. Since this involves invoking the ML compiler each time, this should be avoided. (Rule of thumb: ML code should only occur in one-time initializations.) Even worse: by executing the ML codeexception E_Real of real, we actually create a different incompatible exception typeE_Realeach time (and override the name space elementE_Realeach time). This will lead to failing code (at least if we would extend our example to actually use the created real values). Also, in more complex examples, we might want several functions (not justfromString) to share the same setup. To achieve this, we need global variables to track whether the initialization code has already been executed and to storefromStringID, and – if we don't want our code to fail in the presence of several simultaneous instances of Isabelle – keep track for which instances of Isabelle the initialization has happened already.All this is made easy by the OperationCollection trait. The above code can be rewritten as follows:
object Real extends OperationCollection { override protected def newOps(implicit isabelle: Isabelle, ec: ExecutionContext): Ops = new Ops() protected class Ops(implicit val isabelle: Isabelle, ec: ExecutionContext) { isabelle.executeMLCodeNow("exception E_Real of real") val fromStringID : Future[Isabelle.ID] = // Converts 'E_String str' into 'E_Real real' isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))") } def fromString(string: String)(implicit isabelle: Isabelle, ec: ExecutionContext) : Future[Isabelle.ID] = for ( strId <- isabelle.storeString(string); fromStr <- Ops.fromStringID; real <- isabelle.applyFunction(fromStr, strId)) yield real }
Note that we have defines an inner class Ops that performs the initialization and may depend on the Isabelle instance
isabelle. Yet we use it (infromStr <- Ops.fromStringID) as if it were an object. The trait OperationCollection makes this possible. Under the hood,Opswhen used like an object is a function with implicit parameters that creates a newclass Opsinstance only when needed (i.e., when a previously unknown Isabelle instance is used).In general, OperationCollection is used with the following boilerplate:
object ObjectName extends OperationCollection { override protected def newOps(implicit isabelle: Isabelle, ec: ExecutionContext): Ops = new Ops() protected class Ops(implicit val isabelle: Isabelle, ec: ExecutionContext) { // arbitrary initialization code that is specific to the Isabelle instance `isabelle` } // code that uses Ops like an object }
Note the following: - Ops must be not be called differently - In
protected class Ops,protectedcan be replaced by something weaker if the operations should be accessible outside the current object (e.g.protected[packagename]) - WhenObsis used like an object, implicit of types Isabelle and scala.concurrent.ExecutionContext must be in scope - The functionnewOpsmust be defined exactly as specified here
Value Members
- object Isabelle
- object IsabelleDestroyedException extends Serializable
- object IsabelleMLException extends Serializable