NHacker Next
login
▲Tao: A statically-typed functional languagegithub.com
257 points by memorable 45 days ago | 97 comments
Loading comments...
zesterer 44 days ago [-]
Hey, author here. I definitely didn't expect to see Tao on Hacker News. As is probably obvious, the language is extremely early in its life, and it's not practical to write anything but trivial examples in it yet. Please don't judge!
roguas 44 days ago [-]
Designing a PL is a hard complicated process, very slim portion of professional programmers are able to deliver some novelty here. I think any judgement should be treated as merely a feedback on the usefulness of this language. The effort alone is exceptional accomplishment. Take care.
zesterer 44 days ago [-]
Thanks for your kind words :)
code_biologist 44 days ago [-]
I remember a golden age of programming reddit and HN in the late 2000s that inspired me learn a bunch of new programming languages, Haskell the biggest influence among them. It seemed like people were always posting their own personal languages, lisps (I remember the hot takes on Arc lol), and such all the time. I have very fond memories of going through Jack Crenshaw's "Let's Build a Compiler" after seeing it posted a bunch of times, then trying to build my own language in a similar vein.

Cheers to you, and I hope you're learning and having fun working on your project!

zesterer 44 days ago [-]
Thanks!
jrumbut 44 days ago [-]
I too remember that time and it left me terribly jaded about the utility of new languages.

This is the first I've seen in a while where I think the offering hits a spot on the tradeoff continuum that is distinctive and where I can picture many circumstances where I would want to use this language (the soundness aspect is a big part of that).

dataangel 44 days ago [-]
> A rather dogged and obnoxious opinion of mine is that the 'optimisation ceiling' for statically-typed, total functional programming languages is significantly higher than traditional imperative languages with comparably weak type systems.

Curious about plans for this. When Haskell use exploded I followed a lot of the blogs talking about it someday being faster than C because purity made compiler reasoning easier and stream fusion and other things were going to change the world, but as time went on idiomatic Haskell never came even close to C. Now having a lot of performance experience I think a major factor is that functional languages insist on using lists for everything, which map poorly to hardware. Looking at your screenshots, it looks like it still employs the list paradigm.

zesterer 44 days ago [-]
So... I want to preface this by saying that I'm very, very far from being an expert on this topic. Although Tao's compiler has a MIR optimiser, it only covers the basics (inlining, constant folding + symbolic execution, etc.).

I think one of the main reasons that Haskell failed to solve this is actually the same reason that many lower level languages failed: it places too many requirements on data representation. In the case of C/C++ and even (to a lesser extent) modern languages like Rust and Swift, this is because they make promises about representation to the programmer that allow you to circumvent aspects of the language and still write correct code: be it transmutation, casting, field offsets, etc. In the case of Haskell, lists have an entirely arbitrary length and the language makes no effort to constrain this requirement in the type system, meaning that the compiler can only speculatively optimise a list into an unboxed array. In a language with dependent types, it should be possible to constrain the size of a list with the type system, allowing the optimiser to do its job without need for speculative whole-program analysis.

The other reason Haskell doesn't quite succeed is monomorphisation (or lack thereof). Haskell's support for first-class higher-ranked types means that the language can't feasibly make promises about monomorphisation, and as a result it needs to revert to boxing and dynamic dispatch far more than it really should. Conversely, Tao is designed to monomorphise in all cases from the start.

Rust demonstrates that functional programming (and in particular, programming with higher-order functions) is more than possible to optimise very well, and it does this by promising monomorphisation through the type system, allowing the compiler to aggressively perform static dispatch and inlining.

From what I've seen, GHC also fails to pick a lot of low-hanging fruit. The last time I checked (perhaps this has since changed) GHC often struggles with things like TCO and inlining in relatively simple cases as a byproduct of its design (all functions are dynamically dispatched by default, with inlining being a speculative optimisation).

I need to do a little more writing about exactly what ideas I have for Tao, but other languages of similar ilk demonstrate that Haskell is very far from the pinnacle of what is possible (for example, Koka's Perceus reference reuse: https://koka-lang.github.io/koka/doc/book.html#why-perceus).

shadowofneptune 44 days ago [-]
> all functions are dynamically dispatched by default, with inlining being a speculative optimisation

Could you please point me to other approaches to inlining? I am also working on a personal language and it could be helpful.

zesterer 44 days ago [-]
Rust gives each closure a unique type (see the `Fn` trait: https://doc.rust-lang.org/core/ops/trait.Fn.html), allowing the compiler to always statically dispatch to the exact function body at compile-time. That said, this is a rather more explicit approach that won't necessarily fit everything.
yccs27 44 days ago [-]
Tao looks super cool, and similar to the kind of language I'd personally make if I decided to create one. Kudos for actually implementing yours!

Some specific questions about the language design: - Does your flavor of algebraic effects allow distinct effects of the same type (e.g. two separate int-valued `State`s)? I haven't seen anyone talk about this, but it seems like a potential problem with effects.

- Do you have plans for making pattern matching extensible? I've thought a lot about how this could be possible with optics (the FP construction), but haven't found a nice solution yet.

zesterer 44 days ago [-]
> Does your flavor of algebraic effects allow distinct effects of the same type

Could you give an example of what you mean by this? Multiple effects can be combined together (although I'm still working on handlers for multiple effects), for example:

    effect console = input + print

    fn greet_user : console ~ () = {
        print("Please enter your name:")!;
        let name = input!;
        print("Hello, " ++ name)!;
    }
> Do you have plans for making pattern matching extensible?

I have no plans as of yet. Extensible pattern-matching seems to me to be quite hard to unify with exhaustivity. Tao supports most of the patterns that Rust/Haskell supports.

yccs27 43 days ago [-]
> Could you give an example of what you mean by this?

I mean a type like `yield Nat + yield Nat ~ ()`. Here's how this would occur naturally: Define

    effect yield A is A => ()
    
    fn yield_each A, B, e : (A -> e ~ B) -> [A] -> e + yield B ~ () is
        | _, [] => ()
        \ f, [a .. as] => a->f!->yield!; as->yield_each(f)
    
    fn print_each A, B, e: (A -> e ~ B) -> e + print ~ () is
        f, l => l->yield_each(f)
            handle yield B with x => print(x->show)!
Here my_map should always behave the same as the standard map. The effect `yield B` is handled by collect, and the effect e is handled by the caller. But what happens if the effect e here is also yield?

    fn foo : print ~ [Nat] is
        [1, 2, 3]->my_map(n => yield(n)!; n+1)
         handle yield Nat with n => print("Processing " ++ n->show)!
Now yield_each effectively returns `yield Nat + yield Nat ~ ()`. Does this get combined into a single `yield Nat ~ ()`, so that the handler in print_each handles all yields?
zesterer 43 days ago [-]
Ah, I see what you mean. Yes, effects are always a set in terms of the final monomorphised type. This is an interesting case though, so I'll look into it further and see whether I can come up with sensible, unsurprising semantics. Thanks for pointing this out!
throwaway17_17 44 days ago [-]
I think GP is referring to having two effects, like State1 and State2, both of which allow for stateful effects on int references, and then being able to handle the effects uniquely for each effect. I think the underlying type theoretical question would be are Tao’s effects nominally vs. structurally typed.

A more practical example would be having two state-like effects for use as different allocation/deallocation strategies.

zesterer 44 days ago [-]
Tao's effects are nominally typed, so there's no way to accidentally mix them up in the way I assume you're describing. For example:

    # Define a new effect that yields to the caller
    effect yield A = A => ()

    # A generator that emits numbers
    def one_two_three : yield Nat ~ () = {
        yield(1)!;
        yield(2)!;
        yield(3)!;
    }

    # Print the numbers to the console
    def main : io ~ () = {
        one_two_three
            handle yield Nat with n => print(n->show)!
    }
slavapestov 44 days ago [-]
I'm curious how you implement associated types and equivalences between them. In the most general case, this allows encoding an arbitrary finitely-presented monoid in the type system, and the word problem on finitely-presented monoids is undecidable. However, you mention your type checker is Turing-complete.
zesterer 44 days ago [-]
Yep, this is the case: the type-checker allows arbitrary computation to occur at compilation time. You can see some of that here, where I've implemented peano arithmetic/addition/multiplication/branching using the type system: https://github.com/zesterer/tao/blob/master/examples/type-as... . I've not yet had the time or mental fortitude to implement something more non-trivial like N-queens or a Brainfuck interpreter, but perhaps eventually!
dmix 44 days ago [-]
Nice work on the README. Gets right to the point about what it look likes and what the goals are.
lisper 44 days ago [-]
Actually looks pretty interesting and impressive to me.

One question: does numerical destructing require that one of the terms be a constant? What happens if I write y ~ a + b ?

Also:

> In Tao, arg:f is shorthand for f(arg)

Did you mean arg -> f ?

zesterer 43 days ago [-]
> does numerical destructing require that one of the terms be a constant?

Yes, the general pattern is `n + K`. I'm looking to expanding this to other operations in the future too.

> Did you mean arg -> f ?

Yes. I changed the syntax recently, but forgot to change the README. Thanks for pointing this out!

lisper 43 days ago [-]
> Yes, the general pattern is `n + K`.

OK, so what is the intended benefit of y ~ x + 1 over x = y - 1 ?

I can see an obvious benefit if I can write y ~ x + z where neither x nor z is a constant and the resulting semantics invoke some kind of mathematical constraint management system or backtracking search a la Prolog, but if I'm constrained to write things that can be trivially transformed into a binding I don't see the point.

zesterer 43 days ago [-]
I think my other answer here should provide an answer: https://news.ycombinator.com/item?id=31989227
lisper 43 days ago [-]
Ah, that makes sense. Thanks!
agumonkey 44 days ago [-]
good luck
tm-guimaraes 44 days ago [-]
The promises are great, but will it deliver? It’s seems the language has a really huge scope with lots of hard problems to solve.

The thing I loved about golang was that it’s just “good enough” in lots of areas instead of being the best or perfect. That allowed more time ti works on other parts of an ecosystem.

But since Tao is an hobby project, i just hope the author goes nuts and enjoy working on all those things. It is indeed a cool lang.

zesterer 44 days ago [-]
I am indeed "going nuts"!

As mentioned in the README, I don't see Tao as a production-quality language (at least, for the foreseeable future). I'll leave that to the experts. Instead, I'm more interested in exploring the limits of new language design ideas (effect systems in particular). There are already a few interesting things Tao does that I've not seen elsewhere.

landonxjames 44 days ago [-]
Came across this post [0] by Graydon Hoare (original author of Rust and member of the Swift team) in an HN comment the other day. Lots of interesting discussion about future paths for language design work, including some discussion of Effects Systems.

Curious how Tao is pushing the limits on that front?

[0] https://graydon2.dreamwidth.org/253769.html

zesterer 44 days ago [-]
> Curious how Tao is pushing the limits on that front?

I don't want to put emphasis on "pushing the limits" because I'm still very new to language design and mostly self-taught. There are bigger and better languages pushing the envelope further than Tao!

That said, I've been experimenting with:

- Expressing totality and type inhabitance in the type system

- Effect systems (including user-defined effects, effect handling, parametric effects, lowering to monadic IO, etc.)

- Typeclass inference via typeclass variables (Tao can handle several cases that are ambiguous even in Rust, such as the following: https://github.com/zesterer/tao/blob/master/examples/debug.t...)

skavi 44 days ago [-]
would you mind explaining the linked code?
zesterer 44 days ago [-]
The code is a bit of a gimmick, not really the sort of thing you'd have in a real program. It's just there to demonstrate that the compiler doesn't need explicitly telling which typeclasses associated types correspond to. That said:

    fn show_negative A : A -> Str where
        A < Neg,
        A.Output < Neg,
        A.Output.Output < Neg,
        A.Output.Output.Output < Neg,
        A.Output.Output.Output.Output < Show,
    =
        a => (----a)->show
This is a function, called `show_negative`, that is generic over a type, `A`. The function takes an instance of `A` and returns a `Str` (string).

In terms of implementation, the function is quite simple: it just negates the input value a bunch of time and then `show`s it as a string using the `Show` typeclass (equivalent to Haskell's `show`: https://hackage.haskell.org/package/base-4.16.1.0/docs/Prelu...).

Arithmetic operations such as negation are defined using typeclasses. Here's the definition of `Neg`:

    class Neg =
        => Output
        => neg : Self -> Self.Output
This means that types that can be negated must provide two things: an output type produced when they're negated, and a function that actually performs the negation. For example, `Nat` (natural number) looks like:

    member Nat of Neg =
        => Output = Int
        => neg = fn x => (implementation omitted because it's a built-in intrinsic)
i.e: when you negate a `Nat`, you get an `Int`.

The `where` clauses on the `show_negative` just constrains the output type of negating the `A` in such a way that its output type is also constrained, in a chain, with the final type in the chain being `show`-able.

epgui 44 days ago [-]
I just want to say that you’re awesome, and I love your attitude hehe.
zesterer 43 days ago [-]
Thanks!
shirogane86x 44 days ago [-]
This looks great! I've actually been experimenting with making a functional language too, in rust - so I'm probably gonna read through the code soon! Also, those error messages look really good - I'm totally gonna try ariadne and chumsky out :)
zesterer 44 days ago [-]
Good luck! Feel free to open a discussion topic or issue if you have any questions!
throwaway17_17 44 days ago [-]
Since someone brought it up, and since I don’t speak rust… are the error message formats (i.e. the lines and crossings) made by a rust library or did you implement them? They are certainly visually nice.
zesterer 44 days ago [-]
They are made by a Rust library, but it's a library I wrote for this project (now used by others too): https://github.com/zesterer/ariadne
Akronymus 44 days ago [-]
A few observations:

Seems very ML/Haskell inspired.

All tuples seem to require parenthesis.

Definitely not ergonomic on a qwertz layout (Altough, what language is?)

No current plans for an interactive/repl version it seems

No dependant types either.

Overall, pretty interesting. Definitely warrants a closer look.

zesterer 44 days ago [-]
Pretty much!

The language does support limited sugar for tuples without parentheses in some specific cases, such as `match` and `let`:

    let x, y = 5, 4 in
    ...

    match x, y in
        | 4, 7 => None
        \ _, y => Just y
However, these cases only incidentally lower to tuple pattern matching, and deliberately hide the fact that tuples are used internally.
Akronymus 44 days ago [-]
Are you planning on having something similar to computation expressions? Altough, I guess thats basically covered within the do notation.

https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...

zesterer 44 days ago [-]
Yes! As you say, the do notation + monads covers this, but I'm planning to remove it in favour of a slightly more general 'effect basin' syntax that I'm currently working on (syntactically similar to Rust's async + await, but generalised to all effectful operations like mutation, IO, etc.). Example here: https://github.com/zesterer/tao/blob/master/examples/mutate....
platz 44 days ago [-]
> No dependant types either.

You're funny

Akronymus 44 days ago [-]
How so? I personally find dependant types to be very interesting, but I get that they are a PITA to implement. So, for me, it was simply an observation that there are currently no plans for dependant types. No value judgement intended.
isaacimagine 44 days ago [-]
Not to be pendantic, but I think you made a joke without realizing it.
Akronymus 44 days ago [-]
I did? I have now spent like 20 minutes trying to figure it out. But I still don't get it.
macintux 44 days ago [-]
I'm guessing it has something to do with the keyword "either" and Haskell types, but I'm otherwise clueless. Every time I look at Haskell I run the other way.
zesterer 44 days ago [-]
I think it was "value judgement" (dependent types allow dispatching over a value in the type system)
solatic 44 days ago [-]
> Totality

Best of luck. Dhall is a current language with totality, but it runs into impossibly high memory requirements as a result. We had CI workers with 16 GB RAM run into out-of-memory issues because of the sheer size of the totally expanded type space (sum types of types which are themselves sum types of types which are themselves sum types etc... Exponential growth is easy).

I appreciate that this is scoped as a hobby project for now, not recommended for production, so like I said, best of luck :)

atennapel 44 days ago [-]
I'm not sure what you mean with totally expanded type space. But it sounds like Dhall has an issue with unfolding/normalisation.

Totality shouldn't have any special impact on memory usage as far as I know. It just restricts the kind of recursive functions and data types you can write.

solatic 44 days ago [-]
If you have a sum type `< a : A | b : B | c : C >.a`, then A, B, and C need to be unfolded. If A itself is of type `< j : J | k : K | l : L >`, and J is also a sum type, etc., then unfolding everything can result in exponential memory usage and exhaust the amount of available memory. Of course there are ridiculously high-memory systems available, but then it's no longer economical.
lloydatkinson 44 days ago [-]
Somewhat familiar with FP and enjoy some of its benefits in my projects but I haven’t really used a full blown FP language like Haskell. What exactly is a type class? I haven’t found an explanation that makes a lot of sense to me. I think at one time I believed them to be some kind of contract like an interface in a OO language.

Can someone explain?

zesterer 44 days ago [-]
A typeclass (or in Rust, a trait) is something that describes a set of behaviours that must be implemented by a type (in OOP languages, abstract classes are broadly equivalent).

For example, the typeclass `Eq` describes how values of a type might be compared for equality.

    class Eq =
        => eq : Self -> Self -> Bool
The typeclass requires that types conforming to it provide a function, `eq`, that takes two instances of itself and returns a bool (indicating whether the instances are equal). And member of this typeclass might look like:

    for A, B member (A, B) of Eq where
        A < Eq,
        B < Eq,
    =
        => eq = fn (a0, b0), (b0, b1) => a0 == a1 && b0 == b1
You can read this as "for any two types, A and B, a tuple of both can also be compared for equality provided both A and B can themselves be compared for equality"

Typeclasses become useful because they allow you to constrain generic type parameters. For example, we can now write a function that determines whether a list contains a matching value:

    fn contains A : A -> [A] -> Bool where
        A < Eq,
    =
        | _, [] => False
        \ k, [x .. xs] => x == k || xs->contains(k)
All of this is checked at compile-time by the compiler, ensuring that the function can only be called with types that can be compared for equality.
lmm 44 days ago [-]
One of the things that's hard to follow is that people conflate typeclasses and typeclass instances.

So, the typeclass itself is like an interface, but rather than having classes that implement that interface directly, you have typeclass instances which are like an adapter between a value (struct) and the interface - or like a virtual function table. You know how a class instance is essentially a struct + a (pointer to a) virtual function table? Imagine making those two pieces more distinct in code - the typeclass instance is the stanadlone virtual function table.

So your object state is more exposed - you can't really have private fields in this paradigm since your state is just a struct value. But it's clearer what's what, and it's much easier to adapt types to interfaces even when the authors didn't know about each other, since the typeclass instance can be defined separately from both the typeclass and the value type (although there are possible inference problems with this). So lots of secondary cross-cutting functionality problems where OO languages tend to end up with an ad-hoc type registry (e.g. serialization or database mapping - just look at what projects like jackson-datatype-joda or joda-time-hibernate have to do to get themselves registered) are easier to solve in this model.

erezsh 44 days ago [-]
Asking as a complete newbie in this paradigm, but what's the benefit of writing factorial like this?

    fn factorial = 
        | 0 => 1
        \ y ~ x + 1 => y * factorial(x)
Instead of

        | y => y * factorial(y-1)?
zesterer 44 days ago [-]
Subtraction of natural numbers is non-total, and can result in negative numbers, which cannot be represented as a natural number. Most languages throw an exception, panic, etc. None of these are solutions I'm happy with.

In Tao, natural numbers don't support subtraction but you can decrement them by pattern-matching as in this example, allowing the compiler to 'check your workings' and statically prove that your program can never result in underflows.

In a similar vein, you can't index directly into a list (because indexing is a fallible operation). Instead, you must pattern-match on the list and explicitly handle the empty case (you can also write a utility function to do this for you like so)

    fn nth : Nat -> [A] -> Maybe A =
        | _, [] => None
        | 0, [x ..] => Just x
        \ n + 1, [.. tail] => tail->nth(n)
Used like:

    [1, 2, 3]->nth(1) # Evaluates to `Some 2`
maweki 44 days ago [-]
So is the language planned to be computationally complete or total?

I was not completely clear on your definition of totality. But I am all for not Turing-complete models of computation that still do useful things.

zesterer 44 days ago [-]
For now, just total (i.e: functions can't panic, throw exceptions, etc.). I'd like to explore termination analysis in the future though!
samatman 44 days ago [-]
It's an unfortunate conflation between total functions and total functional programming, which is done with non-terminating languages. If I'm reading you right, Tao currently requires functions to be total but is not designed to always terminate.
zesterer 44 days ago [-]
Yes. This is something I've been thinking about a lot, as it happens! In particular, one of the great benefits of pure languages from an optimisation standpoint is that unused values can always be optimised away, but this is not the case if we treat termination (or non-termination) as a side effect! For now, I'm treating it as not being a side-effect (as does C++, although C++ goes a step further and treats it as straight-up UB in certain conditions). This is something I've still yet to nail down semantically.
LelouBil 44 days ago [-]
A couple of months agow I had never tried functional programming languages. Having learned Rust in the past year (and loving it ) made me want to try out Haskell since I always wondered about functional languages and I likes Rust's strong type system.

I go a bit dissappointed with Haskell, but right now I'm reading Tao's README and this looks like everything I've ever wanted ! I'm gonna try it out right now.

throwaway17_17 44 days ago [-]
Can you expand on what disappointed you about Haskell. I’m not looking to convert you or anything, just curious.
daosound 43 days ago [-]
Hi, do you have a link to your language? You teased it quite a bit and I'm very curious to learn much more.
throwaway17_17 42 days ago [-]
I’m not sure if you are checking this random post, but I talked myself into doing some writing on the language (in a presentable format as opposed to my usual .txt file scribbles). Since you are the only person who actively asked for details/info what kind of things would you want to read about? I could write a large amount about my particular beliefs about software, type theory, category theory, and language design, but I would end of writing unconnected ideas all over the place. So, I am trying to focus on presenting the language and using it as an example of the implementation of those topics and a jumping off point for further descriptions of ideas. I think I’m down to two primary options for presenting some general info as a starting point:

a) doing a `front page` type thing laid out like the why-Koka page for the Koka language [1]. (essentially a brief section about each foundational theoretical aspect) Then having links put in for more theoretical/formal information as I write it up.

b) just doing narrative style ‘blog’ post writings about whatever concepts people may want to hear about.

I’m leaning towards option A, but without quite such a strong emphasis on how to use the language and more focus on what the language is. But if you think it would be more informative to just read randomly connected blogs let me know. I’ve never done this before so I don’t have a clue what other people find interesting.

1 - https://koka-lang.github.io/koka/doc/book.html#why

throwaway17_17 43 days ago [-]
I didn’t intend to have the conversations I’d had on HN to appear like teasers (although that is a cool thought). I don’t have any public write ups on it or a public distribution point. It was pretty heavily argued that I was being unreasonable about not releasing, at a minimum, a blog about it for ‘fear’ of unwanted criticism. I don’t have a GitHub (all my code is proprietary for my job so it is never released) so I may try and set up a repo or try and find a place to host a blog.
daosound 43 days ago [-]
Or you can just link a scribd or google docs link with details about the language's design...until you decide on where your blog will be.
throwaway17_17 43 days ago [-]
That’s not a bad idea, and it would certainly be faster than working out where to host a blog.
slewis 44 days ago [-]
Very cool! This might be a naive question. Are your arithmetic patterns equivalent to dependent types, like those found in Idris?
zesterer 44 days ago [-]
They're more akin to 'destructuring' natural numbers into successors. Consider:

data Nat = Zero | Succ Nat

You could pattern match on this Nat, fetching the inner Nat, allowing the type system to prove that all cases are handled exhaustively. Arithmetic patterns just extend this notion to the built-in Nat type.

Full dependent typing is something I'd like to experiment with in time, but I'm not there yet.

toastal 44 days ago [-]
I don't understand what the possible use case for using a \ for the final bar for sum types would be. It would just make it a diff to add a new item at the end. I was hoping it had Unicode support looking at the example screenshot, but no, it's just a ligature font confusing users.
zesterer 44 days ago [-]
Tao isn't indentation-sensitive, so nested `match` expressions are ambiguous without the trailing \ branch.

I don't know whether I'll keep this syntax though. I'm increasingly wondering whether it's better to just bite the bullet and go all in with indentation sensitivity.

That said, the existing \ syntax can be quite nice to read: https://github.com/zesterer/tao/blob/master/lib/parse.tao#L5...

ratww 44 days ago [-]
The \ syntax is my favorite part so far, I’m definitely gonna copy it for my toy language, hope that’s ok ;)
zesterer 44 days ago [-]
Go for it! IIRC it was suggested by someone else in the #langdev channel on the Rust Community Discord, so I can't claim to own the idea.
catlifeonmars 44 days ago [-]
Why not just require parents for nested cases? IMO this is fairly unambiguous, even to people unfamiliar with a particular language.
zesterer 44 days ago [-]
In a previous revision of the language, I did! (https://github.com/zesterer/tao/tree/old). I decided against it because, in short, I find the trailing delimiters to be quite ugly.

Because this is purely a personal project, I'm thankfully not constrained by such mundane concerns as "ergonomics" (unless such concerns relate to me while I'm working with it). I think Pythonic syntax is probably the way to go long-term though...

patrickkidger 44 days ago [-]
Personally I'm a big fan of Julia's syntax here. Indentation-insensitive with the use of the word "end" as the terminator for all types of block. In practice you can lay it out like Python, and the "end"s help you keep track when e.g. you need to place a single expression after some deeply-nested blocks.
catlifeonmars 35 days ago [-]
Is indentation actually necessary to parse a block? I think if you have a well defined terminator (a literal keyword token, such as `end`), indentation can just be a function of an autoformatter with no indentation constraints on syntax.
pjmlp 44 days ago [-]
I guess not to mix with Tao, next gen OS for Amiga, based on bytecode as distribution format.

https://www.edn.com/amiga-reborn-via-tao-alliance/

magicalhippo 44 days ago [-]
Great readme, and definitely made me want to play with it. I was raised on procedural programming and never really got into proper functional programming, but somehow this just clicked for me.
darthrupert 44 days ago [-]
Hardly ever is "good REPL" one of the features of these new languages. Do they think it's irrelevant or is making such a thing too difficult compared to all these more CSy features?
zesterer 44 days ago [-]
REPLs are often difficult to reconcile with AoT compilation and static analysis, particularly in the context of things like type inference (at least, in a way that preserves semantics). It's on my mental todo list, but not a priority for me.
catlifeonmars 44 days ago [-]
How fast is the compiler? In many cases a report can just be syntactic sugar for compiling and running an accumulating log of source code lines. It’s not really important how it works under the hood as long as it’s mostly transparent to the user.

As a side effect, it might also be a good way to keep your compile times down.

zesterer 44 days ago [-]
Fast enough for this approach to work for small examples. For longer examples, I'm less confident: I've yet to implement a proper module system (other than a slightly more principled version of C's #include) so this currently means compiling the entire standard library on every REPL line. Thankfully, the compiler can still handle this within about 100 milliseconds on my machine. Perhaps when I have a more rugged module system I can look into this more, thanks for the suggestion!
substation13 44 days ago [-]
What if one of those lines fires the missiles?
darthrupert 44 days ago [-]
That's not even close to being adequate for the kinds of things good REPLs are good for.
pohl 44 days ago [-]
Do you suppose that might be an outmoded historical bias? I'm wondering if maybe the advancement of hardware has made it so that the costs of AOT compilation and a type system should no longer stand in the way of delivering a reasonable REPL experience. Swift's "playgrounds" seems to do alright — a real outlier in this regard.
skavi 44 days ago [-]
Haskell has a REPL. No idea whether it’s considered good though.
okasaki 44 days ago [-]
Sorry, what's that font in the github screenshots?
zesterer 44 days ago [-]
Iosevka Fixed Slab Semibold
kortex 44 days ago [-]
Nice work! Looks super interesting and rewarding to work on.

Is the namesake "tao" meaning "the way"? Or perchance a nod to Terry Tao?

zesterer 44 days ago [-]
> Tao or Dao is the natural order of the universe whose character one's intuition must discern to realize the potential for individual wisdom

It echoes how I feel when I write programs in languages with strong static type systems: that of the compiler almost fatalistically leading me towards a solution that naturally fits the problem space. A metaphor I like to use is that of an artist knocking away pieces of marble to reveal the statue that was always there beneath.

And, yes, it's great fun to work on! Everybody should give writing a compiler and designing a language a try!

schaefer 44 days ago [-]
The Tao that can be told is not the true Tao.
mbrodersen 43 days ago [-]
What is the Meaning of non-Meaning?
iamwil 44 days ago [-]
What were the series of resources or readings that you did to help you create Tao?
zesterer 44 days ago [-]
There wasn't really any single resource I read. Frustratingly, I find it quite difficult to read research papers (I'm told it's a skill acquired with practice, but it's something I've never gotten into). Most of the implementation is the product of several rewrites and false-starts, along with the occasional hint I've read about here or there on the web.
iamwil 43 days ago [-]
Ah, a trail of even just the web articles you read that was helpful would be helpful, as part of people looking to Tao for inspiration. Consider writing them down in the wiki part of the repo as you go along going forward!
galaxyLogic 44 days ago [-]
What about OOP classes and inheritance? Data hiding? Modules?
zesterer 44 days ago [-]
Tao has typeclasses (like Haskell) that allow for compositional parameteric polymorphism. It also has data types (records, sum types, etc.) and functions, but deliberately does not bundle the two together.

There is no module system yet (only a temporary C-like #include system): that's on my list of things to work on!