dex-lang
dex-lang copied to clipboard
Enforce the constraints implied by the new type-parameter role system
See the description of #1138 for the two invariants.
Specifically, quoting #1138 :
- instances must be parametric over all data arguments. You can't define an instance like Ix (Fin 10). It has to be {n:Nat} (Ix (Fin n)) instead.
- type constructor parameters must be one of type/data/dict, so we can't have, e.g., functions as type constructor parameters.
2 is blocked on having a Data
constraint: #1150
1 can be done any time. One way to do it: when we see an instance, like ixbad : Ix (Fin 10)
or ixgood : (n:Int) -> Ix (Fin n)
, we instantiate it with fresh inference vars and unify the result with the class applied to skolem parameters. So for ixbad
, we'd end up unifying Fin 10
with Fin skolem
, which would fail. Whereas for ixgood
, we'd unify Fin ?1
with Fin skolem
, which would succeed with ?1 -> skolem
.
I don't think this is important, but FYI I found a use for functions as type constructor parameters. Specifically, for defining Poisson processes:
data PoissonProcess =
MkPoissonProcess (Float -> LogSpace Float) (Float) (Float) -- rate function, t0, t1
instance Random PoissonProcess (List Float)
draw = \(MkPoissonProcess rate t0 t1) k.
sample_poisson_process rate t0 t1 k
I was surprised that this works, and I guess I was right to be, since it sounds like you're planning to disallow this?
In any case, I wouldn't block anything to save this little piece of code.
If you're curious, here's the entire example, that runs:
import stats
'# Poisson Processes
def get_last {n a} (xs:n=>a) : Maybe a =
case size n > 0 of
True -> Just xs.(unsafe_from_ordinal _ (unsafe_nat_diff (size n) 1))
False -> Nothing
def logspace_1d_cumsum (n:Type) [Ix n]
(rate: Float -> LogSpace Float) (t0:Float) (t1:Float) :
(n=>Float & n=>LogSpace Float & n=>LogSpace Float & LogSpace Float) = -- rates, cumulative rates, total
rate_eval_locations = linspace n t0 t1
dt = log $ (t1 - t0) / n_to_f (size n) -- -1?
log_rate_evals = for i. (Exp dt) * rate rate_eval_locations.i
cs = cumsum log_rate_evals -- todo: log cumsum exp?
total = case get_last cs of
Just total -> total
Nothing -> zero
(rate_eval_locations, log_rate_evals, cs, total)
def sample_poisson_process (rate: Float -> LogSpace Float) (t0:Float) (t1:Float) (key:Key) : List Float =
(rate_eval_locations, log_rate_evals, cs, total) =
logspace_1d_cumsum (Fin 100) rate t0 t1
N = draw (Poisson (ls_to_f total)) key
locs = for i.
ix = categorical (map ln log_rate_evals) (ixkey key i)
rate_eval_locations.ix -- todo: add interpolation?
AsList N locs
def poisson_process_density (rate: Float -> LogSpace Float) (t0:Float) (t1:Float) (xs:List Float) : LogSpace Float =
(rate_eval_locations, log_rate_evals, cs, total) =
logspace_1d_cumsum (Fin 100) rate t0 t1
(AsList N xtab) = xs
poisson_pmf = density (Poisson (ls_to_f total)) N
loc_density = sum for i.
bin_ix = from_just $ search_sorted rate_eval_locations xtab.i
log_rate_evals.bin_ix
loc_density + poisson_pmf
data PoissonProcess =
MkPoissonProcess (Float -> LogSpace Float) (Float) (Float)
instance Random PoissonProcess (List Float)
draw = \(MkPoissonProcess rate t0 t1) k.
sample_poisson_process rate t0 t1 k
instance Dist PoissonProcess (List Float) Float
density = \(MkPoissonProcess rate t0 t1) x.
todo
import plot
def example_rate (x:Float) : (LogSpace Float) =
Exp (-2.0 - 3.0 * sin (3.0 * x))
t0 = -2.0
t1 = 1.4
num_evals = 100
eval_spots = (Unit | Fin (unsafe_nat_diff num_evals 1))
rate_eval_locations = linspace eval_spots t0 t1
rate_evals = map (\x. (ls_to_f $ example_rate x) / n_to_f num_evals) rate_eval_locations
:html show_plot $ xy_plot rate_eval_locations rate_evals
pp = MkPoissonProcess example_rate t0 t1
(AsList _ vs) : List Float = (draw pp (new_key 0))
vs
def myfunc (r:Float) : Float =
example_rate = \x:Float. Exp (x - 10.0 * sin (3.0 * x))
t0 = -2.0
t1 = 3.4
pp = MkPoissonProcess example_rate t0 t1
(AsList _ vs) : List Float = (draw pp (new_key 0))
vs.(0@_)
grad myfunc 1.0
Oh, that's definitely allowed! In your example, the function is an argument to the data constructor, MkPoissonProcess
, rather than the type constructor, PoissonProcess
.
Thanks for explaining, I hadn't understood the distinction between 'type constructor' and 'data constructor'.