Open zaphar opened 4 years ago
I feel the need of explicit strong types all over the place, but this may be just my bias towards strong type checking. Inferred types is definitely a nice feature but I would like to enforce explicit types wherever I see fit. For example:
Imagine that I have two variables holding Float
values: weight
and height
. I would like to enforce the correct order when I pass these variables to a function. At the moment, both f(weight, height)
and f(height, weight)
compile fine whilst, ideally, the compiler should be warning us that we've passed arguments in the wrong order.
However, if I declare hypothetically something like:
type Weight = Float
type Height = Float
let weight: Weight = 72.5
let height: Height = 1.76
let f = func(height: Height, weight: Weight) { ... }
... the compiler would be able to bark warning me when I pass variables in the wrong order.
I tend to like a mix. I like to infer it where I can but I also like to be explicit about the types as well. So I want to be able to infer when there is no explicit type constraint defined but I also want to be able to specify a constraint for the contract when it's useful.
Just playing around with some syntax here. I'm not completely sold on any of it yet but I need to get the first ideas out of my system.
UCG types are named Shape
s for a ucg value. The defined shape is also the 0 value for that Shape.
shape Width 0.0;
shape Height 0.0;
Any expression can be projected into a shape. If the Shape is a structural subset of the result of the expression then the projection succeeds. If the shape is not a structural subset then the projection fails and a ShapeError will be thrown.
0.0 :> Width;
Compound Shapes can have their components projected into previously defined Shapes.
shape Box = {
width = 0.0 :> Width,
height = 0.0 :> Height,
};
Let statements can have an optional type specifier. If the RHS expression can't be projected into the shape defined by the type specifier then the compiler will throw a ShapeError. If the RHS can be projected into the Shape then the value will have the named shape as their Shape.
let w: Width = 0.0;
let h: Height = 0.0;
Function arguments can have an optional type specifier. Type specifiers check that the arguments passed in are the correct Shape. If the argument is not the same Shape then the compiler will throw a Shape Error.
let make_box = func(w : Width, h : Height) => {width: w, height: Height} :> Box;
Module arguments can use Shapes as their default values. If no argument is passed in then the zero value for the shape is used. If values passed in are not of the same Shape then a ShapeError will be thrown.
let mymod = module{w=Width, h=Height} => (result :> Box) {
let result = make_box(mod.w, mod.h);
};
The default shape for a value is ANY
; All values fit into the ANY
Shape.
The primitive values all have predefined shapes. int
float
str
list
tuple
.
I'm sorry for my long silence.
The defined shape is also the 0 value for that Shape.
Are you talking about monoids? Just curious. Please do not spend much effort on this question: it would be just distraction.
Regarding the trials with the syntax:
I'm confused since apparently there are different ways for expressing the same idea, which is: associate an explicit type to a variable.
One way is similar to several programming languages:
let w: Width = 0.0;
These below seem to be variations of another way:
shape Width 0.0;
shape Box = { ... }
And there's the general form value :> shape
which appears on snippets like:
width = 0.0 :> Width;
{width: w, height: Height} :> Box;
(result :> Box)
I'm puzzled whether or not there's a deep reason behind these variations. Maybe I'm just missing something.
You explained the role of ':>' which is similar, if not the same, as it is in Scala, and I feel familiar with it. having the bias to interpret ':>' as variance (as opposed to invariance or contravariance) but I'm not plenty sure if I understood your intentions properly. Besides, I'm not fully skilled on ucg.
My suggestion (or concern) is related to various slight different ways involved to express basically the same concept. If there's only one general form which could fit into various contexts, this would make the syntax easier to grasp and understand.
the shape
syntax defines the type instead of associating a type to a variable. The :>
construction was a way of post-hoc asserting that a variable fits into a given shape. the result would be a new value with that shape as it's type.
This is just the first pass though to get the bad ideas out of my system :-D
As to the first question no it wouldn't fit the definition of a monoid since there is no defined sequence there. It's more like the default value. It's useful for the module construction syntax.
Just playing around with some syntax here. I'm not completely sold on any of it yet but I need to get the first ideas out of my system.
Structural Typing with Shapes
UCG types are named
Shape
s for a ucg value. The defined shape is also the zero value for that Shape.Shapes can be used as values. If assigned to a binding in a let statement they will be copied and the named binding will have that Shape as it's type.
Any expression can be projected into a shape. If the Shape is a structural subset of the result of the expression then the projection succeeds. If the shape is not a structural subset then the projection fails and a ShapeError will be thrown.
The individual components of Compound Shapes can have an optional shape specifier. If no shape is associated with a component part then the default shape for the value is the shape for that component.
List shapes specify all the allowed types in the list.
Let statements can have an optional shape specifier. If the RHS expression can't be projected into the shape defined by the type specifier then the compiler will throw a ShapeError. If the RHS can be projected into the Shape then the value will have the named shape as their Shape.
Function arguments can have an optional type specifier. Type specifiers check that the arguments passed in are the correct Shape. If the argument is not the same Shape then the compiler will throw a Shape Error.
Module arguments can use Shapes as their default values. If no argument is passed in then the zero value for the shape is used. If values passed in are not of the same Shape then a ShapeError will be thrown.
The default shape for a value is
ANY
; All values fit into theANY
Shape.The primitive values all have predefined shapes.
int
float
str
list
tuple
.Row Based Polymorphism with Anonymous Shapes
Using anonymous shapes allows us to accept any value that matches the same shape.