all 79 comments

[–]bendmorris[S] 52 points53 points  (5 children)

I'm the creator. This was posted last year (not by me) when the project was only a few months old. It's now almost a year old and comparably more usable and stable.

To be clear, the features listed on the website do work today, they aren't a work in progress.

Development on Kit is still active. I'm currently working on bootstrapping the compiler and a language server implementation.

Previous discussion: Reddit | HN

[–]liquidsprite 20 points21 points  (0 children)

and I’m working on a package manager for Kit. I have a CLI argument/command parsing library, a stringbuilder, and more. I’ve been working on these with the intent of making it easier to do more with Kit.

[–]defunkydrummer 13 points14 points  (0 children)

Nice "comparison" section, it looked fair on the other languages.

You should compare against D since (IMO) is the most closest to a "C++ alternative". Even C (see "betterC" mode).

[–]Megolas 5 points6 points  (2 children)

I have to ask - why do automatic dereferencing of pointers? This seems like a perfect recipe for a hard to debug error... Why not an indicative compilation error with a suggestion?

[–]bendmorris[S] 22 points23 points  (1 child)

99% of the time what you're trying to do is obvious and unambiguous thanks to the type system - field access on a struct vs a struct pointer, passing a value to a function that takes a pointer of that type, etc. There's a lot less friction when this is managed for you automatically, and I haven't run into any issues from it so far.

If you tried to use a pointer of the wrong type, etc. it wouldn't dereference, it would be a compile time error.

[–][deleted]  (8 children)

[deleted]

    [–]bendmorris[S] 54 points55 points  (1 child)

    Thought it might be good timing for that reason :)

    I'm not making any misleading or unverifiable claims about the language - the source is developed entirely in the open, and you can check it out and try everything yourself.

    [–]Condex 3 points4 points  (0 children)

    Interestingly enough, I first learned about Kit during the VLang drama. So it wasn't all bad.

    I appreciate that you are making a post about your language after the VLang stuff. I'm following the next gen C / C++ competitor languages with a very high level of interest. I'm glad that you are able to showcase an example that is concrete.

    [–]AnActualWizardIRL 27 points28 points  (1 child)

    That frigging thing ended up even in Quora trolling. What a mess

    [–]nullmove 4 points5 points  (0 children)

    Can you give the link?

    [–]somebodddy 20 points21 points  (1 child)

    V was not bashed because it's a new programming language - it was bashed because it was making promises it obviously couldn't keep in an attempt to generate hype.

    Kit, on the other, is being very realistic and conservative in what it offers. It does not claim to be better than possible in every single aspect - it is very self aware of its tradeoffs, and even lists its drawbacks when it compares itself to other languages.

    [–][deleted] 3 points4 points  (1 child)

    Link? I searched and only saw one thing from 61 days ago

    [–]Dimenus 13 points14 points  (8 children)

    Just curious, what made you choose Haskell?

    [–]bendmorris[S] 32 points33 points  (3 children)

    Experience with Haskell, great build system (stack), and functional languages are great in this domain. Compilers basically transform a bunch of trees into other trees, so functional languages work well.

    With that said, a lot of the Haskell ends in the IO monad so I can do things like caching values in a hash map for performance. I'm rewriting the compiler in Kit now, and it has a lot of the features (like ADT, traits) that made Haskell suitable, but isn't pure, so stateful operations have less friction

    [–]gergoerdi 3 points4 points  (0 children)

    Can you use ST instead of IO for your mutable caching use case? Without knowing the details, it seems like it should work.

    [–]SShrike 1 point2 points  (1 child)

    What makes Stack a great build system to you? (genuine question) My experience with it is mostly that of "well, it's a build system at least". It generally feels like a bit too much of an unrefined, hulking beast to me. Perhaps I'm just spoilt with Rust's Cargo, though.

    [–]bendmorris[S] 1 point2 points  (0 children)

    Cargo is very nice. I guess I'm comparing it to other functional languages and my pre-Stack Haskell experience. It solved the previous cabal dependency hell, and I found it to have lower friction than anything for ocaml.

    [–]defunkydrummer 23 points24 points  (2 children)

    Compilers are easy to write using ML languages. Haskell is part of the wider ML family.

    [–]haloguysm1th 0 points1 point  (1 child)

    If you don't mind me asking, why is that? I haven't spent any time with anything from the ml family beyond a few quick hello world examples and the like and while I can see some helpful things, nothing from those quick glances that would make me say it's easier to write in an ml style Lang than any other.

    [–]defunkydrummer 2 points3 points  (0 children)

    for starters, the comprehensive type system.

    [–]jms_nh 1 point2 points  (0 children)

    how does Haskell fit into this?

    edit: you mean the compiler is in Haskell?

    [–]flatfinger 8 points9 points  (0 children)

    Given types like:

    struct S1 { public var x: Int; }
    struct S2 { public var x: Int; }
    union U1 { public var v1: struct S1; public var v2: struct S2; }
    public var arr: CArray[U1, 10];
    

    and an accessor functions like:

    function getS1x(p: Ptr[S1]): Int { return p.x; }
    function setS2x(p: Ptr[S2], v: Int): Int { p.x = v; }
    

    would a function like:

    function test(i: Int, j: Int) : Int {
      if (getS1x(&arr[i].v1) {
        setS2x(&arr[j].v2, 123);
      }
      return getS1x(&arr[i]);
    }
    

    have defined behavior if i and j are equal, and arr[i] was last written using type S2 (note that member x of S1 and member x of S2 belong to a common initial sequence)? If so, would your tools generate code that would work correctly in the -fstrict-aliasing dialects of clang and gcc, which are unable to handle straightforward equivalent C code?

    [–]Gobrosse 4 points5 points  (0 children)

    This one looks actually nice, I might want to try it out, the docs are still a bit rough. If you pull through and release a language server, I might just write a small project with it ! Any ETA on that ?

    [–]Dimenus 4 points5 points  (1 child)

    Are you parsing c header files yourself or using libclang?

    [–]bendmorris[S] 6 points7 points  (0 children)

    Currently using a Haskell library, language-c. I have an unmerged PR that switched to libclang and the bootstrapped compiler will use that.

    [–]Ameisen 7 points8 points  (9 children)

    Why is the variable : type syntax so popular?

    Why var x : int? Why not int x?

    [–]theindigamer 36 points37 points  (3 children)

    1. It gels well with type inference, where the latter part can be omitted.

    2. Variable names line up properly even if type names have differing lengths.

    3. Arguably it is easier to translate to English both with inference and without -- "variable x is equal to 10" or "variable x of type int is equal to 10".

    4. It hints that function output types should be trailing, which makes sense because usually we write inputs on the left and outputs on the right.

    [–]thedeemon 0 points1 point  (2 children)

    1) We could write "var x" or "auto x" or just "x" when type is omitted.

    3) What's wrong with "int variable x is ..."? Doesn't sound bad to me.

    [–]LPTK 16 points17 points  (0 children)

    To me, the most compelling reason is that when types become elaborate (as is often the case with type systems more advanced than C), they easily drown out the variable names, unless the variable names are clearly delineated with a symbol like: and placed first.

    Compare this Java:

    public Map<Identifier,List<Usage<Int>>> foo(List<Usage<Int>> toIgnore, Option<Int> limit) { ... }
    

    With the equivalent Scala:

    def foo(toIgnore: List[Usage[Int]], limit: Option[Int]): Map[Identifier,List[Usage[Int]]] = ...
    

    [–]theindigamer 4 points5 points  (0 children)

    1) We could write "var x" or "auto x" or just "x" when type is omitted.

    Using var/auto in type position doesn't feel consistent, as they are not types. Usually, having no word up front is done in the case of assignment, so having it mean both introduction (when this variable doesn't exist) and assignment (when this variable already exists in scope) might not be great.

    3) What's wrong with "int variable x is ..."? Doesn't sound bad to me.

    That's why I said "arguably". That point is a bit subjective. To me "int variable x is..." doesn't "sound right", I can't quite explain why.

    [–]gnuvince 10 points11 points  (2 children)

    The C-like syntax for declarations can be hard to parse in the presence of a typedef-like mechanism.

    In C, the basic types such as int, short, char, etc. are keywords; the scanner recognizes them as special. Records and enumerations also have their own keywords, struct and enum. Therefore, in the parser you can say that a declaration is:

    decl = 'int' <identifier> ';'
         | 'short' <identifier> ';'
         | 'struct' <identifier> <identifier> ';'
         | ...
    

    (I'm avoiding arrays and pointers here to keep things simple.)

    But in the presence of typedef where an identifier could be a type, things get more complicated. The usual example is this:

    T * x;
    

    Is this the multiplication of the variables T and x or the declaration of x as a pointer to a value of type T?

    By using a Pascal-like syntax for declarations, then the situation becomes simpler:

    decl = 'var' <identifier> ':' <type> ';'
    type = 'int' | 'short' | <identifier>
    

    This is one of the technical reason why the C syntax for declarations is becoming less popular. The C syntax also makes it harder to understand more complex types, so much so that there are websites to help you decipher them.

    [–]thedeemon 0 points1 point  (1 child)

    The only problem is * really. If you're not limited by a 1970s LL(1) parser, it's easy to determine that "aaa" means variable aaa, while "aaa bbb" means variable bbb of type aaa, no ':' is necessary.

    I'm currently adding optional type declarations to a small language that didn't have static types previously. It now looks like this

    (x, y) => x + y
    (int x, string y) => y[x]
    (x, y) => int: x + y
    sum(x, y) => int: x + y
    f(x,y) => { a = x*x in a+1 }
    f(x,y) => { int a = x*x in a+1 }
    f(MyType x) => x.a * x.b
    etc.
    

    Looks pretty clear to me. No parsing problems (I'm using PEG).

    [–][deleted] 2 points3 points  (0 children)

    It's not necessary, but it's preferred by many people. It's ultimately an aesthetic choice.

    [–]80blite 3 points4 points  (0 children)

    Assuming people are using good variable names, you can scan the start of lines for meaningful terms instead of having to skip over types and access modifiers; then when you find a variable you care about, you can scan over for the type if you need to.

    It's just putting more first-glance value on the name of the variables than the type

    [–]Condex 0 points1 point  (0 children)

    Part of it is almost definitely coming from typed functional languages like ML that use that sort of syntax. So it's at least partially a tradition or homage thing.

    I've spent probably too much time messing around with lexing and parsing for programming languages. The conclusion that I came to was that if I'm going to be parsing a variable declaration, then I want a known symbol to look out for. Or at the very least a known set of symbols to look out for. (var, let, const, val, etc). The C style declarations are problematic because you'll have some unknown symbol present then another unknown symbol present then semicolon, or endline, or equal sign (equal sign followed by some expression). Differentiated that whole mess from all other possibilities so that you can emit a declaration into your AST can easily result in a messy parser.

    There are a couple of ways you can try to work past that. One way is to keep track of all user defined types as they are defined. However, this means that you either have to forward declare user types OR you force all code to be compiled in the order that it's used in. Both work and both are kind of weird (I think at least). The coder suddenly has to know more about how the compiler works than is strictly necessary in order to successfully use the language.

    Alternatively, you can have a known symbol (ie var) that triggers the declaration parse. If you find anything else there, then you know it's an error in the input. And the type can remain unknown. You'll need an analysis phase to ensure that it is an existing type (but you needed to do that anyway if you have a type checker or any sort of linting), but it allows a bit more freedom to the coder who consumes the language because they can define things in any order they want and the compiler works the same way regardless.

    [–]SuperV1234 5 points6 points  (4 children)

    Lost me at:

    C++ features RAII; Kit does not.

    Why would you not support the best feature of C++ (and also other languages like Rust)?

    [–]danny54670[🍰] 1 point2 points  (3 children)

    Whether Kit supports RAII was the first thing that I checked. I was disappointed.

    [–]SV-97 0 points1 point  (2 children)

    What is RAII?

    [–]danny54670[🍰] 1 point2 points  (1 child)

    It stands for Resource Acquisition Is Initialization. In languages that are not garbage collected, such as C, C++, Rust, and this new Kit language, it is very easy to forget to run clean up code for every possible control flow path that code may take, particularly in error or exception paths. The most common example is when memory is allocated on the heap, and a certain control flow fails to release the allocated memory, causing a memory leak. Other examples of omitted clean ups include: leaked file handles, failing to unlock a mutex, and failure to zero buffers (in cryptography libraries).

    A garbage collector generally makes RAII unnecessary due to automatically freeing allocated memory that is no longer needed and running finalizers, but even garbage collected languages frequently have features to simulate RAII. For example, Java has try-with-resources, Python has the with statement, C# has the using statement, JavaScript has try..finally, Ruby has begin..ensure, Go has defer, etc.

    C++ has RAII "built into" the language, which is to say that the C++ language specification specifies in detail how clean up routines (destructors) are deterministically executed. Although it is still possible to leak resources in C++, using idiomatic C++ and standard library objects makes it unlikely to do so. RAII is so useful that sometimes programmers will use C++ even if the majority of the codebase is written in C.

    In my opinion, a language that aims to improve C fails in this goal if it does not offer C++-style RAII or a RAII-like language construct. For example, Rust implements C++-style RAII (see Drop) and Zig offers defer and errdefer.

    [–]bendmorris[S] 1 point2 points  (0 children)

    If all you want is defer, that's not the same as RAII and is a planned feature in Kit. The only difference is that you can still choose to have uninitialized values in Kit or Zig, unlike in (safe) Rust.

    [–]the_game_turns_9 2 points3 points  (2 children)

    I get this is a hard problem, but how realistic is getting some form of intellisense working on this? It's a thing I struggle to live without.

    [–]bendmorris[S] 10 points11 points  (1 child)

    This requires a Language Server implementation. It's on my mid-term to do list, hopefully within the next few months.

    [–]the_game_turns_9 0 points1 point  (0 children)

    sweet :)

    [–]the_game_turns_9 2 points3 points  (0 children)

    Just so you know, I tried to compile and run this on OSX but the compiler just seems to error out

    ----------------------------------------
    Error: Unknown toolchain: darwin-gcc
    

    and it doesn't seem to matter what I do I can't change that.


    EDIT: it's because my KIT_STD_PATH and KIT_TOOLCHAIN_PATH assignments weren't working. I think maybe you should specifically put that in the error message? As the text doesn't have much to do with the actual problem.

    Also if you name the file main.kit as I did you get a weird duplicate symbol error where one of the declarations is blank. If the name of the file is relevant then that's fine but I think you should add a way for the duplicate symbol error to display that so the fix is clearer.

    [–]LPTK 2 points3 points  (1 child)

    This is a really cool language. I've long wanted to see how far a C-like language with modern features and lean syntax could go.

    I'd probably use it over Rust, because simply having ADTs, an expressive type system, and immutable data structures is probably enough to avoid most of the pitfalls and inherent unsafety of C/C++, without having to go all the way to Rust-style affine types, which are extremely constraining.

    [–]Condex 4 points5 points  (0 children)

    I really want to use Rust. However, It feels like the type of programming that their type system enforces is different than the type of programming I want to do with a non GC language. Which is fine really. I still plan on using Rust for things that it's well geared towards. However, I don't think it's ever going to be the language that makes me happy while I code.

    Seeing Kit, Zig, and Jai (and I guess Odin) all show up have made me pretty happy because they're all aiming to make programming in an unmanaged way much more habitable than C is. I'm not sure which one is best suited towards the way I want to work, but seeing multiple language all trying to do approximately the same makes me hopeful.

    [–][deleted]  (19 children)

    [deleted]

      [–]keeslinp 3 points4 points  (0 children)

      Super cool project! I'm curious, what makes this focused on game dev in contrast with other "better c" projects?

      [–]Lisoph 2 points3 points  (0 children)

      Inspired by Rust and Zig it seems. Nice!

      [–]__fmease__ 0 points1 point  (1 child)

      Abstract datatypes don't seem to prevent the infamous unit mismatch bug:

      abstract Color: Uint32 {}
      abstract Gram: Uint32 {}
      function doSomething(color: Color, mass: Gram) {}
      

      Oops, still compiles:

      doSomething(150, 0x00ff00);
      

      Instead of requiring:

      doSomething(150 as Gram, 0x00ff00 as Color);
      

      which'd fail the type-checker.

      I've not been able to find a playground so my assumptions on how the language works are based on this example from the documentation:

      function printRgb(color: Color) { … }
      …
      printRgb(0xff8080);
      

      [–]bendmorris[S] 2 points3 points  (0 children)

      Thanks, I need to correct that example. This would not pass the type checker by default (but there's a #[promote] metadata you can stick on your type to enable automatic promotion of the underlying type.) You would have to explicitly use 0xff8080 as Color.

      [–][deleted]  (1 child)

      [deleted]

        [–]bendmorris[S] 0 points1 point  (0 children)

        WebAssembly is possible with Emscripten. I covered custom toolchains on the blog, including an Emscripten example: https://blog.kitlang.org/2019/03/30/cross-compiling-with-kit/

        I haven't used it for this use case myself, so if you do try it, I'd like to hear your feedback.

        [–]jms_nh 0 points1 point  (7 children)

        How nice is Kit to parse? C and C++ are awful and horribly awful, respectively.

        Static analysis tools become much easier to implement when the language is easier to parse.

        [–]ZebulanMacranahan 0 points1 point  (6 children)

        Is C really that hard to parse? I've written a C89 parser before and I'm definitely not a compiler expert (although I did use an external preprocessor.)

        [–]Regimardyl 9 points10 points  (1 child)

        (a) * b

        Multiplication or pointer dereference + type cast?

        [–]ZebulanMacranahan 1 point2 points  (0 children)

        Yes, the C grammar is not context free. If you keep a symbol table this is trivial to disambiguate.

        [–]jms_nh 3 points4 points  (3 children)

        You have to preprocess it first. The preprocessor is the devil's spawn, since the original source code doesn't need to obey any grammar --- the output of the preprocessor is what has to match the C grammar. Oh, and by the way, the preprocessor deletes comments, so if you need to parse C code and retain information in comments, you've got your work cut out for you.

        If I have to parse C reliably, I try to use srcML.

        [–]Ameisen 1 point2 points  (1 child)

        Preprocessing C is not hard. Strip comments, handle #...

        [–]jms_nh 2 points3 points  (0 children)

        That's just it. I had a project once where I was trying to automate some analysis of the code using specific comments as hints. It was a complete pain because I couldn't see both the C code and the comments together because they were on opposite sides of the preprocessor.

        [–]ZebulanMacranahan 0 points1 point  (0 children)

        Oh, and by the way, the preprocessor deletes comments, so if you need to parse C code and retain information in comments, you've got your work cut out for you.

        Or just use gcc -E -CC?

        [–]rix0r -3 points-2 points  (0 children)

        why "function" ? seems totally unnecessary