Skip to content

Instantly share code, notes, and snippets.

@HarrisonGrodin
Last active October 21, 2021 22:38
Show Gist options
  • Save HarrisonGrodin/c70a2efec9ac047c78b7b5fb88bd6e37 to your computer and use it in GitHub Desktop.
Save HarrisonGrodin/c70a2efec9ac047c78b7b5fb88bd6e37 to your computer and use it in GitHub Desktop.
Standard ML style guide

Standard ML Style Guide

Core Language

atexp

Special Constant: <scon>

<scon>

Value Identifier: ⟨op⟩<longvid>

<longvid>
op<longvid>

❓ Is this the best style for infix operators? Other ideas would be:

  • op <longvid>, but looks odd in curried scenarios (e.g., List.foldr op + 0 vs List.foldr x + 0)
  • (op <longvid>), except (op * )

Record: { <exprows...> }

{}
{ <lab> = <exp> }
{ <lab1> = <exp1>, …, <labn> = <expn> }

❓ Should the spacing be required next to the braces? Alternative would be {<lab1> = <exp1>, …, <labn> = <expn>}.

{ <lab1> = <exp1>
, …
, <labn> = <expn>
}
⚠️ Style Errors
  • Using {} over () at type unit (rather than type {})?

Record Selector: #lab

#lab

0-tuple: ()

()
⚠️ Style Errors
  • Using () over {} at type {} (rather than type unit)?

n-tuple: (<exp1>, …, <expn>)

(<exp1>, …, <expn>)
( <exp1>
, …
, <expn>
)

❓ Spaces probably shouldn't be used next the parentheses. Does that influence the decision for records?

List: [<exp1>, …, <expn>]

[]
[<exp1>]
[<exp1>, …, <expn>]
[ <exp1>
, …
, <expn>
]

Sequence: (<exp1>; …; <expn>)

(<exp1>; …; <expn>)
( <exp1>
; …
; <expn>
)

❓ The multi-line syntax is uniform with other similar syntaxes, but should it be preferred here? If not, what would be better?

Let Expression: let <dec> in <exp1>; …; <expn> end

let
  <dec>
in
  <exp1>;
  …;
  <expn>
end

❓ Should there be an allowable short-form if <dec> has one declaration? e.g.,

let val x = 21
in 2 * x
end
(* my favorite, I think? *)
let val x = 21
in 2 * x end
let val x = 21 in
2 * x
end
let val x = 21 in
2 * x end

❓ Can we line up the ; placement with the raw sequencing operation?

❓ Does it make sense to support a single-line sequence, e.g.

let
  <dec>
in
  <exp1>; …; <expn>
end

where the <expi>s aren't "too long"?

⚠️ Style Errors
  • Empty <dec>

Parenthesized Expression: (<exp>)

(<exp>)
⚠️ Style Errors

Completely redundant parentheses. These are certainly redundant:

(42)
((42))
(x)
(let val x = 21 in 2 * x end)

These probably shouldn't be considered redundant, although not sure (re conversation with @shwestrick):

((* important constant *) 42)
((* should use x, not y *) x)

exp

TODO

dec

TODO

atpat

TODO

pat

TODO

ty

TODO

Module Language

strexp

Struct: struct <strdec> end

struct end
struct <dec (short)> end
struct
  <strdec>
end

Structure Name: longstrid

<longstrid>

Structure Ascription (Transparent): <strexp> : <sigexp>

<strexp> : <sigexp (short)>
<strexp> :
<sigexp>

Structure Ascription (Opaque): <strexp> :> <sigexp>

<strexp> :> <sigexp (short)>
<strexp> :>
<sigexp>
Examples
struct
  type t = int
  val toString = Int.toString
end :> SHOW where type t = int
struct
  type t = int
  val toString = Int.toString
end :>
sig
  type t = int
  val toString : t -> string
end

Functor Application: <funid> (<strexp>)

<funid> (<strexp (short)>)
<funid> (
  <strexp>
)

Structure Let: let <strdec> in <strexp> end

let
  <strdec>
in
  <strexp>
end
Examples
let
  structure S1 = F (X1)
  structure S2 =
    G (
      structure A = X2
      structure B = X3
    )
in
  struct
    open S1 S2
  end
end
let
  structure S1 = F (X1)
in
  H (val foo = S1.x)
end
let
  structure S1 = F (X1)
in
  H (
    structure S = S1
    val foo = S1.x
  )
end
⚠️ Style Errors
  • Empty <strdec>

strdec

Declaration: dec

<dec>

Structure Declaration: structure <strbind>

structure <strid> = <strexp (short)>
structure <strid> =
  <strexp>

The same rules apply for subsequent and clauses, where and is indented to match the end of structure.

Examples
structure S = struct end
structure S2 = S1
structure S =
  struct
    type t = int
    val x : t = 42
  end
structure S1 = X1
      and S2 = X2

❓ So, things like this should be considered good style? Debatable, but perhaps it's good formatting-level style and just bad programmer style. (Could consider a nonzero number of and clauses with any clause having a non-short exp to be ⚠️ bad style?)

structure S1 =
  struct
    type t = int
    val x : t = 42
  end
      and S2 = X2
      and S3 = Foo (X3)
      and S4 =
  Foo (
    type t = int
    val x : t = 42
  )

Local Definition: local <strdec1> in <strdec2> end

local
  <strdec1>
in
  <strdec2>
end
⚠️ Style Errors
  • Empty <strdec1>

Sequential Definition: <strdec1> <strdec2>

<strdec1>

<strdec2>
⚠️ Style Errors
  • No space between <strdec1> and <strdec2>
  • Semicolon after <strdec1>

sigexp

TODO

sigdec

TODO

@shwestrick
Copy link

structure S1 = X1
      and S2 = X2

IMO it should be left-aligned:

structure S1 = X1
and S2 = X2

This correctly handles the case where X1 is actually a large definition, and so the indentation pops back out (rather than popping in) when moving onto S2.

Although as far as style-checking goes, honestly I don't think and should ever be used for structure declarations. They should never have included it in the grammar lol. It's equivalent to just writing structure, right?

@HarrisonGrodin
Copy link
Author

Hmm, yeah that's fair. And yep, equivalent to writing structure, except if you write and they can't be mutually recursive (theoretically, X1 and X2 could be evaluated in parallel). It's definitely not extremely useful, but if we add whitespace between declarations, it's a nice way to group together similar definitions.

@shwestrick
Copy link

structure S =
 struct
   type t = int
   val x : t = 42
 end

Might want to also consider no indentation:

structure S =
struct
  type t = int
  val x : t = 42
end

Or even further:

structure S = struct
type t = int
val x : t = 42
end

The latter two are preferable for the common case where a single file defines a structure (/functor), because otherwise every
line of the file has unnecessary indentation. Unnecessary in the sense that it only takes up space, and doesn't help the reader in any way.

@shwestrick
Copy link

Perhaps it would be worthwhile to distinguish "big" and "small" structures for this purpose. The distinction is a bit fuzzy though.

@HarrisonGrodin
Copy link
Author

Hmm... I see your point regarding unnecessary indentation.

Instinctively, I'm not a fan of the "even further" idea, since I think putting the structure declaration at the "same level" as the other declarations can get confusing quickly. (And, it feels like a special case, since presumably putting another structure inside of S should incur more indentation.)

Not sure how I feel about the standard "no indentation" example. Definitely seems more reasonable, but how would this interact with other cases, like functor application? e.g.,

structure S =
Foo (
  type t = int
  val x : t = 42
)

feels odd, but maybe that's just me.

Personally, I don't mind the extra indentation for the sake of uniformity (especially in the case of other top-level decls), but perhaps I'm an edge case?

@shwestrick
Copy link

I also agree I'm not a big fan of completely unindenting the contents. I've seen that syntax in various codebases (MLton is a good example), but I think only for outermost structures which take up a whole file.

Personally, I don't mind treating struct-end differently than functor applications. But perhaps it's actually me that's the edge case ;)

@shwestrick
Copy link

By the way, I think we'll need to assign someone as the executive decision maker in order to settle on something. Would you like to do that?

@HarrisonGrodin
Copy link
Author

I would be delighted to!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment