Dot notation for object field access and method calls - and bracket notation for indexing

This issue extends to Racket's other data structures as well. Only lists let you write "non-prefixed" functions. So you can call length on a list, but you have to call vector-length on a vector, same with vector-map and vector-set! , etc.

The dot mechanism in sketching actually allows you to add "dot fields" and "dot methods" to structs.
I am not sure it is a good idea to do so - so I haven't advertised or documented it.

Anyways, take a look at line 615 of "dot-method.rkt" and below.

The line

(add-predicate! 'vector vector?)

adds the new "type" vector to the dispatcher.

The line
(add-fields! 'vector '(ref length x y z) )
adds the fields ref, length, x, y and z to the dispatcher.
In a program one can write:

v.length and v.x

The lines

(add-method! 'vector 'length vector-length)
(add-method! 'vector 'ref    vector-ref*)
(add-method! 'vector 'list   vector->list)
(add-method! 'vector 'fill!  vector-fill!)
(add-method! 'vector 'values vector->values)

adds methods.

You can now write (v.list) for (vector->list v) etc.

The problem is of course that (v.list) will be slower than (vector->list v),
but you will have to benchmark it to see how much.

2 Likes

Perhaps you could use the functions from racket/sequence, for example sequence-length works for strings, lists, vectors and many other collection types. sequence-length is a bit long to type, but you could always import a shorter version:

#lang racket
(require
  racket/require
  (filtered-in
   (lambda (name)
     (and (regexp-match #rx"^sequence-" name)
          (regexp-replace #rx"^sequence-" name "s")))
   racket/sequence))

;; all "sequence-" functions are renamed to start with just "s"
(slength "hello")        ;; 5
(slength (vector 1 2 3)) ;; 3
(slength '(a b c d))     ;; 4
(sref "hello" 1)         ;; #\e
(sref (vector 1 2 3) 1)  ;; 2
(sref '(a b c d) 1)      ;; b

Alex.

4 Likes

@soegaard That sounds pretty awesome actually. I agree that performance may be an issue with all these syntaxes that do runtime lookups, but the success of python and ruby (and clojure?) shows that it could be a worthwhile tradeoff for programmer comfort. I think its worth experimenting with.

@alexh I've looked at sequences before and thought they only worked with the for loops for some reason, I didn't realize you could use them standalone. But also if I'm gonna write sequence-something I might as well write the type, so this shortening concept is an improvement.

Perhaps I should take these ideas and try making languages with them at this rate to see how worthwhile they are in practice. :open_mouth: I'll have to find some free time to study all of this more in depth though. Thanks everyone.

Also I should first use Super more extensively and see if it does enough since @soegaard probably considered many of these tradeoffs already when he made it.

Still work in progress, but just recently I've added support for mutation in dot-notation in bazaar/struct.
It requires some manual work though (which could probably be eased with a smarter constructor), but allows for selecting fields:

#lang racket
(require bazaar/struct)

(struct person ([name #:mutable] [address #:mutable] phone))

(define person1 (person "bob" "there" "007"))

;;; With dot notation

(define-with-struct.id person1 person (name phone))
person1.name ; bob
(set! person1.name "alice")
person1.name ; alice
(person-name person1) ; alice

;;; Without dot notation

(define-with-struct person1 person (name [address place])) 
name ; alice
(set! name "bob")
name ; bob
place ; there
(set! place "nowhere")
(person-address person1) ; nowhere

Right now I'm likely the only user of bazaar/struct, but if there is some interest I can extract it to a documented and tested package.

Inheritance is not yet supported, but I'd like it to be.

3 Likes
(.name bella)

I think that Clojure notation is for Java interop?

Generally (IIRC) Clojure prefers hash-tables (which it calls "maps"). To do look-ups you can apply a hash-table to its key or vice versa: e.g. both (:some-key some-map) and (some-map :some-key) work.

If you like that style, you can do something similar in Racket (albeit using symbol keys like 'some-key) by redefining #%app; here's a toy example.


Structs and hash-tables are apples and oranges; each has pros and cons. Structs are faster, smaller, and stricter. Whether you consider "stricter" a bug or a feature, for a given purpose, tends to drive the preference, I think?

Certainly there are applications where functions pass around hash-tables, ignoring or preserving mappings they don't know or care about, and adding or updating others --- and that seems to be an advantage. Whereas for other applications that would seem way too "ad hoc". Probably the answer varies for things closer to the outer edge of an app where it interacts with the rest of the world.

4 Likes