6 ms·
Hmm. I understand your rejection for `{}`, but I really think the end result feels nothing like the Java experience. Can I have a shot at defending it? :) It wi
by LangMakers 5y ago
Hmm. I understand your rejection for `{}`, but I really think the end result feels nothing like the Java experience. Can I have a shot at defending it? :) It will be a long answer including some of my reasonings, and by all means feel free to disagree if you think I'm wrong about something.
Kind is extremely permissible and terse in general, and a lot of caution and love is put in making it very practical. While `{}` looks like noise in some places, it allows others to be less verbose, resulting in some snippets that look almost like Python, even though Kind isn't indentation aware (which, IMO, is very annoying). For example, commas and semicolons are always optional, so you can actually write lists as just `[1 2 3]`, maps as just `{"foo":1 "bar":2}` and function calls as just `f(1 2 3)`. That wouldn't work without `{}` on case.
There are many places where the Haskell-like syntax break down. We aim to fix every one of these. For example, the following Haskell function:
blocks :: Entity -> Bool -> Bool
blocks entity floats True = True
blocks entity floats False = case entity of
Creature name hp age items pos -> True
Tile pos effect isWater -> not isWater || floats
Wall pos -> True
Fire pos damage kind -> False
Is equivalent to this Kind function:
blocks(entity: Entity, floats: Bool, ghost: Bool): Bool
case ghost {
true: true
false: case entity {
creature: true
tile: not(tile.is_water) || floats
wall: true
fire: false
}
}
Both do the same thing. Which one is more readable? The key insight is that we avoid repetition of variable names (a consequence of Haskell's equational notation), and the need to re-name every field of every variant every time you pattern-match (by naming fields as `matched_name.field_name` by default). That sounds like a small thing, but pattern-matching is the bread and butter of functional programming. Having such a terse syntax for case-of makes the whole language feel so much more refreshing.
Just like that, there are dozens of small little things that we do to make the syntax just work in practice. For example, working with Maybe is often frustrating in functional languages. In Kind, we have a default operator:
let x = some(3) <> 5 // x is 3
let y = none <> 5 // x is 5
An "abort" operator, that flattens your code:
sum_first_3(list: List<Nat>): Nat
x = list[0] abort 0 // returns 0 if x is none
y = list[1] abort 0
z = list[2] abort 0
x + y + z
And, of course, Maybe monad:
sum_first_3(list: List<Nat>): Nat
Maybe {
get x = list[0]
get y = list[1]
bet z = list[2]
return x + y + z
} <> 0
And the same attention we gave to Maybe, we gave to so many other small things. For another example, we HATE indentation. Pattern-matching a structure with only one constructor causes a needless indentation. In Kind, you can use "open", that flattens it too:
open vec
vec.x + vec.y + vec.z
Another source of frustration in Haskell is setting/getting values from lists, maps, records. Things are so inconsistent. `Map.lookup`, `List.index`, `!!`. And lenses, while amazing, are currently really messy, hard to learn and read, full of dependencies and impact the performance. Kind has simple and obvious answers for all of these. For example:
name = my_list[7]
Works just as you expect, returning the 7th element of `my_list : List<A>` as a `Maybe<A>`.
name = my_map{"foo"}
Returns the "foo" entry of `my_map : Map<A>` as a `Maybe<A>`. And:
name = my_record@foo
Returns the `foo` field of `my_record` as `A`. Don't need a maybe? Just default it:
name = my_list[7] <> "anonymous"
Wanna set it?
name = my_list[7] <- "Eric"
That gives the user the convenience of working on JavaScript lists, in a pure functional language.
Something else I noticed in Haskell is that code tends to become really messy when the function body becomes big. That is, when you need to chain many intermediate values. There isn't an obvious answer as to where stop growing horizontally and start adding lets vertically, and when to use `let` or `where`. Sometimes the computation flow becomes upside down, sometimes not. Also, foldr/foldl tends to become really messy when the function body is large. We have one straigth-forward answers to all these situations in Kind. Our let and "pure for" syntax allows for some pretty Python-looking functions:
age_factorial(birth:_year Nat, names: List<String>): String
// Concatenates all names in a string
name = ""
name = for name in names:
name | " " | name
// Computes the age
age = current_year - birth_year
// Computes the factorial of the age
fact = 1
fact = for n from 0 to age:
fact * n
if age <? 18 then
"You're too young to use this program"
else
"Hello, " | name | ", the factorial of your age is " | fact
Obviously, pure fors just desugar to folds. Also, maps are well-thought. In JavaScript, `{foo: 3}` and `{"foo": 3}` both are the same map. In Kind, if you don't write `""`, then it is a variable, which makes a lot more sense. For example:
key = "foo"
map = {key: 3}
Just a small thing though. This answer is already big, and it doesn't even cover things like the built-in HTML syntax, the `sigma` sugars (`[x: Nat] x <? 50` is the type of Nats that are smaller than `50`), other sugars for theorem proving, like `rewrite`...
Basically, what I'm just saying that there are so many nice things behind the initial impression, perhaps you're judging prematurely because of your taste for `{}`? Give it a chance and I think you'll love it!
- dwohnitmok 5y agoI really like the language, but you're exaggerating the verbosity of the Haskell code by not comparing them in an apples-to-apples way. Here's the same Haskell code written in the same way you've written your Kind code. blocks entity floats ghost = case ghost of True -> True False -> case entity of Creature{..} -> True Tile{..} -> not isWater || True Wall{..} -> True Fire{..} -> False The `{..}` is from `RecordWildCards`, a common extension. I do like the omission of `let` and `where` in favor of just variable definitions. For monadic notation, instead of `get` and `return` I'd prefer something like https://github.com/monadless/monadless https://github.com/monadless/monadless The main problem with `do`-inspired monadic notation is that it forces monadic code to always be in A-normal form, which is pretty annoying. It'd be far nicer to allow for monadic code to look the same as normal code, just with say an extra brace (such as "IO { }") to indicate that you're inside a monad.
- LangMakers 5y agoFair point! That is a recent update of Haskell, though (one I wasn't aware of when designing the case syntax). It does agree with my point, though, right? > The main problem with `do`-inspired monadic notation is that it forces monadic code to always be in A-normal form, which is pretty annoying. It'd be far nicer to allow for monadic code to look the same as normal code, just with say an extra brace (such as "IO { }") to indicate that you're inside a monad. I agree with that. Not sure how that could work, though. Idris had an interesting syntax, but IIRC it wasn't general.
- dwohnitmok 5y agoWell `RecordWildcards` has been around for 14 years... but even without it instead of `{..}` you'd just have `_`s. The main thing that is different is that your Kind example had nested case statements while your Haskell example tried to match everything on one shot, which makes for a non-equivalent comparison, and you purposefully repeated field names on both sides of `->`, which is not necessary even in vanilla Haskell when you work with records. > Not sure how that could work, though. Idris had an interesting syntax, but IIRC it wasn't general. RE Idris I assume you're talking about idiom brackets for applicatives? The general syntax is given in something like https://github.com/monadless/monadless https://github.com/monadless/monadless. The idea is to basically take async-await syntax and generalize it to any monad. So e.g. your `Maybe` example (using `!` for the equivalent of `await` for concision) would look like Maybe { x = !list[0] y = !list[1] z = !list[2] x + y + z } but because it doesn't have to be in A-normal form anymore you can compress it all into a single line. Maybe { !list[0] + !list[1] + !list[2] } So you can take any normal code you have and put in a monadic context simply by inserting brackets around the whole thing and then sprinkling in `!`s as necessary. EDIT: Or to think of it another way, just move the `get` from the left-hand side of `=` to the right-hand side. I'd recommend renaming it to something to indicate that this is a syntactic transformation, rather than a normal function (which is why I like something like `!` or anything else that has a symbol in it to indicate this is essentially a macro).