compiler
ltr syntax
-- haskell is generally read right to left
print . foldr (+) 0 . filter odd $ [1..10]
-- first filter odd, then sum, then print
-- this is because this is how function application works in general
print(fold(...))
-- would it be better to reverse this?
[1..10] |> filter odd |> foldr (+) 0 |> print
-- imo, this is more readable at first glance
-- apparently, it's common in f#
-- it seems to be called forward/backward composition
live coding/debugging
- repl
- changing code while running (hot reloading)
naming
capitalized types
conventions
- underscore postfix for functions that loop and don't return (haskell
until_
) - prefix question mark or underscore for typed holes (idris, haskell)
it prevents double-naming (i.e. some may use isSame, some same (like contains instead of doesContain). same?, contains? read better than any other)
i really like this, but may clash with infix operators if ? can be used. i.e.
a?=b
may be interpreted as a ?= b
or a? = b
option is just not allow ? in infix operators/start of them
maybe prefer result (i.e. find : ... -> Result Index
) instead of predicates
and use ? to convert result to bool (i.e. cointains = find?
)
- postfix question mark for predicates (lisp)
flow typing
(/) : Int -> Int -> Result Int of
_, 0 -> error DivByZero
x, y -> builtin_div x y
-- usage
x / y -- : Result Int
x / 10 -- : Int. compiler detects first branch is unreachable
-- if you know something compiler doesn't, you can use assert or assume. use
-- sparingly
assume y != 0 in x / y -- : Int
digitToInt : Char -> Result Int of
x | isDigit x -> (ord x) - '0'
| _ -> error NotDigit
intOrZero : Char -> Int of
x | isDigit x -> digitToInt x -- : Int. x is constant and isDigit is pure
| _ -> 0
-- note that these are essentially just refinement types
-- we would also like a way to do a check for the output
toChar : Int -> Result Char
ensures isDigit result
of
| x | x >= 0 && x < 10 -> chr $ x + ord '0'
| _ -> error NotDigit
digitToInt $ toChar 5 -- : Int
-- 5 >= 0 && 5 < 10, meaning toChar 5 : Char and because toChar ensures isDigit,
-- digitToInt $ toChar : Int. the compiler also needs to check that the result
-- of toChar is always a digit
no match expr
-- use equation style instead
-- problem. duplicated params
f : Env -> Params -> SumType -> Result of
env, params, A => ...
env, params, B => ...
env, params, C => ...
env, params, D => ...
-- with match, this would be much better
f : Env -> Params -> SumType -> Result of
env, params, sumty = case sumty of
A => ...
B => ...
C => ...
D => ...
-- alternative, without match, allow of within of
f : Env -> Params -> SumType -> Result of
env, params of
A => ...
B => ...
C => ...
D => ...
// different syntax type dataonly = @skip(len) vec
b : dataonly = ...
// you can only use functions that don't use the len field
a : (a: i32, b: i32) -> i32 = a + ba5 : (a: i32) -> i32 = a(5, $1)
fn add3(a, b, c) -> i32 = a + b + c
fn a5(a) = add3(3, 2, a) a5 = add3(3, 2, $1) a25 = add3(5, $1, $2) a25 = add3(5, $..)
// alternative. use named arguments a5 = add3 ~a=3 ~b=2
// alternative. use ! after function name // explicitly show that it's currying a5 = add! 3
// idea. explicitly need to enable currying fn add(a, b) ... add5 = add 5 // not allowed
fn add(~a, b, c) add5 = add ~a=5 // allowed (int->int->int) add53 = add ~a=5 ~b=3 // not allowed. b doesn't allow currying
// only one letter argument names don't need to be named fn add(a, b) = a + b add 5 3 // okfn map(fn, data) = ... map(fn=add 5, data=...) map(add 5, ...) // not allowed. fn is not single letter
## interface change should always result in a compile error
variable type/function signature are considered their respective interface
## infix functions
```hs
-- 2-parameter functions in haskell can be infix
mod 5 2 == 5 `mod` 2
-- maybe better to explicitly declare a function infix
-- e.g. a function starting with `
mod :: Int -> Int -> Int -- not infix
5 mod 2 -- error
`mod :: Int -> Int -> Int -- infix
`mod 5 2 -- error
5 `mod 2 -- ok
-- curry
(`mod 5) 2 == 2 `mod 5
(5 `mod) 2 == 5 `mod 2
## named parameters
```ml
(* named parameters considered bad and are usually a crutch for bad api design
contrived example, but illustrates the point *)
add : float -> float -> float -> float -> float -> float -> float -> float -> float
(* preferred *)
alias Point = { x: float, y: float, z: float }
add : Point -> Point -> Point
(* possible keyword parameters ala ocaml *)
add : ~name: String = "unnamed" -> Int -> Item
of x = ...
(* usage *)
add ~name="foo" 5
idk anymore
module Vector
Item : Type
len : Int
data : [Item]
let of_list : [Item] -> Vector
len : Self -> Int
| self = self.len
concat : Self -> Self -> Self
| self, other = { len = self.len + other.len }
module parameters
(* file: random.x *)
module Random(seed: Int)
(* seed can be used as if it's a val *)
let state = seed ref
(* main gets the env argument *)
module Main(env : Env)
let () =
Stream(env.out).printf "Hello, world\n"
type annotations instead of gemerics
type annotations are basically type functions
haskell-style type syntax
:: Int, Int -> Int
fn add(a, b) = a + b
(* maybe?? *)
let add : Int -> Int -> Int =
a, b, = a + b
let plus : Nat -> Nat -> Nat of
| Z , n -> n
| S m, n -> S (plus m n)
names from types
fn insert(Ord, BTree)
(* equivalent to *)
fn insert(ord: Ord, bTree: BTree)
(* not allowed when ambiguous *)
fn add(a: Int, b: Int)
optional struct features
struct list<bool double_ended?, bool sized?>(
next: Self,
@if double_ended?
prev: Self,
@if sized?
length: i32,
)
fn len(list: list<sized? = true>) = list.length
// in rust, it would be a wrapper (ex. peekable)
struct sized(list: list, length: i32)
pipe scope
// piping some type brings into scope the module in which it is defined
module A {
type B = i32;
let add5 x = x + 5
}
A.B |> add5
// equivalent to
A.B |> A.add5
statement = expr consumer
functions as pattern matchers
// first argument must be a list of two elements
fn something([a, b], c: i32) = a + b + c
// overloading
fn a(arg: f32) = ...
fn a(arg1, arg2) = ...
fn a(arg: i32) = ...
a something
// is equivalent to
match something
| (arg: f32) => // call a(arg: f32)
| (arg1, arg2) => // call a(arg1, arg2)
| (arg: i32) => // call a(arg: i32)
macro system
macro if {$(condition: expr) => $(then: expr) else $(else: expr)} =>
match ${condition} | true => ${then} | false => ${else}
another idea is to have alternative lisp frontend and to call lisp functions from the language
no-bracket syntax
fn a(a: i32) -> i32 = a + 5;;
fn a(a: i32) = print a;;;
struct Point(x: i32, y: i32)
choice A =
| A(i32)
| B(f32)
// => always denotes a block start. ;; block end
=>
let x = 5;
x + 5
;;
// let with block
let x : i32 =>
let y = 5;
y
;;
fn a(a: i32) -> i32 => a + 5;;
for i in 0..10 => print i;;
let lbtype: () -> void;;
let lambda = (x: i32) -> f64 => x + 5;;
let lambda = (x: i32) => x + 5;;
=> print 5;;
// block is either a signgle expression or list of statements taht require
// return
fn a() => 5 + 3;;
fn a() =>
let x = 5 + 3;
return x
;;
if a < 5 => print a;;
if a < 5 => a; else 5; let x = 5; x;
fn a => 5
// same as
fn a() -> i32 => 5
if a < 5 => a else 5
assert a < 5
// expression based. ';' joins expressions
// significant indentation
=>
if x < 5 =>
print x;
x = 5;
x = 3; // not part of if block
5 // misleading indentation error
=>
match y =>
| ... => match x
| 5 => print x
| 3 => print 3
| _ => print y
// lambda
()=> 5
// problem
print(x) => 5 // print(x)(=> 5)
print (x)=> 5 // print((x) => 5)
print (x)=> 5 () // print((x) => 5)() ?
enum
// enum is a type sum
type Enum = i32 | f32;
// Enum is either i32 or f32
// there are on-fly types
type Enum =
| A(i32)
| B(f32)
// equivalent to
type A = i32;
type B = f32;
type Enum = | A | B;
// there are also implicit void types
type Enum = A | B | C
// equivalent to
type Enum = A() | B() | C()
name-first syntax
a : i32 = 5;
b := 5;
lmb : (i32) -> i32 = { it + 5 }
lmb := { it + 5 }
// problem
fn a() { ... } // implies void return
a : () = { ...; 5 } // implies deduced return type
module arguments
destructibles
struct unique_ptr(item: *i32);
fn drop(self: unique_ptr) { free self.item; }
// unique_ptr is destructible. you cannot assign it
let a = unique_ptr(malloc(int)); // not allowed
// you can only create it with 'with'
with a = unique_ptr(malloc(int));
// with calls drop at the end of the block
local returns
// syntax
// exit [label] [value] ['if' condition]
// if label is not provided, it exits the inner-most block
fn a() {
exit fn 10 if true;
exit a
a := {
exit 5
} // => 5
loop {
exit a if a > 10;
a += 2;
} // => 11
}
first-class fsm (finite state machine)
see: reddit
while True:
print "always"
with state_1:
print "state 1"
continue state_2
with state_2:
print "state 2"
continue state_1
// outputs
// always, state 1, always, state 2, always, state 1, ...
auto-generated function types
// you write
fn add(a, b) = a + b;
// compiler adds types with capitalized names upon succesful compilation
:: (A: Addition, A) -> A
fn add(a, b) = a + b;
literal variants
extern
assignment sugar
automatic lift for collections
dependent types and first class modules
struct modules / primary structures
module Program {
// because Scanner has data, it's a struct module. that means, there is an
// implicit struct Scanner(data: [i32]);
module Scanner {
type Enum = | A | B | C;
let data: [i32];
// constructor
fn(data: [i32]) =>
// we need to initialize all fields
data = data;
fn get() -> i32 => data.pop()
}
Scanner([1, 2, 3]).pop() // -> 3
}
non-optional subtyping
ufcs
fn a(this: i32, that: i32) -> i32 = this + that;;
a(5, 6) == 5.a(6)
fn add(this: i32, that: i32) -> i32 { this + that }
struct
struct<A: Integral> Int: A(
)
fn Int::a(i)
// primary constructor
type Int(a: i32) = struct(
x: i32 = a,
y: i32 = 5,
)
// if primary constructor is not provided, imlicit one will be created
// implicit constructor requires all named fields.
// you can also overload a constructor
// note. no return type. constructor always returns self
// you assign fields in self
fn Int::Int(self: Int) {
self.x = 5;
self.y = 5;
}
module classes
module vector;
fn dump(stream: Stream = std::out) {
stream.write(data);
loop items {
stream.write(it);
}
}
fn dumpitem(stream: Stream = std::out) {
stream.write(data);
}
module dump {
fn dump() {
stream.write(data);
}
}
}
constructors
struct Vec(x: i32, y: i32, z: i32)
// constructors are separated from other functions by capitalized name
fn Vec.Zero() { Vec(0, 0, 0) }
// function with same name as struct is a constructor. it implicitly returns the
// struct
fn Vec() { Vec(0, 0, 0) }
// option 2.
fn Vec.Vec() { }
Vec() // sugar for Vec.Vec()
signatures
sig Map {
type Key;
type Value;
fn get(key: Key) -> Value;
}
sig IntMap : Map {
type Key = i32;
}
struct IntHashMap = HashMap : IntMap;
// note that : Map is completely optional. it just tells the compiler to emit an
// error if it's not a subtype of Map. it signs Map even without it
struct<
type Key
type Value
type UnderlyingContainer : Stack<Key> = Vec<Key>
> HashMap : Map(type Value = V) {
let value: UnderlyingContainer;
}
struct HashMap<Key, Value>
fn HashMap::get(key: Key) -> Value {
...
}
// this just checks that HashMap is a subtype of Map
// equivalent to HashMap : Map in definition
struct _ = HashMap : Map;
let a = HashMap<i32, i32>();
let b = IntMap<i32>();
struct Something()
struct Something : Map{type Key = i32; type Value = i32}
fn Something::get(i32) -> i32 {}
program stages
@stage(1)
static x; // x can only be modified in stage 1 or earlier
// a can only be called by functions in stage 1 or earlier
@stage(1)
fn a() {
x = 10;
}
// b can not call a, but a can call b
@stage(2)
fn b() {
}
// concurrency cannot be used in any stage
fn main() {
// by default, main doesn't have a stage
// the first statement needs to be @stage if you want to use stages
// transition. cannot have code before this
@stage 1
}
lambdas
// normal syntax
let a = (x: i32) -> i32 { x + 5 }
// trailing lambda syntax
a.map { it + 5 }
// lambda blocks
let a = { (x: i32) -> i32 => x + 5 }
let a = { x => x + 5 }
// lambda blocks differ from normal blocks by =>
{ => 5 } // lambda
{ 5 } // block
loops
// while loop
while a < 5 { }
// iteration loops with lambdas
loop vec(5, 3, 2, 1) {
print it
}
// inifinite loop
// note that with loops, block is implicitly a lambda, so you can skip =>
loop { // => optional
}
enum traits
enum trait Color {
fn r() -> u8;
fn g() -> u8;
fn b() -> u8;
}
// all variants of Color must be in same module
struct Red : Color();
fn Red::r() -> u8 { 255 }
fn Red::g() -> u8 { 0 }
fn Red::b() -> u8 { 0 }
enum
optional/extension fields
struct Player(has_hitbox: bool) {
hitbox?: Hitbox = has_hitbox? Hitbox(),
}
// hitbox is an optional field. if has_hitbox is false, the field will not even
// be allocated upon construction
// this mimics llvm's TrailingObjects class
// you cannot create Hitbox after construction
monads
fn addone(a: i32) -> i32? { if a < 5 { a + 1 } else { none } }
let x = addone(4)
=> addone(it) // 5
=> addone(it) // not run
=> addone(it)
assert(!x?)
idea
a way to encapsulate states in a type and only expose the states that are valid at the time
e.g. ast -> typedast
struct VarDecl {
substruct Typed {
name: String,
}
type: Type*, // when initially creating ast, we may not know the type
}
fn analyze(Typed decl) -> VarDecl {
// decl.type has uninitialized state
// function figures out the type
update decl {
type = ...
};
// note that updating decl does not resize/move the struct. Typed has the same
// size as VarDecl, but only exposes the fields that are valid in that
// substruct
return decl; // decl is now VarDecl and decl.type is valid
}
// this will also help creating new structs
stuct A { XKnown { x: i32 }, y: i32 }
let a = XKnown { x: 5 }
fn figureOutY(a: XKnown) -> i32 { 10 }
let y = figureOutY(a)
update a { y = y };
errors
error return function annotation
error SomeError
error AnotherError ""
error MsgError(i32) "" // you can have data in error
// function that returns an error
// throwing a ParseError will throw AnotherError
@throws(SomeError "something happened", // all errors reqire documentation
AnotherError < ParseError, // any instance of ParseError will be thrown
// as AnotherError
DataError(i32) "" // you can attach data to errors. may be removed for
// performance
)
fn root(x: i32) -> f64 {
if x > 0 { (f64 x).sqrt() } else {
err ParseError // this will throw AnotherError
// probably won't allow this. you can only directly throw SomeError but you
// can rethrow ParseError
}
}
// caller needs to handle the errors somehow
root(0) err // rethrow
root(0) catch e {
// e is an enum of possible errors
print("error: {e}")
// trace e allows you to print the stack trace. this is only allowed in
// prototype mode as they are expensive to generate
trace e
}
// this will assume root doesn't error. in debug moe, this will abort the
// program
let x = assert root(9)
// everything that can error, does. like arithmetic, array access, ...
// and you need to handle it
let x = assert a + b
// there are unchecked versions of these operators
let x = a +! b // may overflow
// there is also compiler flow analysis that will allow you to skip checking
assert a < 5
let x = a + 5 // no error. compiler knows it can't overflow
// assert checks in release mode and panics if false.
// for debug only checks, you use assume
assume a < 5 // checks in debug. in release, it's a no-op
contract idea
@error SqrtError "can't sqrt negative number"
@requires x >= 0 else SqrtError
@debug @requires x < 1000 // maybe
@ensures result >= 0 // result is a special keyword in ensures clause
fn sqrt(x: f64) -> f64
{
// x is guaranteed to be >= 0
x.sqrt()
}
// if you can guarantee the contract, you can skip the error handling
assert a == 4
sqrt(a) // no error
assert b == -1
sqrt(b) // need to handle error
traits and concepts
trait Number : Add + Sub + Mul + Div {
fn abs(self) -> Self;
}
concept<T> Numeric {
};
fn Numeric::abs()
generics
struct Vec<T>()
fn Vec::push(T t) {
// T is available
}
fn<A> Vec<A>::push(A a) {
// in case you need T from outer scope?
}
nominal vs structural typing
trait Iterator {
fn next(self) -> T;
}
struct A();
impl Iterator for A {
...
}
struct B();
// you can use Int as Iterator implicitly
// but need to cast B to iterator explicitly
// probably going to use nominal typing and structural subtyping of
// traits/contracts. that is, you don't need to impl Trait for a type to use it.
// it just needs to be in scope
// if it walks like a duck and talks like a duck, then it's a duck
contract Duck {
fn talk();
fn walk();
}
// a duck can fly
fn Duck::fly() { ... }
// Pig walks and talks
struct Pig();
fn Pig::talk() { print("oink") }
fn Pig::walk() { ... }
// ergo, Pigs can fly
Pig().fly();
dafault initializations
nothing is default initialized. you need to explicitly initialize everything you use. there is however late initialization
prototype mode
you can enable prototype mode in the project file. this will allow you to use some features that are nice to have, but shouldn't be used in production code. e.g. stack traces. in this mode, the compiler will try to recover from syntax errors and continue compiling and just print a warning of what it assumed you meant. some compiler errors will also be warnings or disabled in this mode. e.g. unused variables, unreachable code, unhandled errors, etc.
unexpected annotation
if a < 5 {
doSomething
} else {
// in debug mode, this will abort the program with an error message
// in release mode, the compiler will assume this is unreachable
@unexpected("a shouldn't be less than 5")
}
(idea) indent-based
indent-based blocks that end with two semicolons. statements end with one
(idea) function syntax
fn add a: i32 -> b: i32 -> i32 = a + b
// equivalent with lambda notation
let add = fn a: i32 -> b: i32 -> i32 { a + b }
// no parameters
let no_args = fn -> () { () }
// ==
let no_args = fn () -> () { () }
other languages interop
you declare modules for other languages in the project file
// declerations from terrain.hpp are in terrain module
include(cpp) "terrain.hpp" in terrain
// declerations from stdio are in cppio module
include(c) <stdio.h> in cio
then in your code, you can define and use them
module terrain // you need to be in the same module to define functions
// assume terrain.h has following contents
// int get_height(int x, int y);
// we define the function here
let get_height = (x: i32, y: i32) -> i32 {
// we can also call functions from other modules like this
cio::printf("hello world\n");
10 // return value
}
ideas
invalid states, which stack let a = 5. / 0.; // inf. invalid state a += 5.; // valid statement. returns f64? with invalid state inf
you can define invalid states on your own types impl Invalid for f64 { Infinity { i == inf } NaN { I == nan } }
. | pipes output of a function to another function a | b but |. pipes output to a member function
ability to split module interface (like header files). outside of module you can only access interface structures
variables
// should mutability be part of type or variable (rust) or both (c++)?
mut x = 5;
// vs
let x: mut i32 = 5;
// difference is allowing internal immutability
// type mutability
struct Point (x: mut i32, y: i32);
let p: mut Point = Point(x: 1, y: 2);
p.x = 5; // allowed
p.y = 5; // not allowed
let r: Point = Point(x: 1, y: 2);
r.x = 5; // not allowed
// variable mutability doesn't allow this
struct Point (x: i32, y: i32);
let mut p = Point(x: 1, y: 2);
p.x = 5; // allowed
p.y = 5; // allowed
let p = Point(x: 1, y: 2);
p.x = 5; // not allowed
p.y = 5; // not allowed
//===
// shadowing
//===
// allowing shadowing in same scope may have some problems
fn a(x: i32) {
// shadowing is nice for sanitizing
let x = if x < 0 { -x } else { x };
// but others may expect that x is what is passed in. especially if you work
// at end of a long function
// ... use x expecting it to be the input x
}
// not allowing shadowing will have worse problems
let state = getstate();
let state2 = processstate(state);
let state3 = reprocessstate(state2);
usestate(state2) // bugprone
// this can be solved with shadowing
// both can be solved by having mutability of type and variable
let state = getstate();
let state = processstate(state); // error
mut state = getstate(); // state value is constant, but i can reassign the
// variable
mut state = mut processstate(state); // valid. i can reassign and change state
// don't like it
(idea) capitalization of types
this way you know if you are dealing with a type or a variable at scanning time
language
// you specify module before the code in the module. allows you to
// move the file around without breaking the code
module somemodule
// for now, you can only define one module per file
// but idea is to allow multiple modules in a file like this. if we want it
// // this code is part of module somemodule
// module somemodule::a
// // this code is part of somemodule::a
// you may only define a module once
// empty module is the current project module, so
// use ::b
// is the b module in current project, but
// use b
// is the a project's default module
// expression-based language. everything is an expression. block evaluates to
// the value of the last expression
fn add(a: i32, b: i32) { a + b }
// available types are:
// primitives: i32, f32, bool, char...
type Int = i32;;
// structures
type Point = (x: i32, y: i32);;
// you create a struct like this
let point: Point = Point(x: 1, y: 2)
// you can skip the field names if you pass variable with same name as field
let point: Point = Point(x, y)
// you have the unit struct
type Unit = ();;
// single values are also structs. you can skip the field name with them
type Int = (i: i32);; // same as Int = i32 and Int = (i32)
// single field structs can be created with just the value
let i: Int = 5
let j: Int = Int(5)
let k: Int = Int(i: 5)
// optionals.
type OptInt = Int?;;
let opt: OptInt = 5
// you can skip the value to create a null optional
let noInt: OptInt
// you can check if an optional is null and the compiler will allow you to
// access the value (type flow analysis)
if opt { opt } else { 2 } // 5
if noInt { noInt } else { 2 } // 2
// they allow pointers to be null
type NullableIntPtr = Int\*?;;
// pointers. they are not nullable. if you move the value, the type becomes
// IntPtr? (nullable) with value null. that means, they are like c++ unique_ptr
type IntPtr = Int\*;;
// functions
type Succ = i32 -> i32;;
// you can create a function like this (lambda notation)
let succ: Succ = ||x: i32 { x + 1 }
// they only take exactly one argument. to pass multiple, use a struct
type Add = (a: i32, b: i32) -> i32;;
let add: Add = (a: i32, b: i32) -> { a + b }
// to pass no arguments, or return nothing, use the unit struct
type NoArgs = () -> ();;
let no_args: NoArgs = () -> { () }
// you call functions like this
succ 5 // equivalent to succ(5)
add(1, 2)
no_args()
// function receivers. you can define a function that will allow you to call it
// as a member
let Int::add = ||(i, a: int) { i + a }
let Point::x = ||(i) { i.x }
// i is a special parameter name that binds the A structure so you can call add
// as a member. it must be the first parameter and must not have a type
let num: Int = 5
num.add(5) // -> 10
// fuck can still not access private members of A (unless part of same module)
// + is a special function name. you must pass exactly two arguments and return
// exactly one
// the two arguments and return types must match
fn +(a: i32, b: i32) -> i32 {
a.add(b)
}
// similar for other operators
docs
-
invariant based programming with assert and assume
-
encoding invariants into the type system
-
bytecode interpreter and runtime analysis
-
performance it analyzes the program and finds likely if path, hot code, ... can detect where memory flow and can transform rc to manual
-
safety can detect every place a specific variable gets assigned detects memory errors (dbl free, ...)
- gc only used in self-ref structures, otherwise rc
-
docsite ideas
optional sidebars for additional stuff. something like detail hiding here.
docs should be written like this
hey there
here is some text that is shown in all cases
>!theory
> here is some text that is shown only when
> theory mode is enabled
>!history
> historical notes. why it was done this way, which languages/language features
> inspired it, and link to initial discussion about this feature
>!imperative
> how it's done in other languages. i.e. mode for beginners coming from c++
>!implementation
> implementation details for this specific feature
>!example
> this is where examples should go. if people are looking for something
> specific, examples may get in the way. they should be shown by default
the user should be able to enable or disable these modes at the top by the title
choosing the right idea matters
naming things matters because it gives the right abstraction for reader
this means even if I provide the same idea with different syntaxes it will affect the readability of programs
name types not values
idea: values from types