3 ms·
Me three! I came from Python (now with MyPy), learned OCaml and liked some aspects, was intrigued by Reason -- and also sad it seems to be in limbo. I also m
by chubot 3y ago
Me three! I came from Python (now with MyPy), learned OCaml and liked some aspects, was intrigued by Reason -- and also sad it seems to be in limbo.
I also miss early returns, and break/continue.
I would like "modern ML" / "Python with sum types" / "Rust with GC" language (indentation/braces doesn't matter to me). Many people seem to agree.
Recently I found TypeScript is kinda fun for this, at least if you're starting from no code, without ecosystem baggage:
https://news.ycombinator.com/item?id=37171801 https://news.ycombinator.com/item?id=37171801
AFAIK TypeScript's type system can do everything in OCaml -- it's extremely expressive -- but it's dis-similar in that it doesn't use the types to compile to native code. I view that as a downside because JITs are unpredictable and also huge.
It has early return/break. The syntax is pretty conventional, with the usual JS weirdness that everyone has to know.
- zogrodea 3y agoI wanted to be a fan of TypeScript and get to use it daily on my job, but actual experience made me dislike the language. I think you already know the pain of external libraries (Express in my case) since you mentioned the ecosystem baggage, and the lack of pattern matching is another big minus for me.
- brightsize 3y agoHave you had a look at Coconut? I don't know if it'll push all your buttons but whenever I hear someone who's reasonably content with Python but wants more FP goodies I always think of it. https://github.com/evhub/coconut https://github.com/evhub/coconut . It's basically a superset of Python3 that transpiles into Python3 and is compatible with MyPy. I don't think I'd code Python w/o it ever again assuming I had the choice. The biggest negative for me is that there's no IDE support for the language last I looked, though of course you can work with the transpiler output (plain Python) in your favorite Python IDE. It might be fun to play around with, I know that I really enjoyed it but then I got spoiled by the language+tooling of Scala3, but if you don't have that option ...
- chubot 3y agoI actually want the imperative Python style, but with sum types. So it's more like "Rust with GC" I suppose. I used to write in a functional style, and then I wrote Python for decades, and my brain flipped. Now I like imperative code :) I guess it's all the usual things about liking break / continue / early return, local mutation, flat code rather than nested code, etc.
- consilient 3y ago> AFAIK TypeScript's type system can do everything in OCaml -- it's extremely expressive Extremely expressive and unsound. And not just in a trivial "escape hatches exist but you should never use them" way - until you've been burned enough it's not at all obvious which operations are unsafe, and there are a lot of them.
- chubot 3y agoI think if you're writing code from scratch, this doesn't really apply -- I'm talking about prototyping language implementations without any libraries at all, sorta like you would do with OCaml from a textbook (e.g. TAPL by Pierce) (I'm aware of all the terrible experiences people have with TypeScript in the NPM ecosystem. But TypeScript is a big, mature tool and you can use it in more than 1 way.) I just noticed the 'deno check' command I'm using turns strict mode on by default, so that's good. https://deno.land/manual@v1.4.1/getting_started/typescript https://deno.land/manual@v1.4.1/getting_started/typescript All widely used gradual type systems are unsound because they have to interoperate with untyped code, and the dynamic checks to make it sound are too expensive. But code written from scratch doesn't have that issue. I'd be interested in a counterexample -- is there a code snippet that passes the strict mode of the compiler, and doesn't interoperate with untyped code, but produces an unexpected runtime error? I guess by "unexpected" I mean that, at runtime, an operation is performed on a value which is not allowed, and the program fails --- I googled and found this -- https://effectivetypescript.com/2021/05/06/unsoundness/ https://effectivetypescript.com/2021/05/06/unsoundness/ -- not sure I agree with some points, e.g. array out of bounds isn't unsoundness! The OPERATION is legal, but the data isn't, which isn't something that any type system will tell you. Similar to divide by zero -- a runtime error does not imply unsoundness. Also, casts can produce unexpected runtime errors by definition -- that's why they are casts, and you have to opt in! Bad article. --- I think these are better examples: https://news.ycombinator.com/item?id=15659657 https://news.ycombinator.com/item?id=15659657 I believe Java has some of those too. Covariance / contravariance is a common source of unsoundness, but definitely not a dealbreaker for me
- consilient 3y ago> But code written from scratch doesn't have that issue. I'd be interested in a counterexample -- is there a code snippet that passes the strict mode of the compiler, and doesn't interoperate with untyped code, but produces an unexpected runtime error? You'd think so, right? But no, typescript is deliberately unsound in ways that have nothing to do with gradual typing. Here are a few examples. Signatures written in method syntax are bivariant, which is not correct interface Unsound { f(x: number | string): number } interface Unsound2 { f(x: number): number } const a: Unsound2 = { f: (x: number) => x } const b: Unsound = a const c: number = b.f("not a number") Type predicate results survive mutation const hasA = (x: object): x is { a: unknown } => "a" in x const deleteA = (x: { a: unknown }) => { delete x.a } const unsound = (x: object) => { if (hasA(x)) { deleteA(x) return x.a } else { return "no a" } } Many stdlib types are incorrect. JSON stuff is particularly bad: JSON.parse and Body.json() both return `any`. You can spread things that aren't objects const unsound = <X,Y>(x: X, y: Y): X & Y => ({...x, ...y}) const bad: never = unsound(5, 4) (And even for objects, `X & Y` is not the correct type when you have overlapping keys) Anything with optional fields can be widened incorrectly const unsound = <T extends { x: number }>(t: T): { x: number, y?: number } => t const bad: number | undefined = unsound({ x: 5, y: "not a number" }).y Assignment doesn't handle `readonly` properly interface Readonly { readonly x: number } interface Mutable { x: number } const a: Readonly = Object.freeze({x: 5 }) const b: Mutable = a b.x = 4 > The OPERATION is legal, but the data isn't, which isn't something that any type system will tell you. There are some that will, though unfortunately none that are really production-ready yet.