Hi Arne,
Thanks for the fixes, I’ll respond only to the points I have something
to say on:
> The reason I don’t want variable names and types interleaved isn’t from
> SRFI 253, but from seeing types obscure the variable names in Typescript
> and Python.
>
> In production code I’ve seen function headers go to fully unreadable
> when argument names got interleaved with ExceptionallyPreciseTypeNames,
> but for the point I’m making here, the official documentation of both
> systems (which shows their best cases) already suffices.
>
> Examples, each with types, then without:
>
> Typescript from https://www.typescriptlang.org/docs/handbook/2/functions.html#specifying-type-arguments
> function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
> function filter1(arr, func) {
>
> Python from https://docs.python.org/3/library/typing.html#abcs-and-protocols-for-working-with-i-o
> def read_and_write(reader: Reader[str], writer: Writer[bytes]):
> def read_and_write(reader, writer):
>
> Try to see the argument names at a glance while reading the code.
> Highlighting helps a bit, but not enough for me.
I orient myself in these signatures using colons (and some other unnamed
neural pathways,) so they were relatively clear to me, but I see your
point.
> That said: this switch between styles should actually be possible while
> evolving a program, because define-checked and define-typed should be
> compatible (I do not see any incompatibilities where one would block the
> other).
Yeah, I don’t see any so far, but I’ll need to understand the whole of
define-typed to make sure.
> And when introspection is implemented via SRFI-283, and we manage to get
> both define-typed and define-checked compatible with that, then mixing
> these shouldn’t even hurt the editing experience.
This is a good dream, and I’m ready to share it.
>>> The argument types and the return type are separated by ->.
>>
>> May I suggest => instead? It’s already used for threading cond / case
>> and SRFI 273 return type checks, so it makes sense to re-use it instead
>> of adding new auxiliary syntax.
>
> Can that get into trouble when a lambda as type predicate uses =>, i.e.
> in cond?
This seems quite improbable, unless you’re parsing really deep into the
signatures / predicates.
> However the only language I found that uses => to mark return types is
> Typescript, and there only in type declarations for functions as
> variables, not on functions themselves. Python, Rust, Haskell, and
> others use ->.
>
> On the other hand, in define-typed this is inside the type definition
> context, so it may be clear that it’s about types.
>
> But I think there’s a reason why they use -> and not =>: it’s that in
> Calculus A → B is how a function mapping from one domain to the other is
> written:
> https://en.wikipedia.org/wiki/Glossary_of_mathematical_symbols#Calculus
>> A → B denotes a function with domain A and codomain B. For naming
>> such a function, one writes f : A → B, which is read as "function f
>> maps any from set A into set B", or f maps A to B " .
>
> So I think -> is the right symbol for separating argument types from
> return types.
These are all valid arguments! I’m not dead set on =>, so I trust you in
making a good judgment.
>> Also, it’s a bummer that rest arguments are not supported. These are
>> quite useful and prevalent!
>
> They are, yes. But I found no way to represent them cleanly while also
> having the return types after the argument types, because
> (a b c . d -> e)
> is not a valid form.
Indeed. Maybe an auxiliary syntax is in order? Something like ellipsis,
I dunno:
(a b c d ... -> e)
scheme-index uses such a notation for variadic arguments, as it’s most
parseable and clean, especially compared to the nastiness of dotted
lists. Something to creatively steal?
> And I don’t want to split the return type from the arguments.
There are benefits in either way, but this is always an aesthetically
charged personal decision, so it’s your call.
> For define-typed* it would be possible to set a rest argument checking
> procedure via #:rest, but this check procedure then also receives all
> arguments defined via #:key.
>
> Not perfect, but it at least provides the possibility.
I realized that, but I still want to see the match between what the
standard procedure allows to get and what its types allow to express.
>>> The implementation for Guile is as fast as manually added type guards
>>> at the start of the body as described in the article Optimizing Guile
>>> Scheme. This makes define-typed an easier way to speed up hot inner
>>> loops, as long as the predicates give the compiler the needed
>>> information.
>>
>> I could’ve been snarky and suggested that we collaborate on improving
>> define-checked that’s already in Guile 😜 But I don’t want to do that,
>> because your work is important and useful too!
>
> I’d have liked to do that, but it provides a lot more tools than I want
> to provide with define-typed.
Well, one can focus on define-checked alone, as the most significant and
(ab)used form among SRFI 253 ones. Optimize that—and the rest is merely
complimentary addition not requiring optimizing to the full.
> Also I hope that the implementation of define-typed already helped speed
> up define-checked. The methods I used to get it fast are all there.
I didn’t get to that yet, but you’re right about it being possible!
>> In particular, I see aesthetic value in define-typed, because you’re
>> reasonable in claiming that types separated from names might be more
>> readable. I don’t necessarily agree with that, but I program in C, so my
>> opinion can be safely discarded here 🤣
>
> C actually was a big influence for me – more exactly: sph-sc
> https://github.com/sph-mn/sph-sc#functions
>
> It’s a way to write C in Scheme-Style:
>
> (define (myfunction a b) (int char void)
> "a description of this function"
> (return 1))
>
> sph-sc is the reason why the implementation also supports the style
>
> (define (name a b) (ret? pred-a? pred-b?)
> …)
>
> It’s just that during usage I caught myself make more mistakes in that
> style ("which one is the return type?") than in the style
>
> (define (name a b) (pred-a? pred-b? -> ret?)
> …)
>
> that I mostly added because people on IRC asked for it and I realized
> that I could hack the syntax rules to support both at the same time.
Oh I see! I didn’t check sph-sc, my bad! I do find this return-first
syntax quite confusing too, but if there’s demand…
>> This is dangerous. What if I wanted to use something like
>>
>> (cut memq <> '(input output input-output))
>>
>> as my return type check? Would be recognized as multiple value checks
>> instead of a single value check, purely because it contains parentheses?
>> SRFI 253 / 273 avoid that ambiguity by always requiring
>> parentheses. Much like standard let and cond requiring them. Sure, it’s
>> ugly and inconvenient at times. But at least it won’t break in horrible
>> ways.
>
> You’re right – thank you for catching this!
>
> The pattern wasn’t structurally clear.
>
> But there’s a way to fix this: for multiple return types have the ->
> inside the list of return types:
>
> (define-typed (foo a) (string? -> string?)
> a)
> (foo "")
> (define-typed (foo a) (string? (-> string? string?))
> (values a a))
> (foo "")
>
> Now these work:
>
> (define-typed (foo a) (string? -> (λ (x) (string? x)))
> a)
> (foo "")
> (define-typed (foo a) (string? (-> (λ (x) (string? (car x)))))
> a)
> (foo "")
> (define-typed (foo a) (string? (-> (λ (x) (string? x))(λ (x)(string? x))))
> (values a a))
> (foo "")
Wow, this is a lot of syntax that the SRFI document does not describe,
ahah. Need to formalize that too.
By now, it feels like there’s too much special syntax for too many
corner cases. There has to be a way to support more cases with less
syntax. There must be.
>> Another note: maybe write out the full grammar of these in the document?
>> It’s fun to reverse-engineer behaviors from syntax-rules, at least for
>> me—but not for everyone I think.
>
> I’m not well-versed in writing grammars, but I would gladly integrate
> one. I can’t expect you to write it, but I’d be grateful if you did …
It doesn’t have to be like Uber-Bachus-Naur-Super-Normal-Form-Grammar,
it can be quite informal. Informal grammars are even more useful, I
think—their crudeness and incompleteness forces you to express things in
a simpler way.
But yeah, I can reverse-engineer some from the code given that it’s
stable. So give me a green light and a week of time, and I’ll either
turn in a grammar or disappear somewhere.
Thanks,
--
Artyom Bologov
https://aartaka.me