This looks very much like Haskell numeric type handling. So part of this
message talks about what that looks like, but mostly because I want to
comment on it wrt some of the stuff you said - hopefully you can pick up
the syntax.
That has 'type annotations' on expressions (including on constants). These
are type declarations but you can make them almost anywhere rather than
specifically on a variable declaration.
So you write:
32
and that actually means '32 in whatever numeric type is needed'
eg. type this into GHC:
$ ghci
Prelude> :t 32 -- what is the type of 32?
32 :: (Num t) => t
means 32 has unknown type, represented by some type variable t, but it is
the case that t is a Num(ber) type.
So now when you ask about +:
Prelude> :t (+)
(+) :: (Num a) => a -> a -> a
That means + is an operator that takes two parameters of a number type a
and returns a value of that number type. Note its a -> a -> a, not
So that means all the number types are the same. You can't
add a float and an int, even though both float and int are number types -
because when we call +, either a = Int or a = Float.
We could have a different operator, fuzzyAdd with type:
(Num a, Num b, Num c) => a -> b -> c
which *could* add a float and an int, and return something of potentially
a third number type. But thats not what we want in stupid.
> I've left unaddressed the problem of bitwidthds of constants, and
> alongside that we have the very clunky operator syntax. Not to mention
> the pain of implementation of all those operators.
>
> It seems to me that introducing a bitwidth operator, _, that works on
> (pretty much) anything gives a clean way to proceed. This would also
> allow us to write operators a little more sanely (e.g. "plus32" would be
> "+_32").
OK. The haskell syntax uses :: as a type annotation:
32 :: Float
means that the expression on the LHS has type float.
Prelude> :t (32 :: Float)
(32 :: Float) :: Float
So _ in your description is close to :: for numeric constants, and
anything that represents a value.
So this can be used like this:
Prelude> (32 + 8) :: Float
40.0
Prelude> (32 + 8) :: Int
40
In the above two cases, a different + is being used - the first for
add-floats, and the second, add-ints.
That behaviour is the same as you propose.
Also:
Prelude> ( (32 ::Float) + (8 ::Float))
40.0
but:
Prelude> ( (32 ::Float) + (8 ::Int))
<interactive>:1:18:
Couldn't match expected type `Float' against inferred type `Int'
In the second argument of `(+)', namely `(8 :: Int)'
In the expression: ((32 :: Float) + (8 :: Int))
In the definition of `it': it = ((32 :: Float) + (8 :: Int))
So far this is almost identical to what you describe.
The Haskell syntax for forcing a particular + is different - its more
general than +_type syntax
Prelude> ((+) :: Float -> Float -> Float) 5 5
10.0
So +_32 is much more concise.
So then what about widen / narrow?
Maybe they're the same function, as you suggest by saying:
> 3. Given 2, we could then write both "widen a" and "narrow a" as
> a_<bitwidth> safely.
widen has two type variables, the input type, and the output type. In
Haskell syntax: widen :: (Num a, Num b) => a -> b
Saying (expr)_type here in your notation:
(257 & 0xff)_8
means (in Haskell notation):
(widen (257 & 0xff)) :: uint8
which givens an implicit widen, and binding the output type.
A different interpretation, the Haskell interpretation of the expression:
(257 & 0xff)) :: uint8
makes 257 and 0xff be uint8s (and then would give an error later on that
257 can't be a uint8), like this:
the expression is uint 8. so for the operator, & :: a->a->a we know
a=uint8, so then we know 257 and 0xff are both uint8s.
So whats the difference between the two, in the case of stupid being used
in real life rather than contrived examples?
Mostly, I think: the haskell way requires an explicit resize operation
when you have something that is known to be one size that you know want to
be a different size. It doesn't use the same syntax (_) for "I'm telling
you what this is" vs "I'm telling you I want you to convert this to a
different size". Maybe that difference is worth making explicit in the
language.
explicit-resize could still be an operator, but using a different symbol.
> 2. The narrow operator should fault if the bits it discards are non-zero.
> 5. Thinking about it, sign probably has to be part of the bitwidth. So
> we'd actually write:
yes.
Thinking about whats going inside the bitfields, then there are a bunch of
types that aren't in a strict linear order.
u32 can hold anything a u8 can hold so a widen always works. Sometimes you
can narrow a u32 to a u8, but you won't know until runtime. So u32 is
strictly bigger than u8 and the words 'narrow' and 'widen' apply.
But for signed 8 (s8?) and u8, then sometimes you can 'convert' an s8 to a
u8 (but not know until runtime - eg 5 works but -5 doesn't work), and
likewise u8 to s8 ( 100 works but 200 doesn't work). So neither is
strictly bigger than the other, and 'widen' and 'narrow' don't make sense,
but their generalisation, 'resize' does.
--