notjack
2020-12-31 08:12:10

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.


kellysmith12.21
2020-12-31 08:19:41

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.


kellysmith12.21
2020-12-31 08:29:41

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


kellysmith12.21
2020-12-31 08:33:03

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


kellysmith12.21
2020-12-31 08:50:58

kellysmith12.21
2020-12-31 08:52:39

kellysmith12.21
2020-12-31 08:53:10

And that’s how I’ve done provides.


notjack
2020-12-31 09:44:19

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


notjack
2020-12-31 09:44:41

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


notjack
2020-12-31 09:45:34

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


kellysmith12.21
2020-12-31 09:48:15

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.


kellysmith12.21
2020-12-31 09:49:42

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.


notjack
2020-12-31 09:50:51

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?


kellysmith12.21
2020-12-31 09:52:36

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.


notjack
2020-12-31 09:53:39

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


kellysmith12.21
2020-12-31 09:53:54

I already have a reprovide form.


kellysmith12.21
2020-12-31 09:54:13

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


notjack
2020-12-31 09:54:22

That makes sense


kellysmith12.21
2020-12-31 09:55:46

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


kellysmith12.21
2020-12-31 09:59:03

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.


notjack
2020-12-31 10:02:26

ah yes, that problem


notjack
2020-12-31 10:02:32

I’ve been bitten by that too


kellysmith12.21
2020-12-31 10:12:26

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.


notjack
2020-12-31 10:15:52

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


kellysmith12.21
2020-12-31 10:17:18

Approach 2.


kellysmith12.21
2020-12-31 10:18:16

Approach 3.


notjack
2020-12-31 10:22:36

hmmm


notjack
2020-12-31 10:23:56

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


kellysmith12.21
2020-12-31 10:36:14

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}}


kellysmith12.21
2020-12-31 10:48:58

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}


notjack
2020-12-31 10:53:34

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


kellysmith12.21
2020-12-31 11:01:13

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.


notjack
2020-12-31 11:06:52

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.


notjack
2020-12-31 11:07:35

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


notjack
2020-12-31 11:07:42

they pretty much all follow that rough pattern


notjack
2020-12-31 11:08:22

It’s a nice pattern in my opinion


kellysmith12.21
2020-12-31 11:10:55

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:


notjack
2020-12-31 11:11:42

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


kellysmith12.21
2020-12-31 11:15:32

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.


notjack
2020-12-31 11:16:50

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


notjack
2020-12-31 11:17:52

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


notjack
2020-12-31 11:19:02

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


kellysmith12.21
2020-12-31 11:20:40

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.


notjack
2020-12-31 11:21:18

Yup and I think that’s a great decision


notjack
2020-12-31 11:21:57

let x = 5 reads totally different from variable x 5


kellysmith12.21
2020-12-31 11:39:17

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


kellysmith12.21
2020-12-31 12:22:41

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


kellysmith12.21
2020-12-31 17:14:34

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).


notjack
2020-12-31 22:02:09

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



kellysmith12.21
2021-1-1 04:42:01

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


kellysmith12.21
2021-1-1 04:44:14

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.