Discussion:
A value for "nothing"
HiPhish
2018-08-26 10:13:31 UTC
Permalink
Hello Schemers,

I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit. The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?

In Racket there is the `(void)` [2] procedure which returns a `#<void>`
object, so that's what I am using there [3][4]. Any suggestions for Guile?

[1] https://msgpack.org/
[2] http://docs.racket-lang.org/reference/void.html?q=void
[3] https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/unpack.rkt#L47
[4] https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/pack.rkt#L35
Joshua Branson
2018-08-26 17:27:20 UTC
Permalink
Post by HiPhish
Hello Schemers,
[1] https://msgpack.org/
Thanks for mentioning this! I'm not sure how to answer your question,
but thanks for pointing out msgpack. I hadn't realized that json could
be faster if it was encoded in binary. That's awesome!
Thomas Morley
2018-08-26 17:21:50 UTC
Permalink
Post by HiPhish
Hello Schemers,
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit. The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
Well, actually that's how the value of *unspecified* is defined.

From boot9.scm:

;;; {The Unspecified Value}
;;;
;;; Currently Guile represents unspecified values via one particular value,
;;; which may be obtained by evaluating (if #f #f). It would be nice in the
;;; future if we could replace this with a return of 0 values, though.
;;;

(define-syntax *unspecified*
(identifier-syntax (if #f #f)))

(define (unspecified? v) (eq? v *unspecified*))



Cheers,
Harm
Post by HiPhish
In Racket there is the `(void)` [2] procedure which returns a `#<void>`
object, so that's what I am using there [3][4]. Any suggestions for Guile?
[1] https://msgpack.org/
[2] http://docs.racket-lang.org/reference/void.html?q=void
[3] https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/unpack.rkt#L47
[4] https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/pack.rkt#L35
John Cowan
2018-08-26 17:49:29 UTC
Permalink
Well, you could use #nil, a Guile-specific unique object that is both falsy
(like #f) and answers #t to the null? predicate. It is used to emulate
Common Lisp's and Elisp's nil. But a more portable approach would be to
define a record type with no slots and make just one instance of it.
Post by HiPhish
Hello Schemers,
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit. The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
In Racket there is the `(void)` [2] procedure which returns a `#<void>`
object, so that's what I am using there [3][4]. Any suggestions for Guile?
[1] https://msgpack.org/
[2] http://docs.racket-lang.org/reference/void.html?q=void
[3]
https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/unpack.rkt#L47
[4]
https://gitlab.com/HiPhish/MsgPack.rkt/blob/master/msgpack/pack.rkt#L35
Mark H Weaver
2018-08-27 04:52:27 UTC
Permalink
Post by John Cowan
Well, you could use #nil, a Guile-specific unique object that is both falsy
(like #f) and answers #t to the null? predicate. It is used to emulate
Common Lisp's and Elisp's nil.
As I wrote earlier, I would avoid using #nil for anything outside of its
intended use case.
Post by John Cowan
But a more portable approach would be to define a record type with no
slots and make just one instance of it.
If _all_ symbols must be distinguishable from the "nothing" object, then
John's suggestion is a fine solution. To avoid unnecessary heap
allocation, you should indeed arrange to make only one instance of it,
as John said.

However, in most cases, symbols are precisely what's needed to represent
distinguished atomic objects such as this.

I looked at <https://en.wikipedia.org/wiki/MessagePack> and I don't see
why a symbol couldn't be used to represent MessagePack's 'nil'.

I hope that HiPhish is not planning to use symbols to represent
MessagePack strings. That would be an abuse of symbols, IMO. Symbols
are intended to represent atomic objects (i.e. objects containing no
internal structure) whose only essential operation is to compare them
for equality.

A practical problem with using symbols to represent strings is that
symbols need to be "interned", i.e. stored in a global hash table, to
ensure that any two symbols containing the same sequence of characters
are represented by the same object. A consequence of this is that every
time a new symbol is encountered, it must be added to the global hash
table, which in turn must be protected by a mutex. This would slow down
creation of MessagePack strings, especially if several threads are doing
it concurrently, to no good end that I can see.

Mark
John Cowan
2018-08-27 13:00:26 UTC
Permalink
Post by Mark H Weaver
However, in most cases, symbols are precisely what's needed to represent
distinguished atomic objects such as this.
The problem with symbols in Scheme is that they are not namespaced, so
two different modules can use the same symbols in different ways.
Exported variable bindings can be managed with the module system.
Post by Mark H Weaver
I hope that HiPhish is not planning to use symbols to represent
MessagePack strings.
However, in formats like JSON where map keys are always strings,
it can save a lot of space to represent them as symbols, since
they are often repeated from one object to the next. There is no such
limitation in MessagePack, although I bet strings are the most common
type of map keys.
--
John Cowan http://vrici.lojban.org/~cowan ***@ccil.org
Knowledge studies others / Wisdom is self-known;
Muscle masters brothers / Self-mastery is bone;
Content need never borrow / Ambition wanders blind;
Vitality cleaves to the marrow / Leaving death behind. --Tao 33 (Bynner)
Mark H Weaver
2018-08-27 21:29:33 UTC
Permalink
Post by Mark H Weaver
However, in most cases, symbols are precisely what's needed to represent
distinguished atomic objects such as this.
The problem with symbols in Scheme is that they are not namespaced, so
two different modules can use the same symbols in different ways.
Exported variable bindings can be managed with the module system.
Right, but this is only a problem if the same variable might contain
either a MessagePack or some other data type that uses the same symbol
to mean something different. In other words, this is only a problem if
you need to be able to distinguish between a MessagePack and some other
data type.

However, given that a MessagePack can also be a boolean, integer, float,
string, etc, and that the plan is apparently to use the corresponding
native Scheme objects to represent these things (which I agree is a good
idea), it will already be infeasible to distinguish between a
MessagePack and anything else.

So, while I agree that this is something to keep in mind when using
symbols in Scheme, I think it's a moot point in this case.

On the other hand, one disadvantage with using record types is that they
cannot be written and then read back in with the standard Scheme reader.
If this is considered desirable, it might be a point in favor of using
symbols instead of records.

Mark
Mark H Weaver
2018-08-27 21:32:04 UTC
Permalink
Post by John Cowan
However, in formats like JSON where map keys are always strings,
it can save a lot of space to represent them as symbols, since
they are often repeated from one object to the next. There is no such
limitation in MessagePack, although I bet strings are the most common
type of map keys.
Agree that this is a point in favor of using symbols to represent
MessagePack strings.

Mark
Mark H Weaver
2018-08-26 20:07:13 UTC
Permalink
Hi,
Post by HiPhish
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile?
First of all, thank you very much for asking the question. I often wish
that authors of Guile libraries would more often ask for design advice
here before committing to a particular API.
Post by HiPhish
I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit.
"The return value of a function that returns nothing" is a
self-contradictory notion, if you think about it :)
Post by HiPhish
The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
*unspecified* is identifier syntax for (if #f #f), i.e. it expands into
the latter.

However, I would strongly advise against writing code (or worse, APIs)
that depend on (if #f #f) or *unspecified* returning a particular
distinguished value.

Quoting R5RS:

If the value of an expression is said to be "unspecified," then the
expression must evaluate to some object without signalling an error,
but the value depends on the implementation; this report explicitly
does not say what value should be returned.

It's true that Guile historically has a special object distinct from all
other objects, which (if #f #f) and various other expressions return,
and which prints as "#<unspecified>".

However, the fact that some existing code out there might depend on the
existence of this distinguished object, and that certain expressions in
Guile return it, is historical baggage which carries non-zero costs as
we move to native code generation.

I would also argue that it carries a terrible conceptual cost, in that
it leads to confusion between the concept of a truly unspecified return
value (as in R5RS) and this distinguished value in Guile that is called
"the unspecified value", a non-sensical notion.

I would also avoid Guile's #nil. That is a very special value, for one
purpose relating to Elisp compatibility, and ideally it should not be
used for anything else.
Post by HiPhish
In Racket there is the `(void)` [2] procedure which returns a `#<void>`
object, so that's what I am using there [3][4]. Any suggestions for Guile?
I would suggest using a symbol. How about 'nil?

Mark
Matt Wette
2018-08-26 22:08:07 UTC
Permalink
Post by Mark H Weaver
Post by HiPhish
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile?
However, I would strongly advise against writing code (or worse, APIs)
that depend on (if #f #f) or *unspecified* returning a particular
distinguished value.
That tells me we should never use `(define x)'.
Post by Mark H Weaver
I would also avoid Guile's #nil. That is a very special value, for one
purpose relating to Elisp compatibility, and ideally it should not be
used for anything else.
#nil only appears in the manual in association with the elisp extension.
I'm guessing it only intended to only appear in Scheme programs as '(const #nil)
when writing tree-il.
t***@tuxteam.de
2018-08-27 08:04:15 UTC
Permalink
On Sun, Aug 26, 2018 at 04:07:13PM -0400, Mark H Weaver wrote:

[...]
Post by Mark H Weaver
It's true that Guile historically has a special object distinct from all
other objects, which (if #f #f) and various other expressions return,
and which prints as "#<unspecified>".
However, the fact that some existing code out there might depend on the
existence of this distinguished object, and that certain expressions in
Guile return it, is historical baggage which carries non-zero costs as
we move to native code generation.
I would also argue that it carries a terrible conceptual cost, in that
it leads to confusion between the concept of a truly unspecified return
value (as in R5RS) and this distinguished value in Guile that is called
"the unspecified value", a non-sensical notion.
I would also avoid Guile's #nil. That is a very special value, for one
purpose relating to Elisp compatibility, and ideally it should not be
used for anything else.
I must admit that I'm... pretty confused about this very prescriptive
tone.

Sorry.

Cheers
- -- tomás
Mark H Weaver
2018-08-27 20:12:21 UTC
Permalink
Post by t***@tuxteam.de
Post by Mark H Weaver
I would also avoid Guile's #nil. That is a very special value, for one
purpose relating to Elisp compatibility, and ideally it should not be
used for anything else.
I must admit that I'm... pretty confused about this very prescriptive
tone.
You're right, I should have explained. I'm overburdened at the moment.

For one thing, HiPhish has already indicated that his library will
support both Guile and Racket, so I conclude that he's at least somewhat
interested in portability to other Scheme implementations.

If he would like people to be able to write code that uses his library
and works on multiple Scheme implementations, then it will certainly be
an impediment to use a Racket-specific value for Nil on Racket, and a
Guile-specific value for Nil on Guile. It would be much better to use a
portable Scheme data structure uniformly.

More generally, even for people only interested in supporting Guile, if
they would like their libraries to be usable from Elisp code on Guile,
which may become important some day if Guile-Emacs matures, then it's
problematic to use #nil for anything that needs to be distinguished from
'() or #f.

Alternatively, if Guile-Emacs dies and people give up on that project,
which is entirely possible, then we should probably deprecate #nil at
some point and eventually remove it. Supporting #nil has costs, both in
performance and in the size of generated native code, when checking
whether an object is true (in every 'if' or 'cond') and when checking
for '(). That performance cost is insignificant in an interpreter or VM
implementation, but as we move to native code generation it may become
significant, as both of the aforementioned operations are quite
ubiquitous.

Mark
Hans Åberg
2018-08-27 20:54:03 UTC
Permalink
Post by Mark H Weaver
More generally, even for people only interested in supporting Guile, if
they would like their libraries to be usable from Elisp code on Guile,
which may become important some day if Guile-Emacs matures, then it's
problematic to use #nil for anything that needs to be distinguished from
'() or #f.
Maybe you need an addition: an empty value.
Mark H Weaver
2018-08-27 20:46:05 UTC
Permalink
Post by Mark H Weaver
If he would like people to be able to write code that uses his library
and works on multiple Scheme implementations, then it will certainly be
an impediment to use a Racket-specific value for Nil on Racket, and a
Guile-specific value for Nil on Guile. It would be much better to use a
portable Scheme data structure uniformly.
One might object, and point out that since the library will presumably
include procedures to construct and test for nil, it shouldn't matter if
they are represented by different objects under the hood.

The problem is, if #nil is used, it's quite likely that some code built
on top of this library and developed using Guile will end up relying on
the fact that #nil is considered false, and that (null? #nil) returns
true. That would lead to possibly subtle breakage when the same code is
then used on another implementation.

Mark
Matt Wette
2018-08-28 00:50:14 UTC
Permalink
Is it reasonable to expect that if a value can be assigned to a variable
then a predicate exists to test for that value type? So, if

(define a (if #f #f))

does not signal an error then there should be a predicate to indicate the
value associated with a is unspecified?

If the define allowed in RnRS? I don't believe there is a predicate to
test for this. (I could be wrong.)

Comments?

Matt
Mark H Weaver
2018-08-28 06:58:37 UTC
Permalink
Post by Matt Wette
Is it reasonable to expect that if a value can be assigned to a variable
then a predicate exists to test for that value type? So, if
(define a (if #f #f))
does not signal an error then there should be a predicate to indicate the
value associated with a is unspecified?
If the define allowed in RnRS? I don't believe there is a predicate to
test for this. (I could be wrong.)
In RnRS, (define a (if #f #f)) is allowed and guaranteed to assign
*some* object to 'a' without signalling an error. However, it's not
specified what object will be assigned. It could be 2 or (foo bar) or
"the cow jumps over the moon".

So, in RnRS, there's no predicate that you could apply to 'a' and be
assured that the result will be #t on all conforming implementations.

Guile has always had a predicate 'unspecified?' since at least 1996, but
personally I would advise against relying on its continued existence in
the future.

Regarding your question whether it's reasonable to expect that every
object has an associated predicate to test for it, I don't know. It's
an interesting question, but I wonder what would be the practical use of
such an expectation?

Given the fact that there are several mechanisms to add new types that
are distinct from all other types, and that we occasionally add new core
types to Guile that no previously extant predicate would answer #t for,
you certainly cannot rely on being able to write a 'cond' statement that
tests an arbitrary object using some set of predicates and be assured
that at least one of those predicates will answer #t. You could write
such a 'cond' statement today with that property, but for a future
version of Guile you might find an object for which none of those
predicates returns #t.

Regards,
Mark
John Cowan
2018-08-28 15:19:06 UTC
Permalink
Post by Mark H Weaver
In RnRS, (define a (if #f #f)) is allowed and guaranteed to assign
*some* object to 'a' without signalling an error.
Actually, the phrase used is "the result is unspecified", which
unfortunately
is not defined in any RnRS. Racket produces a syntax error in this
situation
at least in its default language.
Post by Mark H Weaver
However, it's not
specified what object will be assigned. It could be 2 or (foo bar) or
"the cow jumps over the moon".
In practice, it is #t, #f, (), or a unique unspecified object across all the
Schemes I have tested, most often the last.
--
John Cowan http://vrici.lojban.org/~cowan ***@ccil.org
LEAR: Dost thou call me fool, boy?
FOOL: All thy other titles thou hast given away:
That thou wast born with.
Mark H Weaver
2018-08-28 15:38:41 UTC
Permalink
Post by Mark H Weaver
In RnRS, (define a (if #f #f)) is allowed and guaranteed to assign
*some* object to 'a' without signalling an error.
Actually, the phrase used is "the result is unspecified", which unfortunately
is not defined in any RnRS.
That's the phrase used in R7RS-small, which fails to define it, as you
noted, but that shortcoming is limited to R7RS.

In R6RS, section 11.4.3 (Conditionals) provides this example:

(if #f #f) ===> unspecified

whose meaning is defined in section 6.6 (Evaluation examples), which
states:

Moreover, the "===>" symbol is also used to explicitly say that the
value of an expression is unspecified. For example:

(eqv? "" "") ===> unspecified

I take the use of the singular form of "value" here to imply that it
returns only one value.

R5RS is even more clear. It states "If <test> yields a false value and
no <alternate> is specified, then the result of the expression is
unspecified."

Section 1.3.2 of R5RS makes it crystal clear what that means:

If the value of an expression is said to be "unspecified," then the
expression must evaluate to some object without signalling an error,
but the value depends on the implementation; this report explicitly
does not say what value should be returned.
Post by Mark H Weaver
Racket produces a syntax error in this situation at least in its
default language.
Racket has diverged from Scheme quite a bit, to the point that they
don't even call the language "Scheme" anymore, but rather "Racket".

Mark
Mark H Weaver
2018-08-28 15:59:40 UTC
Permalink
Post by Mark H Weaver
Post by Mark H Weaver
In RnRS, (define a (if #f #f)) is allowed and guaranteed to assign
*some* object to 'a' without signalling an error.
Actually, the phrase used is "the result is unspecified", which unfortunately
is not defined in any RnRS.
That's the phrase used in R7RS-small, which fails to define it, as you
noted, but that shortcoming is limited to R7RS.
Actually, the behavior _is_ clearly defined, in the formal denotational
semantics in both R5RS and R7RS. If you learn how to read those, you'll
see that there's no question that (if #f #f) is guaranteed to return
exactly one unspecified value.

Mark
John Cowan
2018-08-28 16:12:34 UTC
Permalink
Post by Mark H Weaver
That's the phrase used in R7RS-small, which fails to define it, as you
noted, but that shortcoming is limited to R7RS.
The relevant sentences in R5RS and R7RS are identical: " If <test> yields
a false value and no <alternate> is specified, then the result of the
expression is unspecified." Likewise, the paragraph from 1.3.2 you quote
below is identical in both standards. So either they both define it or
they both don't.

In R6RS, section 11.4.3 (Conditionals) provides this example:
Unlike Wil Clinger, and apparently you, I don't believe that examples in
Post by Mark H Weaver
I take the use of the singular form of "value" here to imply that it
returns only one value.
In R6RS 11.13, vector-set! is said to return unspecified values (note
plural), but in the examples appears "⇒ unspecified", showing that this
notation can be used where multiple unspecified values (or zero values) are
allowed.

In practice, I know of no Scheme implementation that returns other than one
value in any of these "unspecified values" situations, which IMO is a Good
Thing.
--
John Cowan http://vrici.lojban.org/~cowan ***@ccil.org
With techies, I've generally found
If your arguments lose the first round
Make it rhyme, make it scan / Then you generally can
Make the same stupid point seem profound! --Jonathan Robie
Mark H Weaver
2018-08-28 17:15:35 UTC
Permalink
Post by Mark H Weaver
That's the phrase used in R7RS-small, which fails to define it, as you
noted, but that shortcoming is limited to R7RS.
The relevant sentences in R5RS and R7RS are identical: " If <test>
yields a false value and no <alternate> is specified, then the result
of the expression is unspecified." Likewise, the paragraph from 1.3.2
you quote below is identical in both standards. So either they both
define it or they both don't.
Unlike Wil Clinger, and apparently you, I don't believe that examples
in specs are normative.
Alright, well, the formal denotational semantics makes it 100%
unambiguous, as I noted in my previous email.

Mark
Mark H Weaver
2018-08-28 19:07:08 UTC
Permalink
Hi John,
Post by Mark H Weaver
That's the phrase used in R7RS-small, which fails to define it, as you
noted, but that shortcoming is limited to R7RS.
The relevant sentences in R5RS and R7RS are identical: " If <test>
yields a false value and no <alternate> is specified, then the result
of the expression is unspecified." Likewise, the paragraph from 1.3.2
you quote below is identical in both standards. So either they both
define it or they both don't.
Good point, you're absolutely right about that. I confess that I didn't
bother to check R7RS carefully, because I simply assumed that you had
good knowledge of it. I only looked carefully at R5RS and R6RS, but I
guess we came to different conclusions from the same text.
Post by Mark H Weaver
Unlike Wil Clinger, and apparently you, I don't believe that examples
I take the use of the singular form of "value" here to imply that it
returns only one value.
In R6RS 11.13, vector-set! is said to return unspecified values (note
plural), but in the examples appears "⇒ unspecified", showing that
this notation can be used where multiple unspecified values (or zero
values) are allowed.
This is also a good point. This looks like a mistake to me in R6RS.
Someone should probably notify them.

Anyway, these ambiguities in the informal descriptions underline the
importance of formal semantics. Thankfully, the core constructs are
unambiguously described by them in the standards, including 'if'.

Thank you for your tireless efforts to promote Scheme standardization,
and also for helping me break through stone walls that were placed in my
way during the R7RS-small process. I'm grateful for your work.

Mark

HiPhish
2018-08-26 20:25:05 UTC
Permalink
The main advantage of JSON is that it is human-readable. This is great if you
want to save the data on disc and be able to get it without needing special
software, or if you want to write it out by hand but be able to parse it by a
computer. I actually had done that, I maintained a number of records by hand
and use a small script to splice the data in a modified form into a LaTeX
document.

However, being text-based becomes a liability when there is no need for human
readability. For instance, if you want to send data between processes there
will never be a human who will eyeball it, size and parsing efficiency are
much more important. JSON is non-trivial to parse.

I am writing this implementation of MessagePack because I want to be able to
write a Neovim client for Guile. This will effectively allow writing Neovim
plugins in Guile: Neovim launches an external Guile process and the two
processes communicate via RPC using MessagePack as the protocol. This way
Neovim can be retrofitted with any language for writing plugins; old Vim
needed be be compiled with support for foreign scripting languages baked into
the binary.
Panicz Maciej Godek
2018-08-27 00:17:06 UTC
Permalink
Post by HiPhish
Hello Schemers,
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit. The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
In my experience, if #f doesn't make sense as a legal value, then using #f
is probably the idiomatic Scheme way to go.
It composes with SRFI-2's and-let* in a way similar to Haskell's Nothing
within the "do" notation.
I did find it useful when I was implementing a pattern matching facility,
where I could distinguish between an empty list of (successful) bindings
and a failed match.
But I think you would need to tell us more about the library: where do the
values come from and what do they represent. What would this "nil" data
type be supposed to stand for?
t***@tuxteam.de
2018-08-27 08:02:44 UTC
Permalink
Post by Panicz Maciej Godek
Post by HiPhish
Hello Schemers,
I am writing an implementation of MessagePack [1] for Guile and a part of the
spec is the presence of a "nil" data type. What would be a good value to
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return value of a
function that returns nothing would be a good fit. The function `display` for
example returns an `#<unspecified>` value, but the only way of producing it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
In my experience, if #f doesn't make sense as a legal value, then using #f
is probably the idiomatic Scheme way to go.
[...]

In this case, as msgpack has explicit true and false values, those seem
the "natural" correspondents of Scheme's #t and #f.

Cheers
- -- tomás
Panicz Maciej Godek
2018-08-27 08:29:24 UTC
Permalink
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Post by Panicz Maciej Godek
Post by HiPhish
Hello Schemers,
I am writing an implementation of MessagePack [1] for Guile and a part
of
Post by Panicz Maciej Godek
Post by HiPhish
the
spec is the presence of a "nil" data type. What would be a good value
to
Post by Panicz Maciej Godek
Post by HiPhish
express "nothing" in Guile? I cannot use '() because that would be
indistinguishable from the empty list, so I thought that the return
value
Post by Panicz Maciej Godek
Post by HiPhish
of a
function that returns nothing would be a good fit. The function
`display`
Post by Panicz Maciej Godek
Post by HiPhish
for
example returns an `#<unspecified>` value, but the only way of
producing
Post by Panicz Maciej Godek
Post by HiPhish
it
without side effects so for is the value of `(if #f #f)`. Is there a better
way?
In my experience, if #f doesn't make sense as a legal value, then using
#f
Post by Panicz Maciej Godek
is probably the idiomatic Scheme way to go.
[...]
In this case, as msgpack has explicit true and false values, those seem
the "natural" correspondents of Scheme's #t and #f.
If that's the case, then perhaps actually returning no value (as in the
(values) form with no arguments) is the way to go?
HiPhish
2018-08-27 08:29:14 UTC
Permalink
Post by Panicz Maciej Godek
In my experience, if #f doesn't make sense as a legal value, then using #f
is probably the idiomatic Scheme way to go.
It composes with SRFI-2's and-let* in a way similar to Haskell's Nothing
within the "do" notation.
I did find it useful when I was implementing a pattern matching facility,
where I could distinguish between an empty list of (successful) bindings
and a failed match.
But I think you would need to tell us more about the library: where do the
values come from and what do they represent. What would this "nil" data
type be supposed to stand for?
I literally don't know where the values will come from, that's the thing.
MessagePack is a data serialization format: a process has some in-memory
object, turns it into bytes and later reads those bytes to generate an in-
memory object. The bytes could come from an entirely different process in a
language which distinguishes between "nothing", "falsely" and "empty list". If
you use MessagePack for a remote procedure call and on the other end you
*must* be able to distinguish between those things, then you also *must* be
able to distinguish them on Guile's end as well.
HiPhish
2018-08-27 08:24:26 UTC
Permalink
Yes, this sounds like the best solution so far.
The eq? predicate is able to distinguish the three. But I think using a
(define-record-type (<nil> nil? make-nil))
(define nil (make-nil))
and thene export njil and nil? but not <i>ni> or make-nil.
HiPhish
2018-08-27 08:40:05 UTC
Permalink
I think I understand: it just so happens that `(if #f #f)` evaluates to
`#<unspecified>`, but it would still be valid if it evaluated to 5 or
"roflcopter".
Post by Mark H Weaver
"The return value of a function that returns nothing" is a
self-contradictory notion, if you think about it :)
I was under the impression that in Lisp any S-expression evaluates to
*something*, even if that something is some junk value or "nothing".
Post by Mark H Weaver
I would suggest using a symbol. How about 'nil?
I was considering the possibility of serialising symbols as strings, but then
'nil would be indistinguishable from the string "nil". The more I think about,
the more a singleton instance of a custom record makes sense.
Ludovic Courtès
2018-08-27 12:37:36 UTC
Permalink
Hi,

I would suggesting returning zero values, using:

(values)

That way, if a caller wrongfully attempts to get at the return value of
that procedure, it’ll get an error.

Fibers does that in several places, and I think it’s a good convention
as it conveys exactly what you want.

Ludo’.
Mark H Weaver
2018-08-27 19:49:42 UTC
Permalink
Post by Panicz Maciej Godek
(values)
That way, if a caller wrongfully attempts to get at the return value of
that procedure, it’ll get an error.
Fibers does that in several places, and I think it’s a good convention
as it conveys exactly what you want.
You cannot store (values) in a variable or data structure, so it
wouldn't work here.

The issue under discussion is how to represent MessagePack's "nil",
which is one of the possible values that a MessagePack can have,
alongside booleans, integers, floats, strings, arrays, etc.

MessagePack is similar to JSON, so the question we're discussing is
analogous to the question of how to represent JSON's "null" in Scheme.

Mark
Ludovic Courtès
2018-08-28 07:52:21 UTC
Permalink
Hello,
Post by Mark H Weaver
Post by Panicz Maciej Godek
(values)
That way, if a caller wrongfully attempts to get at the return value of
that procedure, it’ll get an error.
Fibers does that in several places, and I think it’s a good convention
as it conveys exactly what you want.
You cannot store (values) in a variable or data structure, so it
wouldn't work here.
The issue under discussion is how to represent MessagePack's "nil",
which is one of the possible values that a MessagePack can have,
alongside booleans, integers, floats, strings, arrays, etc.
Oops, sorry for the off-topic reply!

In this context, my preference would be for a singleton type:

(define-record-type <nothing>
(nothing)
nothing?)

Ludo’.
Loading...