I’d like to continue discussing this thread. https://racket.slack.com/archives/C09L257PY/p1591877598012800
Why not use undefined
as initial value for those fields. https://docs.racket-lang.org/reference/undefined.html
> The constant undefined can be used as a placeholder value for a value to be installed later, especially for cases where premature access of the value is either difficult or impossible to detect or prevent. Since @dbriemann mentioned that “he has a class with fields …”, so I suppose he was writing object-oriented code, while object-oriented code usually means imperative style code.
In imperative style code, we use mutable data structures frequently and there are some situations where we can not know the value of some fields in advance. (e.g. cross-reference, etc)
In these situations, we need a null-pointer
or nullable-type
, instead of Bool
. So I think undefined
is a good option.
What do you think about it?
Usually undefined
is reserved for the compiler to emit. For example letrec
can bind variables to undefined
- and if they are referenced before they are defined, it is an error.
That undefined
is distinct from unsafe-undefined
which is what letrec
uses, and which you should not use (except in very special cases)
@soegaard2 > letrec
can bind variables to undefined
imo, that is cross-reference.
The undefined
in racket/undefined
is not used anywhere that I know of.
So when you need cross-reference
-like stuff, you should use undefined
.
I said cross-reference
-like stuff means: > there are some situations where we can not know the value of some fields in advance. You need a placeholder value.
(Actually, that’s not quite right, it’s used by shared
, by class
contracts in some forms, by the syntax/toplevel
library, and in a few other random places.)
@chansey97: the protocol that Racket programs usually use is, if #f
is not a value of interest, then use #f
for the placeholder value.
In other words: It’s used to implement binding constructs where an expression may refer to the variable before it is initialized.
If #f
is a value of interest, then you have a couple of options
@sorawee I don’t think use #f
(which is a Bool) as placeholder is good practice. Imagine if a value happens to be #f
I don’t think it is either, but that’s the idiom that Racket programs have been using since forever.
I saw an exercise asking to find how many 2 appears in 1…3000.
And I saw this solution in Python.
Then I gave my solution in Racket below.
(require srfi/13)
(for/sum ([i (in-range 3001)])
(string-count
(number->string i) #\2))
I wonder if there is even concise solution?
you can use ~a
instead of number->string
(for*/sum ([i 3001] [c (~a i)] #:when (eqv? c #\2)) 1)
@chansey97 If you have a strong opinion that this idiom is not good, you can say so at https://github.com/racket/rhombus-brainstorming. Rhombus is a project for what the “next version of Racket” would be.
But Racket, as it is right now, has this idiom everywhere, and it would be difficult to break the idiom in the way that maintains consistency.
And several people attempted to fix this idiom. See https://docs.racket-lang.org/rebellion/Option_Values.html for example. But it also means that you must always use these libraries when you write programs. You can’t write vanilla Racket anymore.
@sorawee Thanks for telling me this website. I will submit a proposal later.
I agree that using #f
to represent a missing value isn’t ideal, but with minor tweaking, it can work surprisingly well in practice, at least it has for me. Since I know I’ll likely be using this idiom, I try and define my booleans in such a way that a default value of #f
is reasonable. Rails treats null values in the database as false for booleans, and I never had an issue with that.
@wanpeebaw clearly the winner is (string-length (string-replace (string-join (map ~a (sequence->list (in-range 3001))) "") #px"[^2]+" ""))
On a more serious note, count
seems like a reasonable function to have, so once you have that: (for/sum ([i 3001]) (count (~a i) #\2))
(define (count str ch)
(for/sum ([ c str ])
(if (eqv? c ch) 1 0)))
Hmm… it looks like my original loop is faster than for
(define (count str c)
(let ([ len (string-length str) ])
(let loop ([i 0][ sum 0 ])
(if (< i len)
(loop (add1 i) (if (eqv? c (string-ref str i))
(add1 sum)
sum))
sum))))
Maybe use in-string
in sequence form?
@samdphillips yes, that definitely helps. Not enough, but it does help :slightly_smiling_face: It’s not surprising when I look at the macro expansion of for/sum
- it’s doing a lot.
The expansion looks pretty similar and the for loop is using unsafe ops so it does seem non-intuitive why the there is a difference.
(they look similar up to the point that I stopped the expansion that is, maybe the inner for loop that the for generates does something wonky)
Things in the generated for loop like (if #t xxx yyy)
and (if (and #t (not #f)) xxx yyy)
seem suspicious
I forgot about unsafe ops, I added unsafe-string-length
, unsafe-char=?
and unsafe-string-ref
, and that made mine 1.7% faster :slightly_smiling_face: Probably not worth it.
I ran 7 benchmarks, deleted the best/worst two and averaged the remaining 3.
I’ve typically found the for
family introduces some inefficiency, but it’s usually small, and the resulting code w/ for
can look nicer.
Other idle optimization: using bytes instead of strings (if you can work in latin–1) takes 64% of the time of using strings (both for loops)