This package provides classes and functions that deal with the lesser pure parts of Frege - Exceptions, Threads, mutable data.
This package is implementation specific insofar as the compiler may assume that certain items are defined here in a certain way. Changes may thus lead to compiler crashes or java code that will be rejected by the java compiler.
In particular, derived Exceptional instances will reference type class Exceptional.
Make the java.lang.Class object corresponding to the instantiated type available.
An instance for this type class is needed for the element type of JArrays (which are based on reflective true Java arrays) one needs to create.
A subclass of this is Exceptional which adds no additional functionality and behaves basically just as a marker interface for exceptions.
JavaType instances are derivable for all data types.
NoMatch, GuardFailed, Exception, Undefined
The java.lang.Class object of the instantiated type
Function catch requires that the argument of a handler function is an instance of Exceptional.
This is derivable for pure native data types.
nowarn: argument of type (ST s a)
Runtime method for implementation of catch
The construct
action `catch` handler
is a ST action with the same type as action.
If action yields a result, this will be the result of the overall action. However, if, during execution of action the JVM raises an exception e with java type E, and E is a subtype of java type H, and H is the java type associated with the argument of handler, the return value will be:
handler e
Otherwise, if the type of e does not allow to pass it to handler it will be propagated upwards and a `catch` b will not return to its caller.
Because catch is left associative, it is possible to catch different exceptions, like in:
action `catch` handler1 `catch` handler2
Care must be taken to check for the most specific exception first. In the example above, if the exception handled by handler1 is less specific than the one handled by handler2, then handler2 will never get a chance to execute.
Another way to put this is to say that if E1 and E2 are distinct exception types handled in a chain of catches, and E1 is (from the point of view of Java!) a subtype of E2, then the handler for E1 must appear further left than the handler for E2. If it is a super type of E2, however, its handler must appear further right. And finally, if the types do not stand in a sub-type relationship, the order of the handlers is immaterial.
Note If action is of the form:
doSomething arg
then, depending on the strictness of doSomething the argument arg may be evaluated before the action is returned. Exceptions (i.e. undefined values) that occur in the construction of the action do not count as exceptions thrown during execution of it, and hence cannot be catched.
Example:
println (head []) `catch` ....
will not catch the exception that will be thrown when println evaluates
For a remedy, see try.
nowarn: argument of type (ST s a)
The construct
action `finally` always
returns the same value as action, when executed.
However, no matter if action produces a value or diverges (for example, by throwing an exception), in any case will always be executed, and its return value dismissed.
Note that finally only returns to its caller if action would have done so.
finally has the same fixity as catch, hence it is possible to have
action `catch` handler1 `catch` handler2 `finally` always
Deliberately throw an exception in the ST monad.
Deliberately throw an exception in the IO monad.
Make sure that exceptions thrown during construction of an action can be catched. See catch for an explanation.
Example:
try println (head []) `catch` (\u::Undefined -> println u.catched)
should print:
frege.runtime.Undefined: Prelude.head []
try does work for unary functions only. To be safe with functions taking more actions, use:
pure a >>= (\a -> pure b >>= (\b -> f a b))
The catchAll function runs a ST action and returns either the result or the exception thrown.
Mutable is a wrapper for native data types. A value of type Mutable s x is really an x, but it is tied to the ST thread s, from which it cannot escape.
The compiler will enforce the following rules:
To understand the motivation for rule 5, observe that the Mutable.Mutable data constructor cannot be applied in Frege code, hence the only possibility to obtain a value of type Mutable s t is through a native function. But by rule 4 above, this could only happen in the ST or IO monad, from whence those values cannot escape. The ST monad is a context where the sequence of actions matters. If we allowed passing mutable data to pure functions, their results would depend on whether ST actions modified the value before the result is actually evaluated.
Although in a strict sense, no pure function should get mutable data, rule 5 is only enforced for native pure functions, as normal frege functions couldn't do anything with the native value, except passing them on to native functions eventually.
There will be means to get a read-only copy (Freezable.freeze) of a mutable value through class Freezable.
There is also the possibility that, although a value is mutable, there are certain properties that cannot change (such as the length of an array). It will be possible to bypass rule 5 in such cases.
To summarize:
Implementation note: It is assumed that Mutable is a newtype.
obtain a read-only copy of a mutable value through cloning or serialization.
Apply a pure function to a mutable value that pretends to be read-only.
The function must not rely on anything that could change in the mutable data!
obtain a mutable copy of a read only value through cloning or serialization.
Alias for Mutable.readonly
Obtain a read only native value (temporarily) typed as Mutable
For example, this could be needed when running a native ST method that uses the value as read-only, but we can't declare it, like:
private native copyOf java.util.Arrays.copyOf :: ArrayOf s HashMap -> Int -> ST s (ArrayOf s HashMap)
They type for mostly mutable values that are tied to the IO monad.
For java types that are mutable only so that they always would occur wrapped into MutableIO, the convention is to declare them as
data Thing = mutable native org.mut.impure.Thing
and just write Thing everywhere. The type checker will check the rules for native functions as if Thing was MutableIO Thing.
However, normal type unification does not take the mutable status into account, so Mutable a m will never unify with Thing.
They type of IO actions that return a mutable value of type d
This is an abbreviation for ST RealWorld (Mutable RealWorld d)
They type of ST actions that return a mutable value of type d
This is an abbreviation for ST s (Mutable s d)
Type class for mutable values that support making read-only copies. To be implemented with care.
"Freeze" a mutable native value. The result is supposed to be immutable or at least not reachable from other parts of the code, especially from java code.
The most prominent way to freeze a value is by Cloneable.clone-ing it, if that is supported. But note that sometimes a deep copy would be needed, and that clone does not do that.
The inverse of Freezable.freeze creates a value (an object) which can be passed to impure functions without compromising the frozen object passed as argument.
One possibility to thaw an object is by cloning it.
If Freezable.thaw is not implemented correctly, bad things may happen.
For a data type declared like
data D = native Javatype
where Javatype implements the java.lang.Cloneable interface, one can get implementations for Freezable.freeze and Freezable.thaw by just stating
instance Cloneable D
The Freezable.freeze and Freezable.thaw operations are implemented in terms of Cloneable.clone.
Note: Cloning does not produce safe copies if the cloned object contains references to mutable objects. In such cases, sort of a deep cloning would be required.
clone v must be a native method that works like java.lang.Object#clone.
For a data type declared like
data D = native Javatype
where Javatype implements the java.io.Serializable interface, one can get implementations for Freezable.freeze and Freezable.thaw by just stating
instance Serializable D
The Freezable.freeze and Freezable.thaw operations are implemented in terms of Serializable.copySerializable, which serializes its argument to a byte array and creates a new copy by deserializing it from the byte array.
copySerializable v is supposed to be a native function that is implemented by frege.runtime.Runtime.copySerializable at the instantiated type.
make a safe copy through serialization/deserialization
make a safe copy through serialization/deserialization
A mutable reference, suitable for use in the ST monad.
get the value the reference is pointing to
modify the referenced value with a function
create a reference that is initially set to the argument value
assign another value to the reference
Haskell compatibility
Alias for Ref.get
Alias for Ref.put
Alias for Ref.modify
Alias for Ref.new
Serializable.thaw, Serializable.copySerializable, Serializable.freeze