BlockArgument and $

I must admit that I supported BlockArguments because it allows bulleted argument lists, which I have wanted for quite a while (section “Less parentheses 1: Bulleted argument lists”), and would call it a “clever use”, not quite an “ab-use”.

I’d say every layout-based language that is invented or not calcified since Markdown and Yaml became common place should have bulleted argument lists (and maybe also bulleted syntax for lists/arrays/etc) – and the idea keeps popping up in various places, e.g. by @david-christiansen in the lean chat…

9 Likes

I really like the name “bulleted arguments lists” and I’ve wanting for quite a while too.

Now the following from nomeata’s link is fascinating, but imo people should be cautious with syntactical changes:

Less parentheses 2: Whitespace precedence

The final proposal is the most daring. I am convinced that it improves readability and should be considered when creating a new language. As for Haskell, I am at the moment not proposing this as a language extension (but could be convinced to do so if there is enough positive feedback).

Consider this definition of append:

(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : (xs++ys)

Imagine you were explaining the last line to someone orally. How would you speak it? One common way to do so is to not read the parentheses out aloud, but rather speak parenthesised expression more quickly and add pauses otherwise.

We can do the same in syntax!

(++) :: [a] -> [a] -> [a]
[]   ++ ys = ys
x:xs ++ ys = x : xs++ys

The rule is simple: A sequence of tokens without any space is implicitly parenthesised.

The reaction I got in Oxford was horror and disgust. And that is understandable – we are very used to ignore spacing when parsing expressions (unless it is indentation, of course. Then we are no longer horrified, as our non-Haskell colleagues are when they see our code).

But I am convinced that once you let the rule sink in, you will have no problem parsing such code with ease, and soon even with greater ease than the parenthesised version. It is a very natural thing to look at the general structure, identify “compact chunks of characters”, mentally group them, and then go and separately parse the internals of the chunks and how the chunks relate to each other. More natural than first scanning everything for ( and ), matching them up, building a mental tree, and then digging deeper.

Incidentally, there was a non-programmer present during my presentation, and while she did not openly contradict the dismissive groan of the audience, I later learned that she found this variant quite obvious to understand and more easily to read than the parenthesised code.

Some FAQs about this:

  • What about an operator with space on one side but not on the other? I’d simply forbid that, and hence enforce readable code.
  • Do operator sections still require parenthesis? Yes, I’d say so.
  • Does this overrule operator precedence? Yes! a * b+c == a * (b+c).
  • What is a token? Good question, and I am not not decided. In particular: Is a parenthesised expression a single token? If so, then (Succ a)+b * c parses as ((Succ a)+b) * c, otherwise it should probably simply be illegal.
  • Can we extend this so that one space binds tighter than two spaces, and so on? Yes we can, but really, we should not.
  • This is incompatible with Agda’s syntax! Indeed it is, and I really like Agda’s mixfix syntax. Can’t have everything.
  • Has this been done before? I have not seen it in any language, but Lewis Wall has blogged this idea before.

Well, let me know what you think!

Haskellers just seem to be Lisp programmers who hate parens (and love type systems, before you kill me).

Maybe if do bulleting (this is a better term than BlockArgument abuse) ends up being mainstream?

Also, with Unicode syntax, can bullets be a synonym for do?

Hah :slight_smile: But then we pay the price of the complicated meta programming that is TH.

1 Like

I agree, I really wish that TH was just a text processor with access to the AST.

Fwiw, ($) being a function is not polymorphic enough for linear-base, hence they have:

($) :: forall {rep} a (b :: TYPE rep) p q. (a %p -> b) %q -> a %p -> b infixr 0 

It’s probably a side argument to why having a layout specific keyword/trick/rule is more “clean” in some sense.

3 Likes

Maybe ; could just do.

Hey, think of SPJ! He’s actually using braces and semi-colons in the GHC codebase!

1 Like

This explain

By the same rules, it is possible to write:
  do 
    x <- getInt
    y <- getInt
    return (x + y)
as an abbreviation for:
  do {
       x <- getInt;
       y <- getInt;
       return (x + y)
     }
C programmers might prefer the latter.

(in the introduction of monadic syntax in haskell 1.3.

What is interesting is that in GHC indeed uses ; but as “bullet”

lintCoreAlt case_bndr scrut_ty _ alt_ty (Alt (LitAlt lit) args rhs)
  | litIsLifted lit
  = failWithL integerScrutinisedMsg
  | otherwise
  = do { lintL (null args) (mkDefaultArgsMsg args)
       ; ensureEqTys lit_ty scrut_ty (mkBadPatMsg lit_ty scrut_ty)
       ; rhs_ue <- lintAltExpr rhs alt_ty
       ; return (deleteUE rhs_ue case_bndr) -- No need for linearity checks
       }
  where
    lit_ty = literalType lit
1 Like

I’m learning TH right now via the lib documentation (I need to clone Snoyman’s file-embed in the least derivative way possible, but of course I’ll credit him), but QuasiQuotes seem to be a ridiculously ergonomic way to get metaprogramming.

If you can understand the binding and scoping (unnecessary) rules maybe …

Reminder that, at least with the latest HLS, hovering over a do will show you the type of the entire do block. That, to me, is a non-insignificant advantage. I’m not getting that for bracketed expressions nor for expressions that come after a $.

7 Likes

That thread was inspired by the same thread from a couple of weeks ago that inspired this one - I wanted to see if I could crank it out as a macro in less than 10 minutes. The answer was “yes”!

4 Likes

Introducing the bulleted argument list solution nobody was asking for:

module Arg where

end :: a -> a
end = id

(>>) :: a -> (c -> d) -> (a -> c) -> d
x >> g = \f -> g (f x)

(>>=) :: a -> (a -> c -> d) -> (a -> c) -> d
x >>= g = \f -> g x (f x)  -- Not sure if this is the best option
{-# LANGUAGE QualifiedDo #-}
module Arg2 where

import Arg
import Data.Function ((&))

exampleZip :: [(Integer, Integer)]
exampleZip = zip & Arg.do
  map (*10) [1..5]
  map & Arg.do 
    (+1)
    [1..5] ++ [10..20]
    end
  end

strangeSharingExample = f & Arg.do 
  x <- 2^100
  2
  x
  end
    where 
    f x y z = x + y / z
11 Likes

Pretty cool! That wasn’t possible back when wrote the blog post. (Just saying to get over the 20 char limit)

Wow, great indeed, thanks for pointing to this. It will be a great weapon in the war against parentheses. :grinning_face_with_smiling_eyes: :v:

Here is my first:

 safeButNotBuiltin <- and2M
    do pure isSafe
    do not <$> do Lens.isBuiltinModuleWithSafePostulates . filePath =<< getCurrentPath

and2M :: Monad m => m Bool -> m Bool -> m Bool
and2M ma mb = ifM ma {-then-} mb {-else-} $ pure False
1 Like

I’m still trying to decide if you are being sarcastic :smiley:

1 Like

BlockArgument and $ and EvilHaskellTips

1 Like

Could you elaborate ?

There was a twitter account with that name that posted all kinds of… tricks, so to say.