-
Notifications
You must be signed in to change notification settings - Fork 157
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
Typings bikeshedding #999
Comments
As is the tradition, I've first implemented this with LEXER HACKS : 509cf13
|
I'm really glad you're still looking into this. I'm also really nervous about adding yet more syntax hacks—I don't know whether LS has already crossed over the line where writing a reasonable parser for it someday is basically impossible, but I'm hoping that nothing we'd do as part of adding types would push it over that line. I want to try to avoid committing any new lexical sins, if possible—adding more back-scanning to the The most bikesheddy question: how to spell has-type? I guess I also think that we should reuse as much existing LS syntax as possible, where applicable. For that reason, I would propose Given the above three opinions, how to handle generics? If type constructors are applied like functions, I would argue that they should be declared like functions—something like Here are some other things to be worried about (where relevant, this is largely from the perspective of Flow; I'm not as familiar with TypeScript):
|
As of right now...So, for my current version (still a lexer hack) the return type syntax is In the spirit of hacks, to not change Lexer.parameters, if we parse an arrow and our last is an ASCR, we put the token on the side while parsing parameters (there's a good reason I don't want to push it!). type param syntaxI'm against adding typings symbolI'm more and more against |
I just wanted to do this as a proof-of-concept.
So implementing this syntax is fine. However... For the return type, it's much more complex. even if we use If we want to do it the "right way" (?) (no folding into ASCR), that means we need to change On the other hand, if we go for a syntax like
Well, I don't think it would be impossible. We'd just need a parser that'd be less confused as to what it's looking at.
As demonstrated, it doesn't, because some rules don't apply between
See ^ Flow stuff
I have no idea how these two are different. Optional field vs nullable type?
It's a type?? |
I like the idea of spaced # Obvious. Typed variable
x :: Int = 0
# Type aliases!?
MyBarCombo = :: BoozeBar<Qwo, Bzt> | (Barable<Qwo> & Boozable<Bzt>)
# Of course, param types and return type
foo = (a :: String, b :: Any?, c :: MyBarCombo) :: SuperBool ->
console.log "#{a}, #{b ? ""}, #{c}"
super-true
# casting / coercion!?
console.log "A real number:", ((47 * 23) :: Real)
# Extending on the alias above, crazy:
MyClass = ::
(foo) ->
@foo = foo + "rules"
@i-give-up = "now" |
didnt dive into all the comments, sorry, but why not add some kind of parse logic marker at the top of the file with code, like "use strict, ts_compat" so it will be converted to globally accessible flag and checked where the compile logic works? so the livescript could be extended to anything.. or maybe it's an utopia) |
in LS, we have:
So that ship sailed a loooooooooooooong time ago. Doesn't mean we need anymore of them. But yes, we can have dually-spaced answering @ozra# Obvious. Typed variable
x :: Int = 0
# vendethiel: my goal is to only allow this in let, var, and const.
# Type aliases!?
MyBarCombo = :: BoozeBar<Qwo, Bzt> | (Barable<Qwo> & Boozable<Bzt>)
# vendethiel: probably, but with a keyword. Also, no <>.
# Of course, param types and return type
foo = (a :: String, b :: Any?, c :: MyBarCombo) :: SuperBool ->
console.log "#{a}, #{b ? ""}, #{c}"
super-true
# vendethiel: that works locally.
# casting / coercion!?
console.log "A real number:", ((47 * 23) :: Real)
# vendethiel: maybe? it currently parses but doesn't do anything.
# Extending on the alias above, crazy:
MyClass = ::
(foo) ->
@foo = foo + "rules"
@i-give-up = "now"
# vendethiel: what's that supposed to be? |
Type constructorI prefer application syntax # only one parameter, it's fine
# EDIT : according to @vendethiel, it's not fine too :/
(x :: Map Int, Int) ->
# multiple parameters, commas confusion :/
(x :: Map Int, Int, y :: Int) ->
# we need use parens to fix this ...
(x :: (Map Int, Int), y :: Int) ->
# or
(x :: Map(Int, Int), y :: Int) ->
# with haskell-like application syntax it's better but it's out of scope for this issue
(x :: Map Int Int, y :: Int) -> Type ascriptionI prefer Return typeProposed return type Type parametersI prefer the precircumfix brackets Higher-order functionI didn't see any example with them :/ # something like that ?
(f :: ((x :: Int) :: String), x :: Int) :: String -> |
genericsof importance: we could [probably] have HOF with spaced type applicationIt's true i forgot to mention this. Mostly because I don't have an actual "good" idea. Higher order type: I don't really like it. TS uses HOF with
|
Type paramsI prefer What about something like HOF and return typesI don't like the double map = forall (A, B), (f :: (A) -> B, arr :: Array A) :: Array B -> ...
# or
map = [A, B](f :: (A) -> B, arr :: Array A) :: Array B -> ... If I already had issues with that notation for return types because, again, it extends the non-locality of the Flow stuffYes, in Flow,
@determin1st's suggestionSupporting multiple syntaxes, however you do it (through in-source pragmas like what you suggest, configuration files, or compiler flags), exponentially increases what the language maintainers need to support. LiveScript's one syntax is barely being maintained right now; I am strongly against dividing our efforts further unless there is a renaissance in people contributing to the code. ClassesWe should also think about these. LS classes don't create ES6 classes, so they should be typeable as |
Type params
LS tends to follow Coco in the "Fewer keywords" mantra.
Well, HOF and return types
If we do that, we can just able forget the idea of doing this properly in the parser and not in the lexer, methinks... Also then it becomes weird that the function's own return type does not use that keyword (though it's the same as in TS, e.g.
I don't think so:
Flow stuff
I see. There's no such difference in TS AFAIK. I find it really ugly, but hey. We havn't even started to bikeshed on how to type heterogeneous objects yet.
Ok. Thankfully, we already have a placeholder thingie, which is what the ML family also uses: ClassesSee the comment on heterogeneous objects. They fall under the same category I'd say.
Note that this is not enough. class C
(a :: Array Int): []
-> @a.push 3
console.log C.new.a # prints [3]
console.log C.new.a # prints [3, 3] |
I don't understand that last example at all. More words, please? |
We need a way to type instance variables, not only prototype variables. class C
-> @a :: Array Int = 5 # we need a way to say "this will always exist on the object" Are you available on IRC for a few minutes? EDIT: IRC logs: https://gist.github.com/vendethiel/5b830f94b5cf838e66c61ea629e31d0c
We disagree on how things should look and what the syntax should be (and the "complexity budget" we have). Though we agree on all the pain points we see: typing objects, typing classes, typing tuples, typing function as parameters. |
I wrote my own proof of concept, with tests (I've checked the resulting code on typescriptlang.org; looks good as an initial stab). See 68e836267. It's very hacky also, but all the hacks are in ast.ls instead of lexer.ls. Notable bikeshed decisions made here:
|
Nicely done! |
I'm gonna go full crazy for a bit... @rhendric first proposed Haskell-style annotations: map :: forall A, B, (Array(A), (A) -> B) -> Array B
map = (as, f) -> as.map(f)
mapper :: Int -> Int -> Int
mapper = (x) -> (y) -> x + y
map [] mapper I think this is impractical as it means you need to declare a new variable for every function. I want to mention a way this could be solved using a Haskell-style where (...which we add a few years back), if made a bit smarter: map [] mapper
where mapper :: Int -> Int -> Int
mapper = (x) -> (y) -> x + y would not bother me, if we made |
@vendethiel, I should have been more clear as to my intention with the examples. They were more of what would work without clash with the simple definition "after
[var decl] - That sounds preferable to me too.
[type alias] - I fully agree here too, except
["casting"] - I figure it can't harm to allow the notation should some one find a good use for it for a static analysis tool or such. If TypeScript is considered the specific target for LS type notation (which I would find perfectly reasonable), then of course any notation that won't forseeably be introduced, or already exist, in TS would maybe be confusing.
[class declaration] - Based on the already crazy example of type alias, this would simply signify a class declaration. But again, not wanted in practice. I just prefer the idea proposed of spaced Regarding generics, I much prefer the |
I've always prefered matching brackets, but fine.
we could have it mean TS'
as @rhendric just demonstrated - doable, but requires a bit of juggling.
Knowing what people would prefer using is still helpful. Thanks for the input. |
microsoft/TypeScript#21316 TypeScript seems to be moving... in many directions at once. How the hell are we writing ``type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;`` |
I'm not at all sure this is a good idea, but continuing in the vein of my proof-of-concept, the below comes remarkably close to compiling: type ReturnType = forall T, if T extends ((...Array any) -> infer R) then R else T |
If anything, I think I'd prefer it if we had a |
That would be a clean way to encapsulate things. However, I think I'd want to avoid a proliferation of |
That's not what I'd envisioned. The Type::compileNode would just generate JS from its content, recursively descending into its AST fragment. |
Could work! That approach might necessitate pulling some logic that lives in existing |
Indeed, I'd argue that's a good thing, and untangling type/real calls code is the only sane way forward imho. |
@rhendric would you consider either 1) allowing type declaration beforehand, like my |
I'm open to both of those possibilities, depending on how they're executed. I do like the idea of (2) over |
Ah, but... obviously, as it currently closes implicit functions, it's non-viable. I don't think we can keep both syntaxes. |
Why is that non-viable? We'd just change the lexer to not close implicit functions at |
Okay, not non-viable, sorry – I'm just biased against that syntax, and requiring extra parens makes it even less interesting to me. |
Aw, really? I'm starting to like it better than my original proposals. Having support for all of fun-with-types = (a :: A, b :: B) -> :: C; ...
fun-with-types = (a :: A, b :: B) -> :: C
...
fun-with-types = (a :: A, b :: B) ->
:: C
... feels nice and uniform to me, and all of these seem more readable than fun-with-types = (a, b) -> ... :: (A, B) -> C
fun-with-types = (a :: A, b :: B) -> (... :: C)
fun-with-types = (a :: A, b :: B) ->
...
... :: C which were the hacks I first tried out. |
I think I poorly expressed myself. I mean your original proposal |
Bah, I'm really starting to consider adding an EDIT: ok, I have a patch that uses |
I pushed a commit to my proof of concept branch supporting |
Is this not acceptable for some reason? Obviously a breaking change, but... map = forall A, B, (f :: (A) -> B, arr :: Array A) -> :: Array B
arr.map f |
Technically, that could probably be made to work. Aesthetically, I'm biased against it because of how freely it mixes type and value symbols without clear syntactic dividers between them ( |
That ship has sailed already, though, no? even when writing |
Maybe I'm splitting hairs, but to me the difference seems large: |
I should also add that I expect there to be more special type constructors; see, for example, |
Fair enough, but I don't see any other that need to be an introducer. Maybe if Flow or TypeScript start supporting existential qualifications, but... There's no real "good" place, except if we want to... pluck them out as we're reading it (basically like TS's I'd appreciate if a few more people voiced their opinions, or threw in some other ideas! :-) @ozra , @wryk , or from #803(?) like @robotlolita, ... yada |
(Starting to wonder if perhaps |
Surely you mean the least I'm totally OK with precircumfix Is there actually a case where we want it inside a type signature? |
Why shouldn't it be valid there? |
In the precircumfix
The extra |
I wasn't sure
For both... We only need to look a single token ( |
The point is that whether or not those parentheses should become There could very well be a simpler answer—don't let me stop you looking for one. I just tried and that's what I ran into before I gave up for now. |
Hello, I'm probably late to the party, but there is at least one thing I want to say: please use HMS type annotations, or something as close as possible to Haskell. One of the reasons I hate all those typed languages is because all the noise and weird characters their require. They make the code verbose and hard to read and to reason about it. That's why I hate java, that's why I like javascript and that was one of the reasons why Haskell impressed me a lot: Types are possible without visual clutter. |
Coming back to this (I'd like to have it for 1.7, if that's imaginable...) regarding
From what we discussed earlier, it seems we don't need a special syntax for |
By all means, if you have an approach you think will work, give it a shot. I don't think I have any more helpful thoughts to offer you about the |
OK. I tried for a bit, and the AST is a bit too damaged by the time we get to $ lscc 'f [A, B] ::> (a :: A, b :: B) ->'
f(function<A, B>(a: A, b: B): any{});
$ lscc 'f = [A, B] ::> (a :: A, b :: B) ->'
var f: <A, B>(arg0: A, arg1: B) => any;
f = function(a, b){}; I can take a stab at the |
Nice. Reviewing the thread, it occurs to me that my earlier discomfort with value-level (Still kind of hoping one of us—or someone else—comes up with either an elegant implementation of |
Hello :). I'm late and probably out of subject but anyway. What about a type system inspired by the fantasy land specification and a bit of purescript ? It could look like: # var name: string = 'john'
let name :: String = 'John'
# var add: (a: number, b: number) => number;
# add = function (a, b) { return a + b; }
let add :: Number -> Number -> Number = (a, b) --> a + b
# multiline support and without curry
let sub
:: (Number, Number) -> Number
= (a, b) -> a - b
# Generic support:
let map
:: a, b. (a -> b) -> Array a -> Array b
= (f, arr) --> arr.map f
# var map: <A, B>(f: ((a1: A) => B), arr: Array<A>) => Array<B>;
# map = function (f, arr) => { return arr.map(f); }
# Interfaced generic support:
let map
:: User a, b. (a -> b) -> Array a -> Array b
= (f, arr) --> arr.map f
# var map: <A extends User, B>(f: ((a1: A) => B), arr: Array<A>) => Array<B>;
# map = function (f, arr) { return arr.map(f); } For function add x y :: Number -> Number -> Number
x + y Wich i think open the door to some inteligent pattern matching (i'm going too far ^^): let addFiveToTen :: Number -> Number =
| (10) -> 15
| (a) -> a By the way, very good work with livescript, i'm really enjoying it :) |
What about the lambdas? |
Good point, like I'm not sure to get a concrete use case of a lambda here, do you have any concrete example ? |
The whole thread goes into detail about our ideas. I know it’s fairly long but have you read all proposals? |
Some time ago (#803) I had started working on a PR to add type hints to the language.
That's still something I miss to this day, so I want to bring it back up.
There were a lot of discussions, so I think we should try to discuss it beforehand.
Typing symbol
I suggested the symbol
@:
to add a type annotation. Someone suggested^
. @gkz suggested::
.The reason I want(ed) not to use
::
is because it's rewritten toID:prototype
when it's parsed directly, at which point we have no info on whether it's going to be part of a signature of just a Parens (we only tag()
asPARAM(
andPARAM)
after seeing->
).We'd need to go through the tokens to mark
ID:prototype
asASCR
(or whatever else). We'd also need to be careful not to tag in "defaults" – that is,(x = -> ::) ->
,(x ? ::) ->
,(x and ::) ->
, etc...where to allow typings
I'd suggest we only allow type annotations in:
function
and->
/<-
/...)var
let
generics
I have one suggestion:
[T](xs @: Array[T]) ->
. The reason I suggest[]
rather than<>
is mostly for parse-ability (and because I'm used to Scala...). Tagging[]() ->
is easier than the ambiguities with<>
, we just have to look for a]
when re-tagging a(
as aPARAM(
, and cycle back to[
from there (since an array is never callable anyway).(since
[T]() ->
andxs[T]() ->
make no sense anyway)Parsing the
@: Array[T]
part is harder, but still simpler than@: Array<T>
, where the>
would be parsed as a BIOP because it's directly followed by a)
...@rhendric I'm interested in your PoV, because it's a sensible topic, considering all the ambiguities to keep in mind :-).
The text was updated successfully, but these errors were encountered: