CoffeeScript for TypeScript(civet.dev) |
CoffeeScript for TypeScript(civet.dev) |
I wouldn't use it in a team project subjecting my teammates to learn "yet another thing" but can see myself using this for personal projects
> First, copy the entire file content over to a new file called src/Main.res by using our %%raw JS embedding trick: > %%raw(`const school = require('school'); etc. `)
I stopped reading there - I'm sorry but that is horrifying. Wrapping JS in in a big backtick string?! No. That's even worse that Dart-JS interop.
When I actually tried to write a project in coffee script, the results were the opposite of what I expected.
The code was harder to read, harder to modify, harder to understand, harder to reason about.
There's something about removing stuff from syntax that makes programming harder. My hypothesis is this: your brain has to spend extra effort to "decompress" the terse syntax in order to understand it, and this makes reading code unnecessarily difficult.
So I fundamentally disagree with the underlying premise of these projects, which seems to be based on PG's concept of "terse is power".
My experience suggests the opposite: there's power in being explicit. Type declaration is an example of such a feature: it makes explicit something about the code that was implicit.
Type declarations add more to the parse tree, and require you to type more, but they actually give you more power.
The same can be said about being explicit in the language constructs.
There of course has to be a balance. If everything is way too explicit (more so than needed) then your brain will do the opposite of what it needs to do with terse code: it has to spend more effort to remove the extra fluff to get to the essence of what the code is doing.
Being terse is good, up to a point. Same with being explicit.
Languages that try to bias too strongly towards one extreme or the other tend to miss the mark. Instead of aiming for balance, they start to aim for fulfilling some higher telos.
The first part was great and deserves credit for pushing the language to evolve. The second part made it a terrible dev experience. My experience was identical to the gp: writing it was fast and intuitive, but reading it was much, much worse. Not just reading other people's code, but even my own: my ability to understand my own code degraded not in weeks or months, but mere days. It was so bad, the overall result was a net negative and I quickly stopped using it. I say this as someone who has enjoyed writing code in over a dozen languages, from assembly all the way to Haskell.
Otherwise it's like improving the efficiency of a closet by ripping out the shelves, drawers, dividers, coat hangers, etc... so that it's just one big volume-maximized empty room.
I'm thinking of using Civet for an upcoming project, I specially want `do` expressions. Even with how much noise an IIFE (`(() => {doStuff(); return ...;})()`) introduces, it's such a natural idea to me that I end up using them very often.
But what makes me question the idea is having to learn a new syntax that is close enough to the JS I already know that I'm going to get confused constantly.
Why would I care about writing `export a, b from "./cool.js"` instead of `export { a, b } from "./cool.js"`? I don't mind those curly braces, I may actually like them a bit; I do very much mind the overhead of remembering these details when I change languages, and there's no way I can remove JS/TS from my life.
Finally, there's expressions like `value min ceiling max floor`. Is that readable at all? You have to actually read each word to know which are operators, which functions, which vars... It seems to me much worse than `max(min(value, ceiling), floor)` or a Lispy alternative like `(max (min value ceiling) floor).
- Everything is an expression
- Pattern matching
- Spread in any position
- Dedented strings/templates
However, I wouldn't use it, because the chance of it becoming abandonware that I just have to migrate off of later is way too high. I'll write a few extra TypeScript characters here and there for the stability.
I am certain we're doing the same thing now with Typescript.
1) Having to learn an entirely new syntax just to save on a few characters (it "decompresses" directly to the original thing)
2) Explicitness vs implicitness/inference
I wholly agree with you about #1 (superficial brevity isn't a very important goal and doesn't justify a whole new language), but #2 is much more of an "it depends"
if err != nil {
return err
}
...is that it's not even Error Handling. It's "Error Shovelling" (manual work to move the error from one place to another).For error handling I tend to write in a style where errors are either asserted out or "folded". If I do several operations in sequence any of them could err, I code in a way where I don't check every single op: instead I make some kind of "error accumulator", or write the code in a style such that if the previous operation failed the next operation will become effectively noop. I then check for errors at the end of the process.
That said, Go is actually right about treating errors as values and not giving special language constructs to throw/catch them.
Redundancy also helps when transmission is imperfect. And you do have imperfect transmission when writing code (typos), and even when reading (skimming text, missing a character).
CoffeScript makes every character count, especially punctuation. It's really, really easy to make a small typo in these. But CoffeeScript eschews redundancy, so the typo becomes another valid grammatical construction, with an entirely different meaning. At best, you get a cryptic translation error elsewhere. At worst, it gets accepted but works differently than you had intended.
APL has this property, too. But an APL program is very terse, you pay attention to every character in a short string of them. It does not feel like Javascript which is traditionally lax in the punctuation and whitespace department, catching you off guard.
CoffeeScript was an interesting experiment, but I'd say its result is negative.
Sadly, with Steve Klabnik's withdrawal from the core Team, the current maintainers are on a path to repeat these mistakes.
But I wonder, if the compiler can get by without it, perhaps we can too? With a different mental model/abstraction, that simply does not need that information - not even by implication. If there is one, probably not easy to come up with.
Like kinematics omitting force (e.g. high school physics, x = x_0 + vt + 1/2at^2). https://wikipedia.org/wiki/Kinematics
I also know as a fact that programmers 100x my caliber have nevertheless written great large-scale software without types.
So I don't make generalizations on the human condition and just do what works for me!
[0] https://medium.com/hackernoon/the-first-typescript-demo-905e...
items = (() => {
const results = [];
for (const item of items) {
if (item.length) {
results.push(item.toUpperCase());
} else {
results.push("<empty>");
}
}
return results;
})();
Seems like they are purposely making the JS version extra long. It could be: items = items.map(x => x.length > 0 ? x.toUpperCase() : `<empty>`)
Edit: Just realised the code on the right is the compiled code, not the "equivalent hand written JS". items = items.map(x => x.toUpperCase() || `<empty>`)This even more terser code will fail if `x.toUpperCase()` fails with an exception (such as when x is not a string).
I thought it was the comparable hand written JS.
I for one appreciate simplicity and would prefer Clojure anytime over Scala. The further comes with barely any syntax, has a couple of quick-to-grep concepts and once you trained your brain to read it and your editor to juggle the parens it is a lot of fun. The latter looks very nice and casual in the beginning but to me feels like a rabbit hole of complex concepts that were always heavier than the domain I was using it for. YMMV.
Tho civet code examples look nice I'm afraid it adds much more complexity than needed, both the concepts you have to keep in your brain's working memory and the whole TS toolchain which is already kind of horrible these days.
var that = this;
function() {
...
}
Back when there was no async/await and no promises, passing callbacks like this was extremely tedious, and node.js had a lot. CoffeeScript was worth using for the fat arrows alone.CoffeeScript didn't die. JavaScript (ES3/5) died and we are all using CoffeeScript now!
That being said, as much as I liked using CoffeeScript in my own personal projects, adopting it for a non-trivial project at work probably turned out to be a net mistake all things considered. The only positive was that the .js output files where clean any easy enough to work with that we could quite easily just drop CoffeeScript and continue developing directly with those .js file
If it ever becomes a liability, you just check in the "transformed" code and it's gone.
I feel like this is exactly the right kind of slogan for a project like this. Smug and opinionated, disregarding anyone who might not feel the same.
A Concise and Powerful Dialect of TypeScript. (my favourite but some might argue that Civet is more than a dialect. I think it's a dialect.)
TypeScript, Streamlined
Write Less, Do More
Expressive Syntax and Faster Coding. (there's already two slogans, and this one is great, plus you don't have to repeat the name of the project in the title section)
Expressive Syntax, Fast Coding. (feels a bit crisper to my ears)
I think there's many ways you can say, "this is an awesome, fast, concise way to write web code that compiles to TS/JS" without suggesting it's _the_ way to write _modern_ TypeScript.
I’m quite surprised it’s not called ToffeeScript though.
Wasn't super popular, but wasn't unknown either.
'Civet' certainly implies your code is being processed, but I'm not sure the connotation is desirable.
And all IMHO of course, but significant-whitespace is the worst idea ever.
You'd think not, and yet ...
Hear hear! I never heard a single argument for significant invisible characters that makes sense, ever.
Who would want to have a program that fails because you used invisible character X instead of invisible character Y?
This might be a good stepping stone though to sway skeptics.
What problem ist actually solved by being able to compile pseudo-languages like Coffeescript or Typescript into each other?
Now I use TypeScript to get the benefit of type-checking. It really helps catch silly little mistakes.
I would use Civet just to be able to use the new pipe operator syntax.
There are even compilers that compile future versions of JS into the current version of JS so future language features can be used in current browsers.
I've heard JS is the new assembly language.
Or just curse me for keeping the dream of CoffeeScript alive :P
Whenever I saw coffeescript I just felt like the developers fancied a change for change sake and doesn't have real problems to solve.
It's clever though to create a new syntax, I'll give you that
- Rest in any position
- Dedented block strings
- Default to const in for loops
- Lack of -> function shortand
- Everything is an expression
- Implicit returns
- Chained comparisons
- Nested unbraced object literals
- Optional trailing commas in arrays
- Optional trailing commas in objects
- x.map .name function shorthand
Each one is a fairly minor concern but they all add up. I'm sure different people will have a different list of favorite features as well. One of my goals in creating Civet was to fix my top 100 issues with TS syntax while being 99% backward compatible. The ultimate goal being: TS with my top 100 issues fixed will be the best language I have ever used.
Can this all not be done as experimental typescript though, rather than a new language?
The difficult part for TS/JS is they can't easily opt into some of the more whitespace/context sensitive features without breaking changes to existing code or forcing people to opt in with a "jsNext" directive or something.
[1] https://www.youtube.com/playlist?list=PLUl4u3cNGP61Oq3tWYp6V...
I have had a non-trivial experience with CoffeeScript. Maybe one can find the syntax easier on the eye, but it is always at the cost of added ambiguity, both for the parser and the reader.
After one too many parsing error debug session in CS, I just moved back to plain JS (later, TS), only to be happy with it ever after. to the point that after this comment, I won’t even consider trying Civet further than their homepage.
I think Civet would success better if it addressed those issues by making the syntax a bit more familiar and consistent.
Keep the python/ruby-like significant indentation and everything-is-expression approach, but add little more verbosity for clarity, for example:
- require and reserve {} for objects
- require () in function calls
- require explicit var/let/const
Also, because CoffeeScript got so controversial reputation, I would not ride on its legacy. Just market Civet as "TypeScript with modern syntax".I spent years writing enterprise scale CoffeeScript with a sizable team and it was pretty rough. We converted to ES5 and now TypeScript (well mostly) and that solved a lot of our problems.
I like to think I helped lead people away from CoffeeScript.
More info: https://civet.dev/cheatsheet#variable-declaration
The CTO in me is horrified at the idea of writing a whole bunch of civet code for it to die a coffeescript style slow death. If it catches on I'll be all over it though.
data
|> Object.keys
|> console.log
when you could have done |> data
Object.keys
console.log
Or even better, don't introduce new syntax and just make it a simple function instead |>(data,
Object.keys,
console.log))
Yes yes, I know "|>" is not a legal variable/function name right now, but also, why not?!Immediately getting ptsd of abandoned coffescript codebases
I've done this and regretted it - CS transpires to ES3 and specifically null chaining is completely unreadable.
From a backend perspective it's no biggie to instead keep the CS dependency and gradually convert files manually when you have to make changes anyway, or when you have 15 minutes to spare between meetings.
You become fluent enough to not even need tests after a while (yikes!).
That said, I always found CoffeeScript to be a worse language (syntactically) than JS.
(-> "Hello, World"
string/uppercase
(string/split #",")
first
string/trim)
`->` is a threading-first macro, and `->>` is a threading last macro. More here: https://clojure.org/guides/threading_macrosIf all languages tried to be the same, most of them would be boring.
$ = (f, x) => f(x)
flip = f => (x, y) => f(y, x)
|> = (x, ...fs) => fs.reduce(flip($), x)
However, |> in typescript is quite hard to type (no recursive types). The best approximation you can get is to manually insert |> = <A, B1>(x:A, f1:(a:A)=>B1)
|> = <A, B1, B2>(x:A, f1:(a:A)=>B1, f2:(b1:B1)=>B2)
At that point, a simpler binary function will make more sense |> = flip($)
And tsc will interpret the types in the pipe more correctly. Of course, the compiler can allow functions to be called without parentheses to avoid a macro for pipe calls, which will bring our definitions to $ = (f, x) => f x
flip = f => (x, y) => f y x
|> = (x, f) => (flip $) x f
It might also help to add a simpler function composition function too, as it will greatly help reuse without requiring you to write lambdas $ = (f, x) => f x
flip = f => (x, y) => f y x
. = (f, g) => x => $ f (g x)
|> = . flip $
It could also help to remove those pesky parentheses from lambda definitions too, maybe with simpler declarations like `f x y =` converting to `f = (x, y) =>` and enabling automatic currying: $ f x = f x
flip f x y = f y x
. f g x = $ f (g x)
|> = . flip $
But, have you noticed that we mostly have binary functions? We could greatly improve readability by making our "modifier" functions (aka combinators or adverbs) into operators. So civet could implement a special syntax for operator definition, and then we would write for definitions like ($) f x = f x
flip f x y = f y x
(.) f g x = f $ (g x)
(|>) = flip . $
(Oh wait, does this look like something else?) So we would be able to express `console.log(Object.keys(data))` like data
|> Object.keys
|> console.log
or equivalently (console.log . Object.keys) $ data
without having to special case for pipes! But more importantly, if you enjoy the Clojure method of piping, you can define |>> x f ...fs = f ? |>> (f x) ...fs : x
which would give you the syntax you like. And I have a feeling the types will compile just right in a language that looks like this...I wouldn't put every method on a new line, though.
Simple copy/paste is enough to break significant-whitespace, let alone space-vs-tabs, etc, etc.
But again, all IMHO, I realise it has it's fans.
items = items.map(x => x?.toUpperCase() || `<empty>`)
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...Do you recall specific RFCs that are leaning in this direction?
A good example would be the behavior of the tilde operator. Another would be the current drafting process of keyword generics.
We've added https://github.com/tc39/proposal-pipeline-operator, a variant of https://github.com/tc39/proposal-pattern-matching, a variant of https://github.com/tc39/proposal-string-dedent and others.
Since our goal is to be 99% compatible with ES we'll need to accommodate any proposals that become standard and pick up anything TC39 leaves on the table (rest parameters in any position, etc.)
Errors are still values in Rust - usually as part of the `Result` type - but unlike Go, it actually has tools to let you deal with them in a convenient way, like the `?` propagation operator (https://doc.rust-lang.org/book/ch09-02-recoverable-errors-wi...), or the functions on the `Result` type like `map`, `and_then`, `map_err`, or crates like `thiserror` for defining error types, and `anyhow` for easily converting them when you don't care about the details.
The try operator and Result type is amazing though.
I don't see a problem with relying on a few super popular basic libraries for almost every project.
I guess you can panic/recover in go but it's very very unwieldy and not quite the same.
Personally I couldn't let go of Clojure's other advantages but at least using the syntax would let you step off the syntax churn bus
But “losing tech” is unproductive when you have to maintain/upgrade your code over many years and onboard new developers into the team.
It is unfortunate that the tech industry’s choice of tools is largely fashion-driven but that is the reality.