Create suffixed numbers Racket
Asked Answered
P

3

6

I'm trying to experiment with what I can do in Racket, and I want to suffix numbers with letters.

For this example, I'd simply like to represent 10000 as 10K, and 1000000 as 1M.

Is there way (with macros or otherwise) that I can expand 1M to:

(* 1 1000000)

Or something to that effect?

Pinkster answered 29/11, 2018 at 17:1 Comment(0)
S
7

In Racket, things like 10K are identifiers, which normally would refer to variables. There are two ways to make them into numbers:

1: redefine what "undefined" identifiers mean

You can redefine what to do on an undefined identifier by defining a #%top macro.

#lang racket
(require syntax/parse/define
         (only-in racket [#%top old-#%top]))

(define-syntax-parser #%top
  [(_ . x:id)
   #:when (id-has-a-k-at-the-end? #'x)
   (transform-id-into-number #'x)]
  [(_ . x)
   #'(old-#%top . x)])

However, this has a subtle problem. If there are any identifiers or variables in your program with K's on the end, they could override any numbers that were written that way. You would need to be careful not to accidentally override something that was intended to be a number.

2: make a reader extension that turns them into numbers instead of identifiers

This will take more time, but it's closer to the "right way" to do this, since it avoids conflicts when variables happen to have K's on the end.

One of the easier ways to extend the reader is with a readtable. You can make a function that extends a readtable like this:

;; Readtable -> Readtable
(define (extend-readtable orig-rt)
  ;; Char InputPort Any Nat Nat Nat -> Any
  (define (rt-proc char in src ln col pos)
    ....)
  ...
  (make-readtable orig-rt
    #f 'non-terminating-macro rt-proc
    ...))

To use this to define a #lang language, you need to put the reader implementation in your-language/lang/reader.rkt. Here that's number-with-k/lang/reader.rkt, where the number-with-k directory is installed as a single-collection package (raco pkg install path/to/number-with-k).

number-with-k/lang/reader.rkt

#lang racket

(provide (rename-out [-read read]
                     [-read-syntax read-syntax]
                     [-get-info get-info]))

(require syntax/readerr
         syntax/module-reader)

;; Readtable -> Readtable
(define (extend-readtable orig-rt)
  ;; Char InputPort Any Nat Nat Nat -> Any
  (define (rt-proc char in src ln col pos)
    ....)
  ...
  (make-readtable orig-rt
    #f 'non-terminating-macro rt-proc))

;; [X ... -> Y] -> [X ... -> Y]
(define ((wrap-reader rd) . args)
  (parameterize ([current-readtable (extend-readtable (current-readtable))])
    (apply rd args)))

(define-values [-read -read-syntax -get-info]
  (make-meta-reader 'number-with-k
                    "language path"
                    lang-reader-module-paths
                    wrap-reader
                    wrap-reader
                    identity))

The main work goes into filling in the .... holes in the extend-readtable function. For example, you can make it recognize identifiers that end with K like this:

;; Readtable -> Readtable
(define (extend-readtable orig-rt)
  ;; Char InputPort Any Nat Nat Nat -> Any
  (define (rt-proc char in src ln col pos)
    (define v (read-syntax/recursive src in char orig-rt #f))
    (cond
      [(and (identifier? v) (id-has-a-k-at-the-end? v))
       (transform-id-into-number v)]
      [else
       v]))

  (make-readtable orig-rt
    #f 'non-terminating-macro rt-proc))

Once this is done, and you have the number-with-k directory installed as a package, you should be able to use #lang number-with-k like this:

#lang number-with-k racket

(+ 12K 15)
; => 12015
Social answered 29/11, 2018 at 17:23 Comment(2)
I thought of #%top as well, and you could use identifier-binding to check whether the identifier already has a binding - if it does, you probably want to skip the transformation.Emmittemmons
An identifier-binding check would not be enough to avoid the problems, because if the identifier is defined, it won't invoke #%top in the first place.Social
B
4

The simplest to is to define the suffixes you need.

(define K 1000)
(define M 1000000)

Then write (* 3.14 M) for 3.14 millions.

As others mention, Racket supports scientific notation 3.14E6 is also 3.14 million.

Yet another alternative is to define functions K, M etc like:

(define (M x) (* x 1000000))

Then you can write

(M 3.14)

to mean 3.14 million.

Businesswoman answered 29/11, 2018 at 17:41 Comment(0)
D
1

Racket does already have built in support for this, kind of, via scientific notation:

1e6 ; 1000000.0 ("1M")
2e7 ; 20000000.0
Drumfire answered 29/11, 2018 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.