Swap the meaning of literals in the Julia REPL
Author rfourquet
8 Stars
Updated Last
2 Years Ago
Started In
March 2020

Build Status


The SafeREPL package allows to swap, in the REPL, the meaning of Julia's literals (in particular numbers). Upon loading, the default is to replace Float64 literals with BigFloat, and Int and Int128 literals with BigInt. A literal prefixed with $ is left unchanged.

julia> using SafeREPL

julia> 2^200

julia> sqrt(2.0)

julia> typeof($2)


This package requires Julia version at least 1.5. It depends on a sub-package, SwapLiterals, described below, which requires only Julia 1.1. Both packages are registered and can be installed via

using Pkg
pkg"add SafeREPL"
pkg"add SwapLiterals"

Custom types

What literals mean is specified via SafeREPL.swapliterals!.

The four arguments of this function correspond to Float64, Int, Int128, BigInt. Passing nothing means not transforming literals of this type, and a symbol is interpreted as the name of a function to be applied to the value. The last argument defaults to nothing.

A single boolean value can also be passed: swapliterals!(false) deactivates SafeREPL and swapliterals!(true) re-activates it with the previous setting. Finally, swapliterals!() activates the default setting (what is enabled with using SafeREPL, which is equivalent to swapliterals!("@big_str", :big, :big), see below for the meaning of "@big_str").


julia> using BitIntegers, BitFloats

julia> swapliterals!(:Float128, :Int256, :Int256)

julia> log2(factorial(60))

julia> sqrt(2.0)

julia> using SaferIntegers, DoubleFloats

julia> swapliterals!(:DoubleFloat, :SafeInt, :SafeInt128)

julia> typeof(2.0)

julia> 2^64
ERROR: OverflowError: 2^64

julia> 10000000000000000000^3
ERROR: OverflowError: 10000000000000000000 * 100000000000000000000000000000000000000 overflowed for type Int128

julia> using Nemo; swapliterals!(nothing, :fmpz, :fmpz, :fmpz)

julia> factorial(100)

julia> typeof(ans), typeof(1.2)
(fmpz, Float64)

julia> [1, 2, 3][1] # fmpz is currently not <: Integer ...
ERROR: ArgumentError: invalid index: 1 of type fmpz

julia> [1, 2, 3][$1] # ... so quote array indices

julia> swapliterals!(false); typeof(1), typeof(1.0) # this swapliterals! doesn't act on this line!
(fmpz, Float64)

julia> typeof(1), typeof(1.0)
(Int64, Float64)

julia> swapliterals!(true)

julia> typeof(1), typeof(1.0)
(fmpz, Float64)

julia> swapliterals!() # activate defaults

julia> typeof(1), typeof(1.0)
(BigInt, BigFloat)

How to substitute other literals?

The more general API of swapliterals! is to pass a list of pairs SourceType => converter, where SourceType is the type on which converter should be applied. For example:

julia> swapliterals!(Char => :string, Float32 => :Float64, UInt8 => :UInt)

julia> 'a', 1.2f0, 0x12
("a", 1.2000000476837158, 0x0000000000000012)

julia> using Strs; swapliterals!(String => :Str)

julia> typeof("a")

Notable exceptions are Symbol and Bool literals, which currently can't be converted with swapliterals! (open an issue if you really need this feature).

String macros

For Int128, UInt128 and BigInt, it's possible to pass the name of a string macro (as a String) instead of a symbol. In this case, the macro is used to directly interpret the number. For example:

julia> swapliterals!(Int128 => "@int1024_str", BigInt => "@int1024_str")

julia> typeof(111111111111111111111111111111111)

julia> 1234...(many digits).....789 # of course very big numbers can't be input anymore!
ERROR: LoadError: OverflowError: overflow parsing "1234..."

As an experimental feature, when a string macro is passed to interpret Float64, the input is then first converted to a String which is passed to the macro:

julia> swapliterals!()

julia> 2.6 - 0.7 - 1.9

julia> swapliterals!(Float64 => "@big_str")

julia> 1.2

julia> 1.2 == big"1.2"

julia> 1.1999999999999999 == big"1.1999999999999999"

julia> 2.6 - 0.7 - 1.9

julia> using DecFP; swapliterals!(Float64 => "@d64_str")

julia> 2.6 - 0.7 - 1.9

For the adventurous

Are you sure?

Few more literals can be substituted: arrays and tuples, and the {} vector syntax, which are specified respectively as :vect, :tuple, :braces. For example:

julia> swapliterals!(:vect => :Set)

julia> [1, 2]
Set{Int64} with 2 elements:

julia> :[1, 2]
:(Set([1, 2]))

julia> $[1, 2]
2-element Array{Int64,1}:

The next question is: how to use the :braces syntax, given that it is not valid normal-Julia syntax? In addition to the previously mentioned converter types (Symbol and String), it's possible to pass a function which is used to transform the Julia AST:

julia> makeset(ex) = Expr(:call, :Set, Expr(:vect, ex.args...));

julia> swapliterals!(:braces => makeset)

julia> {1, 2, 3}
Set{Int64} with 3 elements:

For types which are stored directly in the AST, using a symbol or a function is roughly equivalent (and using $-quoting or :-quoting is similarly equivalent), for example:

julia> swapliterals!(Int => Float64)

julia> (1, :1, $1)
1.0, 1, 1

julia> :(1 + 2)
:(1.0 + 2.0)

julia> swapliterals!(Int => :Float64)

julia> (1, :1, $1)
1.0, 1, 1

julia> :(1 + 2)
:(Float64(1) + Float64(2))

Note that using functions is a rather experimental feature.

A natural question arising pretty quickly is how $-quoting interacts with other $-quoting contexts, in particular with BenchmarkTools. With scalar-substitutions, this is mostly a non-issue, as we usually do not $-quote literal numbers while benchmarking, but this is a bit more subtle when substituting container literals:

julia> swapliterals!(false)

julia> @btime sum([1, 2]);
  31.520 ns (1 allocation: 96 bytes)

julia> @btime sum($[1, 2]);
  3.129 ns (0 allocations: 0 bytes)

julia> @btime sum($(Set([1, 2])));
  20.090 ns (0 allocations: 0 bytes)

julia> swapliterals!(:vect => makeset)

julia> @btime sum($[1, 2]); # $[1, 2] is really a vector
  31.459 ns (1 allocation: 96 bytes)

julia> @btime sum($$[1, 2]); # BenchmarkTools-$-quoting for real [1, 2]
  3.480 ns (0 allocations: 0 bytes)

julia> @btime sum($(begin [1, 2] end)); # BenchmarkTools-$-quoting for real Set([1, 2])
  19.786 ns (0 allocations: 0 bytes)

julia> @btime sum($:[1, 2]) # ???
  20.077 ns (0 allocations: 0 bytes)

Using a symbol versus a function can also have a subtle impact on benchmarking:

julia> swapliterals!(false)

julia> @btime big(1) + big(2);
  176.467 ns (6 allocations: 128 bytes)

julia> @btime $(big(1)) + $(big(2));
  71.681 ns (2 allocations: 48 bytes)

julia> swapliterals!(Int => :big)

julia> :(1 + 2)
:(big(1) + big(2))

julia> @btime 1 + 2
  176.982 ns (6 allocations: 128 bytes)

julia> swapliterals!(Int => big)

julia> :(1 + 2)
:(1 + 2)

julia> dump(:(1 + 2))
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: BigInt
      alloc: Int32 1
      size: Int32 1
      d: Ptr{UInt64} @0x0000000004662760
    3: BigInt
      alloc: Int32 1
      size: Int32 1
      d: Ptr{UInt64} @0x000000000356d4a0

julia> @btime 1 + 2
  63.765 ns (2 allocations: 48 bytes)

Finally, as an experimental feature, expressions involving := can also be transformed, with the same mechanism, for example:

julia> swapliterals!(:(:=) => ex -> Expr(:(=),

julia> a := 1; A # equivalent to `A = 1`

How to use in source code?

Via the @swapliterals macro from the SwapLiterals package, which has roughly the same API as the swapliterals! function:

using SwapLiterals

x = @swapliterals :big :big :big begin
    1.0, 2^123
typeof(x) # Tuple{BigFloat,BigInt}

x = @swapliterals (1.0, 2^123) # shorter version, uses :big as defaults

Note: if you try the above at the REPL while SafeREPL is also active, typeof(x) might be Tuple{BigFloat,BigInt}. Try first swapliterals!(false) to deactivate SafeREPL.

The pair API is also available, as well as the possibility to pass converters in a (literal) array for more clarity:

@swapliterals Int => :big 1

x = @swapliterals [Int => :big,
                   Int128 => :big,
                   Float64 => big
                  ] begin
       1.0, 1, 111111111111111111111
typeof(x) # Tuple{BigFloat,BigInt,BigInt}

Note that passing a non-global function as the converter (to transform the AST, cf. previous section) is likely to fail.

Visual indicator that SafeREPL is active

The following can be put in the "startup.jl" file to modify the color of the prompt, or to modify the text in the prompt. Tweak as necessary.

using REPL

atreplinit() do repl
    repl.interface = REPL.setup_interface(repl)
    julia_mode = repl.interface.modes[1]

    old_prefix = julia_mode.prompt_prefix
    julia_mode.prompt_prefix = function()
        if isdefined(Main, :SafeREPL) && SafeREPL.isactive()

    old_prompt = julia_mode.prompt
    julia_mode.prompt = function()
        if isdefined(Main, :SafeREPL) && SafeREPL.isactive()
            "safejulia> " # ;-)

Switching easily back and forth

You can set up a keybinding to activate or de-activate SafeREPL, e.g. Ctrl-x followed by Ctrl-s, by putting the following in "startup.jl":

using REPL

const mykeys = Dict(
    "^x^s" => function (s, o...)

atreplinit() do repl
    repl.interface = REPL.setup_interface(repl; extra_repl_keymap = mykeys)

Cf. the manual for details. Note that REPL.setup_interface should be called only once, so to set up a keybinding together with a custom prompt as shown in last section, both atreplinit calls must be combined, e.g.

atreplinit() do repl
    repl.interface = REPL.setup_interface(repl; extra_repl_keymap = mykeys)
    julia_mode = repl.interface.modes[1]

    # ... modify julia_mode


  • This package was not tested on 32-bits architectures, so use it at your own risks. By the way, there is no guarantee even on 64-bits architectures...

  • Using new number types by default in the REPL might reveal many missing methods for these types and render the REPL less usable than ideal. Good opportunity for opening ticket/issues in the corresponding projects :) In the meantime, this can be mitigated by the use of $.

  • It should be clear that using BigInt and BigFloat for literals instead of Int and Float64 can make some function calls quite more expensive, time-wise and memory-wise. So SafeREPL just offers a different trade-off than the default Julia REPL, it's not a panacea.

  • float literals are stored as Float64 in the Julia AST, meaning that information can be lost:

julia> using SafeREPL; swapliterals!(Float64 => :big)

julia> :(print(1.2))

julia> 1.2 # this is equivalent to `big(1.2)`

julia> big"1.2"

As said earlier, one can pass "@big_str" for the Float64 converter to try to mitigate this problem: this is currently the default. Another alternative (which does not always produce the same results as with "@big_str") is to call rationalize before converting to a float. There is an experimental option to have SafeREPL implicitly insert calls to rationalize, which is enabled by calling floats_use_rationalize!(true):

julia> bigfloat(x) = BigFloat(rationalize(x));

julia> swapliterals!(Float64 => :bigfloat)

julia> 1.2

julia> swapliterals!(Float64 => :big); SafeREPL.floats_use_rationalize!(true);

julia> 1.2

julia> 1.20000000000001

julia> swapliterals!(Float64 => "@big_str") # rationalize not used

julia> 1.20000000000001

How "safe" is it?

This is totally up to the user. Some Julia users get disappointed when they encounter some "unsafe" arithmetic operations (due to integer overflow for example). "Safe" in SafeREPL must be understood tongue-in-cheek, and applies to the default setting where some overflows will disappear. This package can make Julia quite more unsafe; here is a "soft" example:

julia> swapliterals!(Int => x -> x % Int8)

julia> 1234


Before Julia 1.5, the easiest alternative was probably to use a custom REPL mode, and ReplMaker.jl even has an example to set this up in few lines. Here is a way to use SwapLiterals as a backend for a ReplMaker mode, which uses the valid_julia function defined in its README:

julia> literals_swapper = SwapLiterals.literals_swapper([Int=>:big, Int128=>:big, Float64=>"@big_str"]);

julia> function Big_parse(s)
           expr = Meta.parse(s)

julia> initrepl(Big_parse,
                prompt_text="BigJulia> ",
                prompt_color = :red,

The SwapLiterals.literals_swapper function takes a list of pairs which have the same meaning as in swapliterals!. Note that it's currently not part of the public API of SwapLiterals.

At least a couple of packages have a macro similar to @swapliterals:

  • ChangePrecision.jl, with the @changeprecision macro which reinterprets floating-point literals but also some floats-producing functions like rand().
  • SaferIntegers.jl, with the @saferintegers macro which wraps integers using SaferIntegers types.

Required Packages

Used By Packages