Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorporate Swift Ideas #131

Open
ClickerMonkey opened this issue Oct 21, 2020 · 12 comments
Open

Incorporate Swift Ideas #131

ClickerMonkey opened this issue Oct 21, 2020 · 12 comments

Comments

@ClickerMonkey
Copy link
Member

I like what I see with Swift.

Think about how more swift ideas could be incorporated into Expangine. Document here.

@ClickerMonkey
Copy link
Member Author

ClickerMonkey commented Oct 21, 2020

Assertions

Condition must be true, if not return a message immediately and stop execution ALL the way up (if we're in a function, stop all execution. Maybe throw an error?)

@ClickerMonkey
Copy link
Member Author

ClickerMonkey commented Oct 21, 2020

Operations

Instead of the operations we have now, maybe they are global - and if a type supports them then it has overloadable definitions.

For example, the "add" (+) operation is defined in a global operations list. Then "add" is defined one or more times on Number. For example, Number + Number = Number. But you could have Text + Text = Text also implement add. If there were a multiply operation (*) you could have Text * Times = Text where it repeats the text any number of times.

An operation can define if the parameters/return type NEED to be a specific type, otherwise the implementations can add their own.

Some operations are available to types if they implement one or more required operations.

The dev could add their own operations and implementations. The operation is merely a name that Types can implement. User defined types can also implement these operations so they could do Point + Point if they wanted.

This means if a value passed in is one of X types, and they all have the add operations defined it could perform an add operation without actually knowing the types at compile time. Ideally the OperationExpression saves the exact ID to the runtime implementation, but if it's the operation name then it needs to figure out the types and get the appropriate operation at runtime.

Example (common) operations

  • compare (a: T, b: T): number
  • is valid Type (a: Any): a is T
  • = (a: T, b: T): boolean
  • != <T:{=}>(a: T, b: T): boolean
  • <<T:{compare?}>(a: T, b: T): boolean
  • ><T:{compare?}>(a: T, b: T): boolean
  • >=<T:{compare?}>(a: T, b: T): boolean
  • <=<T:{compare?}>(a: T, b: T): boolean
  • cast to Type <I, O>(a: I): O
  • parse(text: string): T
  • copy (levels deep) (a: T, depth?: number): T
  • format (a: T, format: string): string
  • create (default value for type) (): T

Numeric operations

  • clamp <T:{compare}>(a: T, min: T, max: T): T
  • lerp <T:{+,scale}>(a: T, start: T, end: T, delta: T | number): T
  • slerp <T:{+,scale}>(a: T, start: T, end: T, delta: T | number): T
  • min <T:{compare}>(a: T, b: T): T
  • max <T:{compare}>(a: T, b: T): T
  • ^^ power (a: T, power: T | number): T
  • &&, and (a: T, b: T): T
  • ||, or (a: T, b: T): T
  • ^, xor (a: T, b: T): T
  • !, not (a: T, b: T): T
  • ~, flip bits (a: T, b: T): T
  • + (a: T, b: T): T
  • +* (x + y * z) (a: T, b: T, scale: number): T
  • - (a: T, b: T): T
  • / (a: T, b: T): T
  • * (a: T, b: T): T
  • % (a: T, b: T): T
  • -, negate (a: T): T
  • scale (a: T, amount: number): T
  • any comparison operations above, but with epsilon
  • zero? (a: T): boolean
  • whole? (a: T): boolean
  • decimal? (a: T): boolean
  • positive? (a: T): boolean
  • negative? (a: T): boolean
  • divisible? (a: T): boolean

Collection operations

  • create ( initial: {each} ): C

Iteration

  • each <C, K, V>(collection: C, on: (value: V, key: K): any): number
  • filter <C, K, V>(collection: C, match: (value: V, key: K): boolean): C<K, V>
  • not <C, K, V>(collection: C, match: (value: V, key: K): boolean): C<K, V>
  • map <C, K, V, M>(collection: C, map: (value: V, key: K): M): C<K, M>
  • mapKeys <C, K, V, M>(collection: C, map: (value: V, key: K): M): C<M, V>
  • split
  • reduce <C, K, V, R>(collection: C, initial: R, reducer: (reduced: R, value: V, key: K): R): R
  • group <C, K, V, G>(collection: C, groupBy: (value: V, key: K): G): Map<G, C<K, V>>
  • join (into Text) <C, K, V>(collection: C, delimiter: string, toText: (value: V, key: K): string): string
  • get/put keys in collection <C, K>(collection: C): K[]
  • get/put values in collection <C, V>(collection: C): V[]
  • pairs in collection <C, K, V>(collection: C):[K, V][]

Mutation

  • clear <C, K>(collection: C): void
  • set (key, value): (returns previous value) <C, K, V>(collection: C, key: K, value: V): V?
  • remove value <C, K, V>(collection: C, value: V): K?
  • remove values <C, K, V>(collection: C, values: V[]): K[]
  • remove condition <C, K, V>(collection: C, where: (value: V, key: K): boolean): number
  • remove key <C, K, V>(collection: C, key: K): V?
  • remove keys <C, K, V>(collection: C, keys: K[]): V[]
  • retain keys <C, K, V>(collection: C, keys: K[]): number
  • retain values <C, K, V>(collection: C, values: V[]): number
  • reverse (collection: C): void
  • sort (collection: C): void, <C, V>(collection: C, compare: (a: V, b: V): number): void
  • shuffle (collection: C, times?: number): void

Linear Collection Mutation

  • remove first value <C, K, V>(collection: C, value: V): K?
  • remove last value <C, K, V>(collection: C, value: V): K?
  • add <C, K, V>(collection: C, value: V): K
  • add to start <C, K, V>(collection: C, value: V): K
  • add to end <C, K, V>(collection: C, value: V): K
  • take <C, V>(collection: C, count: number): V[]
  • skip <C, V>(collection: C, count: number): C
  • drop <C, V>(collection: C, count: number): C
  • append <C, V>(collection: C, other: {each} ): void
  • prepend <C, V>(collection: C, other: {each} ): void

Inspecting

  • contains <C, V>(collection: C, value: V): boolean
  • some <C, V>(collection: C, condition: (value: V, key: K): boolean): boolean
  • every <C, V>(collection: C, condition: (value: V, key: K): boolean): boolean
  • find <C, V>(collection: C, condition: (value: V, key: K): boolean): C
  • key of value <C, K, V>(collection: C, value: V, afterKey?: K): K?
  • last key of value <C, K, V>(collection: C, value: V, beforeKey?: K): K?
  • key of condition <C, V>(collection: C, condition: (value: V): boolean): K?
  • count (collection: C): number
  • is empty (collection: C): boolean
  • random subset <C, V>(collection: C): C
  • random subset of <C, V, O:{createCollection}>(collection: C): O
  • random subset to <C, V, O:{add}>(collection: C, out: O): void
  • random value <C, V>(collection: C): V

Collection Operations

  • subset (start key, end key)
  • intersect
  • exclude
  • union
  • symmetricDifference
  • overlaps (collection with each)
  • join (inner, left, right, full, cross - similar to Boolean operations above)
  • unique
  • duplicates
  • isSubset
  • isSuperset
  • isStrictSubset
  • isStrictSuperset
  • isDisjoint

Aggregations

  • min <C:{each}, V:{compare}>(collection: C): V?
  • minWith <C:{each}, V>(collection: C, min: (a: V, b: V): V): V?
  • max <C:{each}, V:{compare}>(collection: C): V?
  • maxWith <C:{each}, V>(collection: C, max: (a: V, b: V): V): V?
  • sum <C:{each}, V:{+, create}>(collection: C): V?
  • sumWith <C:{each}, V>(collection: C, add: (a: V, b: V): V): V?
  • avg <C:{each}, V:{+, /}>(collection: C): V?
  • avgWith <C:{each}, V:{+, /}>(collection: C): V?
  • std
  • variance
  • median
  • bitand
  • bitor
  • bitxor

Other

  • distance
  • manhattanDistance
  • signedDistance

Rules:

  • Only have return values if a value was created
  • Basic operations are defined per type, the remaining functions are system-wide?
    • compare, is valid Type, cast to Type, parse, copy, format, create, +, /, scale

@ClickerMonkey
Copy link
Member Author

Control Transfer Statements

Currently only the return statement halts execution, change the ReturnExpression to a ControlTransferExpression which can do a break, continue, return, throw

@ClickerMonkey
Copy link
Member Author

ClickerMonkey commented Oct 21, 2020

Function Type

Would replace need for Callable

ClosureExpression defines the param types and names -> and the expression which returns the result. The closure has access to the context as well as the param types.

@ClickerMonkey
Copy link
Member Author

Custom Types

They can be anything, not just Objects

They are added to the Type creators/refs. Similar to Entity, but separate.

Entity is still separate, must be object.

@ClickerMonkey
Copy link
Member Author

Collection Types

  • Text (index: char)
  • Color (component: number)
  • Entity (prop: value)
  • Enum (key: value)
  • List (index: item)
  • Set (index: value)
  • Map (key: value)
  • Object (prop: value)
  • Tuple (index: element)

@ClickerMonkey
Copy link
Member Author

ClickerMonkey commented Oct 21, 2020

Protocol

Protocol is a loose type definition which has operations, props, or methods. A user can pass in any type which meets the protocol. This is referred to as an Interface in other languages.

@ClickerMonkey
Copy link
Member Author

The IDE will list all types, some operations are native (defined by runtime) and some are user-defined.

@ClickerMonkey
Copy link
Member Author

Inheritance

@ClickerMonkey
Copy link
Member Author

Generics

Add generic types to functions/methods/closure?. In the function definition you can list generic types and their name. They have an expected type, by default it's Any. The generic types are then available in the Parameters of the Function.

@ClickerMonkey
Copy link
Member Author

  • Add FunctionType (parameterTypes, returnType, genericTypes)
  • Add FunctionExpression (expression, argumentTypes?, aliases?)
    • When FunctionExpression has expectedType of FunctionType, argumentTypes is hidden, aliases is available
  • InvokeExpression name could be local Function (Closure)
  • InvokeExpression could be in path, right after a FunctionType (this)
  • Add extend option to ObjectType. It points to a type name.
  • Change operations to be globally registered
  • Change operation types to be globally registered, they are loosely related to the first type
  • Every type can have readable properties defined. There are native ones and user-defined ones.
  • Every type can have writable properties defined. There are native ones and user-defined ones.
  • Add ProtocolType which defines...
    • List of operations
    • List of properties
    • List of methods
    • ProtocolType is used to define "interfaces" so if a type passed in is compatible, then the Function has access to those items on the ProtocolType.
  • Add GuardType is compatible with boolean, but can modify context types when true is returned.
  • And/Or Expressions should handle GuardType
  • Add TypeInstance class (which is not a type) and it holds a Type, properties, operations, methods, and native?. It needs to answer type questions for PathExpressions, etc.
  • Add isCollection(), getCollectionKeyTypes(), getCollectionValueTypes(), getCollectionValueType(key)
  • Add IterateExpression which takes a type with the each operation. (using ProtocolType)
  • Update ForExpression and add amount which is a positive number to increment by.
  • Change ReturnExpression to ControlTransferExpression to have a type (Continue, Break, Return, Exit) and a result
    • Continue & Break is only available in a loop
    • Return is only available in a FunctionType (can have a result)
    • Exit is available everywhere (can have a result)
  • Add isLoop to Expression (true: For, Do, While, certain Operations)
  • Add AssertExpression (takes condition, optional message to throw)
  • Remove breakVariable from For/Do/While
  • Add Expression.getTypeModifications(context) to handle GuardType, And/Or updates, so If can use it in thens
  • Change EntityType to NamedType
  • Add Generics to FunctionType, each GenericType has an id/letter and optionally a type it needs to be compatible with. The GenericType holds the id, and to determine its type looks up through the parentage until it hits the FunctionType (or maybe always pass it down?). The GenericType is a wrapped type.

@ClickerMonkey
Copy link
Member Author

To reiterate and refresh my memory:

System Types: Any, Boolean, Color, Date, Enum, Function, Generic, List, Many, Map, Not, Null, Number, Object, Optional, Reference, Set, Text, Tuple, Interface

Every type can have props, operations, and methods (operations with this = type value)

A user can define a type, given a name and type definition. They can add/override operations and methods on top of the system type. Props are determined based on the type definition.

A user can define an interface which can have props, operations, and methods (only without definitions)

A user can define a new operation (addScale<T>(a: T, b: T, amount: number): T) . It has a name and syntax for rendering (ex: {0} + {1}). For any type they can add a definition for the operation.

Is the distinction between operations and methods worth it?

Destructure is an expression that gives a type an index/name and that type can determine the destructured type OR say its invalid AND the runtime has an implementation for each type that supports destructuring. des { x, y, z } = obj or array or map or tuple or enum

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant