A syntax-cleaning release?

What do people think about a breaking release which “cleans up” some syntax features of the language?
Following is one example I wish could change, but I’m sure there are others, e.g. the type class/instance syntax is a little unclear in that constraints look like they affect instance selection, but they actually don’t.

I’d like if the language removed support for “re-defining a function” (as a newbie would read it) for each constructor of its input args:

-- Instead of:
f:: Animal -> Food -> Boolean
f Cat Catfood = true
f Dog Dogfood = true
f _ _ = false
-- Require:
f:: Animal -> Food -> Boolean
f = case _, _ of
  Cat Catfood = true
  Dog Dogfood = true
  _ _ = false
-- Or make a new syntax:
f:: Animal -> Food -> Boolean
f Cat Catfood = true
  Dog Dogfood = true
  _ _ = false
1 Like

I’m interested to hear how concerned PureScripter users and maintainers are about keeping close to Haskell syntax.

1 Like

I don’t think I agree that the syntax suggests that constraints influence instance selection, although I suppose it’s hard to judge without a proposal to compare it with. Do you have one?

Being syntactically close to Haskell has never been an explicit or direct goal, in my mind; it’s rare to find a source file which will parse as both PureScript and Haskell, for instance. I think it did (and continues to) make sense to base the syntax on Haskell’s, though, since lots of us are familiar with it and since it was designed with purely functional programming in mind.

I would say, though, that I’m not currently convinced that either of the things you mention are important enough to justify breaking changes now. At this point, I think we should be looking to reduce the frequency of breaking changes to the language, unless there’s a really strong argument to be made in favour of them.

1 Like

From a PureScript user perspective, one thing I find to be inconsistent is how class constraints are written differently in functions and class instance definitions. For example, for functions it’s:

someFunction :: forall a b. Monad a => SomeClass b => b -> a Something

Whereas for instance definitions it’s:

instance instanceName :: (Monad a, SomeClass b) => Class Something a b where

I don’t know if there’s a particular reason for this syntax difference.

Another thing I actively avoid using are where bindings in functions for two reasons:

  1. let exists.
  2. I like having code read top-down and left to right. where bindings invert this and I find them to be one of the causes of the mythical Haskell unreadability (along with abuse of <<< and $).

As for the original post, I did find the “re-defining a function” syntax confusing at first, but after reading up on it, it makes sense to me. The new syntax however feels wrong. The Dog Dogfood = true doesn’t look related to f Cat Catfood = true at first glance. There’s also the question of how to handle guards if the first syntax is gone.

Having said all this, PureScript is still one of the prettier languages I’ve worked with.

1 Like

There’s also the question of how to handle guards if the first syntax is gone.

You can use guards in a case statement:

case blah of
  Just x | isNice x -> “nice!”
  Just x -> “meh, okay: “ <> x
  Nothing -> “bleh”

I think there is actually an argument for deprecating where, in that you can argue that where is less justifiable in a strict language because it’s tempting to shove a bunch of things in a where clause and they’ll be evaluated every time, even if they’re not used. I do like it a lot, though. When used well, I think it can make code a lot nicer.

1 Like

You can use guards in a case statement

Nice!

I do like it a lot, though. When used well, I think it can make code a lot nicer.

Any particular reasons for preferring where over let?

For me, it can reduce cognitive overhead:

stdev = sqrt <<< variance where
    variance xs = sum (squares (diffs xs)) / length xs
    squares = map (_^2)
    diffs xs = map (_ - avg xs)
    avg xs = sum xs / length xs

vs

stdev ds =
    let avg xs = sum xs / length xs
        diffs xs = map (_ - avg xs)
        squares = map (_^2)
        variance xs = sum (squares (diff xs)) / length xs
     in sqrt (variance ds)

In the where example I can immediately see that the standard deviation is the square root of the variance, and if I’m OK with both of those, I don’t really need to read ahead.

On the other hand, since the version with let builds “from the ground up”, I have to keep in mind the definitions of four different things before I see what the actual definition of stdev is.

So for me it’s similar to pointfree vs pointful: one lets you see from high level, the other focuses on how the data is passed around and manipulated.

5 Likes

So for me it’s similar to pointfree vs pointful: one lets you see from high level, the other focuses on how the data is passed around and manipulated.

An interesting analogy, I haven’t thought of it like this before. Funnily enough, I usually prefer pointful style.

Sounds like relatively cosmetic changes which break existing PS code would be considered, but only if there’s a compelling argument for it. I believe that the PS maintainers would prefer to keep the language stable for a 1-2 years to promote documentation efforts and improve other user experience aspects. I can appreciate and agree with that stance.

Despite that being the case, it’s quite nice to have a place (like this thread) to discuss and commiserate on “disagreeable” syntax and provide PS newcomers additional context around the syntax they’re learning.

To commiserate a bit more, imagine how nice it would be to exclusively support GADT-like syntax for data type definitions? Yeah, many data types would be more verbose when defined in GADT-style syntax, but it expresses the semantics so much nicer! I imagine it would be much easier for language newcomers to understand data type definitions! Near-zero cognitive dissonance when/if GADTs are added to PureScript, which would hasten adoption of the concept in libraries and applications!

3 Likes

One thing to consider about using GADT syntax when you don’t have GADTs, is the compiler will have to force you to write the exact same “return type” for each constructor, which could seem weird; like “why make me say this for every constructor when there’s only one possible thing that works”. It’s related to the point of it being more verbose, but I think it’s also potentially a stumbling block due to it seeming like a weird restriction.

5 Likes