
I’d honestly suggest only allowing one module per require expression. Makes diffs easier to read and makes it easier to keep the imports organized.

Hm… I appreciate how terse Racket require
blocks are, but they can become quite unruly, hence the simplifications I’ve already made. Having one module per import would be consistent with other languages.

Given that I want to emphasize explicitly enumerating identifiers to import, allowing multiple modules per require would likely lead to long, potentially messy blocks.

Ok, I’ve changed the require
syntax to have exactly only module.



And that’s how I’ve done provides.

Contract expressions can get pretty long so I always linebreak provides, to give myself more horizontal room

(provide
(contract-out
[foo ...]
[bar ...]
[baz ...]
[zoom ...]))

I definitely like that your thing lets you mix non-contracted exports and contracted exports freely without having to arbitrarily put them in separate groups

One feature that I added is communication between define
and provide
so that, if you want, you can instead put the contract at the define
, and that will be used for contract-out
, if you list the identifier to provide
.

It’s a feature that I’ve wanted for some time, and I think it’s better than keeping a duplicate contract as a comment above the definition.

I think that’s a totally reasonable feature. I wonder whether it’s worth supporting both methods though. How should users decide when to use which approach? Is there reasonable guidance you can give, or are there idioms you’re expecting to see?

For things you define in the module, I’d encourage putting the contract at the definition site. But, if you want to provide something that you required, but with a new contract, then the contract should go at the provide site.

Hmm. What if you had some way of re-exporting imported identifiers that looked more consistent with a definition?

I already have a reprovide
form.

I should probably just add contracts to that, then remove contracts from provide
.

That makes sense

I made reprovide
so that it’s separate from the scope of the rest of the module.

Unfortunately, the definition-site contracts currently only work with procedure definitions. I’ve not yet worked out what syntax to use in other cases, since my define
uses patterns.

ah yes, that problem

I’ve been bitten by that too

There are basically three options: 1. Embed contracts in the patterns. 2. Have duplicate patterns that contain the contracts. 3. Separately contract individual variables. From the start, 1 is a no-go because then patterns could be massive and hard to read. Haskell and many other statically-typed functional languages do 2, but it’s an all-or-nothing approach, which doesn’t work well in this context. I think that 3 is the best option because it allows the programmer to selectively contract values.

hmm, could you give small code examples of 2 and 3? I’m having a little trouble picturing them in my head.

Approach 2.

Approach 3.

hmmm

what about something like a : number?
b : string?
c : boolean?
{define {list a b c} = {list 1 "hello" #false}}

I like separating the contracts from the definition, it’s much tidier, but I’d prefer something explicitly named and bracketed.
perhaps {contracts
a : number?
b : string?
c : boolean?}
{define {list a b c} = {list 1 "hello" #false}}

I suppose the alternative would be to adjust module-level syntax such that core definition and contracting forms can be left unbracketed and unnamed, which would allow a : number?
b : string?
c : boolean?
{list a b c} = {list 1 "hello" #false}

Agreed, things halfway between those two styles fall into an awkward uncanny valley

I like how concise the fully-unbracketed version looks, but that would only apply to contracts and value definitions. Things like require/provide, macro, and type definitions would still need to be explicit, anyway.

Yup. A high level pattern across languages I usually see is that value declarations use binders = expression
syntax and all other declarations are prefixed with some sort of keyword or other syntactic marker.

python, java, c, rust, haskell, etc.

they pretty much all follow that rough pattern

It’s a nice pattern in my opinion

It is convenient syntax, but given how extensible this language will be, it comes across as a little odd. My feelings on this are similar to how I feel about special sugar for certain data types. Perhaps I’m being too much of a purist :thinking_face:

I think one of the jobs of a language is to emphasize to the reader what things are special and worthy of syntactic emphasis

I mean, I intentionally broke away from traditional Lisp syntax and used infix symbols and keywords, for that very reason… You have a good point.

If everything looks (syntactically) homogenous reading is much harder because the syntactic emphasis doesn’t reflect the content’s semantic emphasis

people have to learn the common repeating phrases in the content and they train their eyes to start seeing that as syntax instead of content

I think that’s what part of what lispers talk about when they say that after a while the parens sort of fade away. They’ve stopped looking at parens for structure and are looking at commonly occurring words for structure, words like define
and let
and struct

That’s precisely why I use =
as a literal in define
and let
, so that it provides a cue, even in the middle of the form.

Yup and I think that’s a great decision

let x = 5
reads totally different from variable x 5

I think I’ll try the light contract/define syntax. It’s easy to whip up a new #%module-begin
.

I’ve got the new syntax working and the core libraries updated to use it. The parser could use some improvements, but it works.

I made a new contract building macro for function contracts; it subsumes ->
and ->*
, selecting the appropriate one, and uses syntax that better fits my lang. {λ/c boolean? number? → any} ; simple procedure
{λ/c number? ... → number?} ; rest argument
{λ/c number? [string?] → any/c} ; optional arguments
{λ/c #:foo boolean? [number? #:bar char?] char? ... → (values string? number?)}
The names of the generated contracts share the syntax of the macro (except all delimiters print as parens because the names are built with lists).

Hmm, there was a github issue I started forever ago about the syntax of function contracts, might be relevant. Lemme find it.


That reminds me, I was going to add support for ...+

I’m still not sure how I feel about procedures that take m modulo n
arguments, but if I can figure out a nice interface for working with such argument lists, then I might be swayed.