13

str vs. pr-str - humans vs. machines

 3 years ago
source link: https://blog.klipse.tech/clojure/2016/11/24/stringify-clojure.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

This post is an interactive adaptation of a detailed explanation of Alex Miller in the Clojure Google group about Clojure print system.

Clojure’s print system has two families of functions:

  1. one for human consumption
  2. and one for machine consuption
robot

Many kinds of Clojure data print the same in either mode (strings are one exception).

The human printing functions

The human printing is most commonly encountered with functions like println, print and str. It’s designed to print things to the repl or to the console for a person to read:

Strings print without the surrounding quotes:

xxxxxxxxxx
(str "Hello World!")
the evaluation will appear here (soon)...

Newlines are really printed as newlines:

xxxxxxxxxx
(str "Hello\nWorld!")
xxxxxxxxxx
the evaluation will appear here (soon)...

Tabs are really printed as tabs:

xxxxxxxxxx
(str "Hello\tWorld!")
xxxxxxxxxx
the evaluation will appear here (soon)...

, etc…

Dear reader, what other examples could you think about? Please add a comment below…

str doesn’t directly use either Clojure printing mode but instead returns the toString() of each object (this is Java[script]’s built-in printing system).

xxxxxxxxxx
(.toString "Hello\nWorld!")
xxxxxxxxxx
the evaluation will appear here (soon)...

For strings, both Clojure’s printing and str wind up just relying on Java[script] to print a string in a “human-readable” way.

The problem with humans

The problem with humans is that they don’t like to read like machines. In other words str and read-string do not always play well together. Let’s see it in action:

(In clojurescript, read-string is not part of the core so we have to explicitly require it.)

xxxxxxxxxx
(require '[cljs.reader :as r :refer [read-string]])
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(-> (str "Hello World!")
  read-string)
xxxxxxxxxx
the evaluation will appear here (soon)...

Oh oh…

What’s happened there? Where is the World?

In order to discover it, let’s check what is the type of the object returned by read-string:

It is not a string:

xxxxxxxxxx
(-> (str "Hello World!")
  read-string
  string?)
xxxxxxxxxx
the evaluation will appear here (soon)...

But it is a symbol:

xxxxxxxxxx
(-> (str "Hello World!")
  read-string
  symbol?)
xxxxxxxxxx
the evaluation will appear here (soon)...

The reason is because str omits the surrounding quotes - in order to be human friendly:

xxxxxxxxxx
(str "Hello World!")
xxxxxxxxxx
the evaluation will appear here (soon)...

So, for read-string, this string is in fact composed of two symbols. And read-string reads only one object.

xxxxxxxxxx
(read-string "Hello World!")
xxxxxxxxxx
the evaluation will appear here (soon)...

In order to be read-string, one has to not-omit the surrounding quotes:

xxxxxxxxxx
(read-string "\"Hello World!\"")
xxxxxxxxxx
the evaluation will appear here (soon)...

This is exactly the purpose of clojure’s data printing functions…

The data printing functions - for machine consumption

The data printing functions are things like:

  • pr - like print, but for data
  • prn - like println, but for data
  • pr-str - like str, but for data

The idea with the data printers is that the thing you print should be readable by Clojure. So pr-str etc… will print a string as the actual characters Clojure would need to read that string back as data.

Strings print with the surrounding quotes:

xxxxxxxxxx
(pr-str "Hello World!")
xxxxxxxxxx
the evaluation will appear here (soon)...

Newlines are printed as \n:

xxxxxxxxxx
(pr-str "Hello\nWorld!")
xxxxxxxxxx
the evaluation will appear here (soon)...

Tabs are printed as \t:

xxxxxxxxxx
(pr-str "Hello\tWorld!")
xxxxxxxxxx
the evaluation will appear here (soon)...

Clojure types

Clojure types (like maps) implement toString() to route back into the Clojure printing system.

So str and pr-str prints the same:

xxxxxxxxxx
(str {:first "Hello"
      :second "World!"})
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(pr-str {:first "Hello"
         :second "World!"})
xxxxxxxxxx
the evaluation will appear here (soon)...

What else?

This is the big picture. I have left the even more complicated pretty printing (pprint) and cl-format (following CommonLisp) parts.

You are in a maze of twisty little passages, all alike. If you look too hard at it, you are likely to be eaten by a grue.

The print and pprint systems also have many dynamic vars to influence behavior and a number of multimethods intended for extension or modification.

In particular, you can provide your own printers for either built-in types or custom records or types by extending things like print-method (human) or print-dup (data).

If anyone wanted to write a mini Clojure book, this would be a killer topic.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK