Closed Kesanov closed 6 years ago
Interesting. I like the idea of not needing <
, and giving up of >
is a reasonable tradeoff for that. Looks like something that could work, but I'm not sure I fully get it. I need more info to really understand what you did there. Could you be precise about the translation you have in mind?
For example, the current notation does this: for every <foo
, move foo
to the nearest binder or pipe, wrap everything else in a lambda and replace the old location by the bound variable of that lambda. For example, | (if 1 [<foo <bar 3] 4)
becomes (foo X => (bar Y => (if 1 [X Y 3] 4)))
. I kinda like this syntax because it:
let
, at least as expressive as the do
notation. Ex:foo = do
print "Write a line."
line0 <- getLine
print "Write another line."
line1 <- getLine
if length line0 < length line1
then print "The first line was shorter."
else print "The second line was shorter."
return "Done."
becomes
foo = |
(print "Write a line.")>
line0 =< getLine
(print "Write another line.")>
line1 =< getLine
(if (ltn (len line0) (len line1))
(print "The first line was shorter.")
(print "The second line was shorter."))>
(return "Done.")
Which is clearly not as aesthetically appealing as Haskell, but provides the same usability.
foo = func <$> x <*> y <*> z
becomes
foo = | (func <x <y <z)
Could you do the same I did here, but for your transformation? I.e., specify it, give me a simple example, and show how the snippets above would look on it?
Thanks!
Maybe it will be more understandable if I replace |
with <=
.
Basically it is just a trivial syntactic sugar where
let
<= a <= let -- _ is optional
b <= foo _ <= bar -- unless you have multiple assign statements on one line
in baz b
in a
is rewritten into
(foo b => ( bar _ => (baz b _ => a => a)))
1. It is at least as expressive as do notation.
foo = let
<= print "Write a line."
line0 <= getLine
<= print "Write another line."
line1 <= getLine
<= if (ltn (len line0) (len line1))
(print "The first line was shorter.")
(print "The second line was shorter.")
in wrap "Done."
2. Applicative style is not possible.
But do we really need it that much? Yes, sometimes it comes handy, but how often do you actually need it?
3. Also allows nice syntax for unboxing.
let
a <= b <= pair "a" "b"
c = "c"
d <= e <= pair "d" "e"
in con a (con b (con c (con d e)))
This is really cool though, and easier to grasp, I guess. Not having the applicative style sucks, though, but giving us the pattern matching style for free is very nice. One problem I have is that it would kinda require us to give up on the existing syntax (because it would interfere with stringification, as the stringifier would need to chose one of both - also, "there should be one and only one obvious way to do it"). Hmm...
Edit: this would also make the parser simpler without the lift stuff. This is very Moonish on its spirit, I definitely like it, think it is better than what we have now. This should replace the bang notation. I don't have time to work on it immediately now, but would appreciate a PR. Are the keywords let
/ in
actually necessary?
I am wondering whether it would not be better to use an infix pipe operator instead.
foo =
print "Write a line." | _ =>
getLine | line0 =>
print "Write another line." | _ =>
getLine | line1 =>
if (ltn (len line0) (len line1))
(print "The first line was shorter.")
(print "The second line was shorter.") | _ =>
wrap "Done."
It looks pretty similar and as a bonus point, there is no ambiguity in deserialization (stringification).
Also IF ELSE
syntax looks kinda neat.
( if bar
...
| if baz
...
| if foo
...
| ... )
But that syntax already works, no? Except we need a lot of parenthesis, of course. But I'm thinking in writing 2.0 version of the parser/stringifier which will kill all those (and more) parenthesis and have the operators... but it doesn't need the |
s for that, right?
Indeed, the |
is not really needed.
Perhaps the stringifier should just not add indentation on the
(func var =>
body)
pattern, but add on the
(func
var => case0
var => case1)
pattern?
That does not cover if
though, which should be also indented as:
(if condA
doA
(if condB
doB
(if condC
doC
doD )))
But I'm thinking in writing 2.0 version of the parser/stringifier which will kill all those (and more) parenthesis and have the operators.
Cool. But seriously, currently the error messages from parse errors are horrible. I would focus on that first.
I think great error messages would be part of a "make a better parser/syntax" project.
On if
, probably a syntax sugar would help a lot:
when:
condA: x
condB: y
condC: z
default: w
Becomes:
(if condA x
(if condB y
(if condC z
w)))
And
switch value:
"foo": a
"bar": b
"tic": c
"tac": d
default: e
Becomes
(if (cmp value "foo") a
(if (cmp value "bar") b
(if (cmp value "tic") c
(if (cmp value "tac") d
e))))
So for the first case the syntactic sugar does not make sense since
(if condA
...
(if condB
...
(if condC
...
...)))
is already very readable.
switch
on the other hand is a different entity than if
since it can choose branch in O(log(N))
or even O(1)
so I would definitely not implement it with if
under the hood.
And finally, it is quite easy to detect, when to indent and when not. The rule of thumb is, if you see a repeating pattern like
(val 1
(val 2
(val 3
end)))
then do not indent it. (Such a repeating pattern is usually possible to be replaced with fold
. )
It looks like
| a =<(prompt "How are you?")
could be simply replaced with| a = prompt "How are you?"
where the second version is using simple pattern matching inlet
.The big advantage is, we can pattern match on multiple arguments, and we don't need
<
and>
anymore (and it is IMHO easier to understand):The disadvantage is you have to write
| _ = print "Hello"
instead of(print "Hello")>
and you cannot use... <foo ...
anymore, which might be a good thing, since it is really hard to understand code that contains multiple binds in one line.