Re: [racket-users] Question about Racket design philosophy: returning (void)
On 11 April 2018 at 06:48:49, Matthias Felleisen (matth...@felleisen.org) wrote: > Perhaps the real problem is one of the contract/type system. We have seen > effect systems > over and over again, though usually they try to express complicated > invariants and have > them checked at compile time. What if contract and type systems came with two > arrows: > > ->! for imperative functions, as in “this function may mutate the given > argument” > -> for ‘pure’ functions, as in “this function promises not to mutate the > given argument" I think this would be quite helpful. We can then easily enforce invariants like “the client-supplied function doesn’t mutate the given argument”. (Is there an existing way to enforce this?) On the other hand, I still like returning # for such imperative functions, because: (1) functionality-wise the returned value would be redundant; (2) for fluent DSLs, it could be ambiguous which input argument should be returned, and the best choice may depend on how the API is used in the client code. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Question about Racket design philosophy: returning (void)
Alexis says that (-> vector? exact-integer? any/c void?) is better than (-> vector? exact-integer? any/c vector?) because the former clearly signals the imperative nature of the function inside of the spec while the latter could be either a read-only or a RW function. Perhaps the real problem is one of the contract/type system. We have seen effect systems over and over again, though usually they try to express complicated invariants and have them checked at compile time. What if contract and type systems came with two arrows: ->! for imperative functions, as in “this function may mutate the given argument” -> for ‘pure’ functions, as in “this function promises not to mutate the given argument" but only in that they mutate a given argument. Then we could have both a ‘signal’ in the type/contract signature AND the “useful thing is returned” from Smalltalk (as Neil correctly reminds us). Of course, following my usual Laffer-curve-for-types argument, we should explore the usefulness of this idea with an inspection of existing code and other pragmatic explorations. ;; - - - [[ I think the idea of a fluent interface is a good one, but it doesn’t depend on OO and/or ‘self’ returns at all. We create fluently embedded DSLs in Racket all the time, and never touch either one of them. ]] -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Question about Racket design philosophy: returning (void)
Alexis King wrote on 04/10/2018 03:32 PM: There is definitely a school of thought that buys into the idea of returning “the thing being operated on” rather than returning nothing for side-effectful functions. I think this is most characterized by so-called “fluent interfaces”[1], a way of encoding DSLs into object-oriented languages. As far as I can tell, this was style has been around for a very long time, but it was really forced into mainstream usage by the runaway success of jQuery in the mid to late aughts. Yes, it was idiomatic in Smalltalk to return `self` for some things, and (together with the minimal required punctuation, the method names with interspersed arguments, and parsing disambiguation) this permitted the brave programmer to write neat-looking stream-of-words sequences of messages that jQuery could only dream of. :) In Racket, with the `racket/class` object system, you'd probably use `send*` instead, which is boring, but clear. BTW, Alexis is responding to the suggestion of "always return something useful, rather than void". I think the case of `gzip` without a second argument, however, is different. In that case, the code is generating a kind of handle or locator for the output of some (also side-effect-producing) operation. Given that this generation behavior has been put into this procedure, and that it is common for a program want to do something else with that output or its handle/locator, then I think it makes sense to return the generated locator. (`gzip` is not the best example, because it involves side effects in this other filesystem environment, along with locator naming conventions coming from elsewhere, which is why it can have the output argument unspecified.) -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Question about Racket design philosophy: returning (void)
> On Apr 10, 2018, at 14:00, David Storrs> wrote: > > Aside from I/O, I can't think of too many cases where (void) is the > intuitively correct or most useful return value, but it is extremely > common throughout the built-in Racket functions. I'm not sure where > you're drawing the lines on 'API design' vs 'comprehensive > guidelines', but I'd sure like it if the guideline "Always return > something useful unless there's a really good reason not to" got > included. There is definitely a school of thought that buys into the idea of returning “the thing being operated on” rather than returning nothing for side-effectful functions. I think this is most characterized by so-called “fluent interfaces”[1], a way of encoding DSLs into object-oriented languages. As far as I can tell, this was style has been around for a very long time, but it was really forced into mainstream usage by the runaway success of jQuery in the mid to late aughts. The advantage of fluent interfaces is significant if you have an API that involves frequent object creation and mutation. It makes it possible to program in a more expression-oriented style, similar to the way the GoF builder pattern is useful in C++/Java-style OO languages. However, it has a cost of imprecision: it’s not always clear which thing is the most obvious to return, and it masks when functions exist solely for side-effects. For an example of the first problem, consider the jQuery $.append function[2]: $('.foo').append($('Hello!')) Which element does this return? Does it return the set of elements produced by $('.foo'), or does it return the new element created by $('Hello!')? Both answers are useful, and indeed, jQuery actually includes a separate $.appendTo function[3] that does the exact same thing as $.append but flips the arguments around, mostly to make the method chaining work out more nicely in certain situations. This is an awkward thing for an API designer to worry about; it is confusing for a library to provide the exact same function that just happens to return a different one of its arguments. The other problem with always returning something is that returning # is extremely meaningful: it means the function’s only purpose is to perform a side-effect. When contracts (or, in a statically typed language, types) are used precisely, they can be quite communicative without having to read anything but the signatures alone. When given a Racket function with the following signature: (-> vector? exact-integer? any/c void?) ...it’s pretty likely that function is vector-set!. But now imagine the same function returned the mutated vector: (-> vector? exact-integer? any/c vector?) Now it’s much less immediately clear that this function is intended to be used to perform a side-effect, and I might misinterpret it as returning a new vector instead of updating the existing one. You might argue that the benefit in chaining outweighs the cost of signature clarity, but I think Racket mostly eschews that idea because Racket is a language with a functional bent. It discourages using mutability where immutable structures will do, and of course, useful functions on immutable data cannot return #. Therefore, it’s both (1) rare for idiomatic Racket to use lots of functions that produce #, so they wouldn’t benefit much from threading arguments through, and (2) especially important that side-effectful functions are called out as such as efficiently as possible. Racket makes it easy to use the same value twice without forcing library authors to arbitrarily pick certain arguments to thread through side-effectful functions. Internal definition contexts are available almost everywhere, and there is a plethora of local binding forms. Ultimately, the choice to return # instead of some input argument probably doesn’t dramatically help or harm the language (it would still be Racket if it aligned with the other school of thought), but I happen to like the choice Racket makes. Alexis [1]: https://en.wikipedia.org/wiki/Fluent_interface [2]: http://api.jquery.com/append/ [3]: http://api.jquery.com/appendTo/ -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Question about Racket design philosophy: returning (void)
On Tue, Apr 10, 2018 at 1:18 PM, Neil Van Dykewrote: > Good catch; I agree that it would be better if `gzip` returned the target > path, in all cases. > > There is another change I'd make to that signature: currently, arg > `out-file` is optional, of type `path-string?`, defaulting to > `(path-add-extensionin-file".gz"#".")`. In a backward-compatible way, I'd > instead make it optional with type `(or/c #f path-string?)`, and defaulting > to `#f`, with an `#f` value then resulting in > `(path-add-extensionin-file".gz"#".")` behavior. That way, code calling > `gzip` can pass in the "use the default" value. This pattern becomes more > useful when there is more than one optional argument (whether or not it uses > keywords), but it's helpful even with just the one optional argument. It > also keeps the signatures in the documentation a little simpler. > > This might be getting into the "art" side of API design. We can come up > with some good stylistic guidelines for API design, probably including > guidelines that cover the above, but not comprehensive guidelines. Aside from I/O, I can't think of too many cases where (void) is the intuitively correct or most useful return value, but it is extremely common throughout the built-in Racket functions. I'm not sure where you're drawing the lines on 'API design' vs 'comprehensive guidelines', but I'd sure like it if the guideline "Always return something useful unless there's a really good reason not to" got included. Relatedly, the 'gunzip' function from file/gunzip (https://docs.racket-lang.org/file/gunzip.html) has this signature: (gunzip file [output-name-filter]) → void? file : path-string? output-name-filter : (string? boolean? . -> . path-string?) = (lambda (file archive-supplied?) file) This seems to be incorrect. The output-name-filter is getting a path? instead of a string? for its lead argument. Am I missing something? > > -- > You received this message because you are subscribed to the Google Groups > "Racket Users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to racket-users+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Question about Racket design philosophy: returning (void)
Good catch; I agree that it would be better if `gzip` returned the target path, in all cases. There is another change I'd make to that signature: currently, arg `out-file` is optional, of type `path-string?`, defaulting to `(path-add-extensionin-file".gz"#".")`. In a backward-compatible way, I'd instead make it optional with type `(or/c #f path-string?)`, and defaulting to `#f`, with an `#f` value then resulting in `(path-add-extensionin-file".gz"#".")` behavior. That way, code calling `gzip` can pass in the "use the default" value. This pattern becomes more useful when there is more than one optional argument (whether or not it uses keywords), but it's helpful even with just the one optional argument. It also keeps the signatures in the documentation a little simpler. This might be getting into the "art" side of API design. We can come up with some good stylistic guidelines for API design, probably including guidelines that cover the above, but not comprehensive guidelines. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
[racket-users] Question about Racket design philosophy: returning (void)
A lot of functions in Racket return (void) instead of a useful value. One example is the gzip function from file/gzip; it would be useful if this returned the filepath to which the file was compressed, but instead it simply returns (void). I have a lot of respect for Racket and its designers, so I'm guessing this wasn't a random choice. What is the reason for this philosophy? -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.