abstract class Converter[A] extends AnyRef
An instance of this class describes the relationship between a Scala type A
, the corresponding ML type a
,
and the representation of values of type a
as exceptions in the object store. To support new types,
a corresponding Converter object/class needs to be declared.
We explain how a converter works using the example of IntConverter.
The first step is to decide which
Scala type and which ML type should be related. In this case, we choose scala.Int on the Scala side,
and int
on the ML side.
We declare the correspondence using the mlType method:
final object IntConverter extends MLValue.Converter[A] { override def mlType = "int" ... }
Next, we have to decide how decide how code is converted from an int
to an exception (so that it can be stored
in the object store). In this simple case, we first declare a new exception for holding integers:
isabelle.executeMLCodeNow("exception E_Int of int")
This should be done globally (once per Isabelle instance). Declaring two (even identical) exceptions with the
same name E_Int
must be avoided! See OperationCollection
for utilities how to manage this. (E_Int
specifically does not need to be declared since it is predeclared.)
We define the method valueToExn that returns the ML source code to convert an ML value of type int
to an exception:
final object IntConverter extends MLValue.Converter[A] { ... override def valueToExn(implicit isabelle: Isabelle, ec: ExecutionContext): String = "fn x => E_Int x" // or equivalently: = "E_Int" ... }
We also need to convert in the opposite direction:
final object IntConverter extends MLValue.Converter[A] { ... override def exnToValue(implicit isabelle: Isabelle, ec: ExecutionContext): String = "fn (E_Int x) => x" ... }
(Best add some meaningful exception in case of match failure, e.g., using boilerplate from MLValue.matchFailExn. Omitted for clarity in this example.)
Next, we need to write a function for retrieving integer values from the object store (method retrieve).
It gets a value : MLValue[Int]
as input and has to return a Future[Int]
containing the stored integer. In principle,
there are not restrictions how this is done but the simplest way is the following approach:
- Decide on an encoding of the integer
i
as a tree in the Data data structure. (In this case, simply DInt(i)
will do the trick.) - Define an MLRetrieveFunction
retrieveInt
that performs this encoding on the ML side, i.e., we need to write ML code for a function of typeint -> data
. (data
is the ML analogue of Data, see the documentation of Data.) - Retrieve the value by invoking
retrieveInt
(gives aFuture[Data]
) - Convert the resulting Data to an Int. That is:
final object IntConverter extends MLValue.Converter[A] { ... override def retrieve(value: MLValue[Int]) (implicit isabelle: Isabelle, ec: ExecutionContext): Future[Int] = { val retrieveInt = MLRetrieveFunction[Int]("fn i => DInt i") // compile retrieveInt for (data <- retrieveInt(value.id); // invoke retrieveInt to transfer from Isabelle DInt(long) = data) // decode the data (simple pattern match) yield long.toInt // return the integer } ... }
Note that val retrieveInt = ...
was written inside the function retrieve
for simplicity here. However,
since it invokes the ML compiler, it should be invoked only once (per Isabelle instance, like the
executeMLCodeNow above). See OperationCollection for an auxiliary class
helping to manage this.
Finally, we also need a function store that transfers an integer into the Isabelle object store. Again, the easiest way is to use the following steps:
- Define an encoding of integers as Data trees (we use the same encoding as before)
- Define an MLStoreFunction
storeInt
that decodes the data back to an int on the ML side, i.e., we need to write ML code for a function of typedata -> int
. - Encode the integer to be stored as Data
- Invoke storeInt to transfer the Data to ML and store the integer in the object store. That is:
final object IntConverter extends MLValue.Converter[A] { ... override def store(value: Int)(implicit isabelle: Isabelle, ec: ExecutionContext): MLValue[Int] = { val storeInt = MLStoreFunction[Int]("fn DInt i => i") // compile storeInt val data = DInt(value) // encode the integer as Data Ops.storeInt(data) // invoke storeInt to get the MLValue } }
Note that val retrieveInt = ...
was written inside the function retrieve
for simplicity here.
Like above for storeInt
, this should be done only once (per Isabelle instance).
This concludes the definition of the Converter. Finally, the converter should be made available as an implicit value. That is, we define in a suitable place
implicit val intConverter = IntConverter
so that intConverter
can be imported as an implicit where needed. (And if the converter
we constructed is not an object but a class taking other converters as arguments,
we instead write something like
implicit def listConverter[A](implicit converter: Converter[A]): ListConverter[A] = new ListConverter()(converter)
or similar.)
Notes
- Several Scala types can correspond to the same ML type (e.g., Int and
Long both correspond to
int
). - If the converters for two Scala types
A
,B
additionally have the same encoding as exceptions (defined via valueToExn, exnToValue in their Converters), then MLValue[A] and MLValue[B] can be safely typecast into each other. - It is not recommended to have the same Scala type
A
for two different ML typesa1
anda2
(or for the same ML type but with different encoding). First, this would mean that there have to be two different instances of Converter[A]
available (which means Scala cannot automatically choose the right one). Second, it means that one has to be manually keep track which Converter was used for which value (no type safety). - The attentive reader will notice that we use MLRetrieveFunction.apply and MLStoreFunction.apply
when defining the converter, but that these functions take the converter we are currently defining as an
implicit argument! However, this cyclic dependency is not a problem because MLRetrieveFunction.apply
and MLStoreFunction.apply never invoke store and retrieve from the converter, so we can call
those
apply
functions from store and retrieve. - In simple cases (when
A
is simply supposed to be a wrapper around a reference to a value in the Isabelle process, constructions of converters are simplified by using MLValueWrapper or AdHocConverter.
- A
the Scala type for which a corresponding ML type is declared
- Source
- MLValue.scala
- Alphabetic
- By Inheritance
- Converter
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Instance Constructors
- new Converter()
Abstract Value Members
- abstract def exnToValue(implicit isabelle: Isabelle, ec: ExecutionContext): String
Returns ML code for an (anonymous) function of type
exn -> a
that converts a value encoded as an exception back into the original value.Returns ML code for an (anonymous) function of type
exn -> a
that converts a value encoded as an exception back into the original value.It is recommended that this function produces informative match failures in case of invalid inputs. MLValue.matchFailExn is a helper function that facilitates this.
This function should always return the same value, at least for the same
isabelle
. - abstract def mlType(implicit isabelle: Isabelle, ec: ExecutionContext): String
Returns the ML type corresponding to
A
.Returns the ML type corresponding to
A
.This function should always return the same value, at least for the same
isabelle
. - abstract def retrieve(value: MLValue[A])(implicit isabelle: Isabelle, ec: ExecutionContext): Future[A]
Given an mlvalue.MLValue
value
, retrieves and returns the value referenced byvalue
in the Isabelle object store.Given an mlvalue.MLValue
value
, retrieves and returns the value referenced byvalue
in the Isabelle object store.Must not invoke
value.
retrieve orvalue.
retrieveNow because those functions invokethis.
retrieve. (But calling retrieve or retrieveNow on other MLValues is allowed as long as no cyclic dependencies are created.) - abstract def store(value: A)(implicit isabelle: Isabelle, ec: ExecutionContext): MLValue[A]
Given a
value : A
, transfers and storesvalue
in the Isabelle object store and returns an mlvalue.MLValue referencing the value in the object store.Given a
value : A
, transfers and storesvalue
in the Isabelle object store and returns an mlvalue.MLValue referencing the value in the object store.Must not invoke MLValue
(value)
because that functions invokesthis.
store. (But calling MLValue(...)
on other values is allowed as long as no cyclic dependencies are created.) - abstract def valueToExn(implicit isabelle: Isabelle, ec: ExecutionContext): String
Returns ML code for an (anonymous) function of type
a -> exn
that converts a value into its encoding as an exception.Returns ML code for an (anonymous) function of type
a -> exn
that converts a value into its encoding as an exception.It is recommended that this function produces informative match failures in case of invalid inputs. MLValue.matchFailExn is a helper function that facilitates this.
This function should always return the same value, at least for the same
isabelle
.
Concrete Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##: Int
- Definition Classes
- AnyRef → Any
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native() @HotSpotIntrinsicCandidate()
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native() @HotSpotIntrinsicCandidate()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native() @HotSpotIntrinsicCandidate()
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native() @HotSpotIntrinsicCandidate()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native() @HotSpotIntrinsicCandidate()
- final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])