Getting Started with Lisp for Pythonista: Supplement

Introduction

This is a supplementary article to Introduction to Lisp for Pythonista (Hy tutorial Japanese translation). In Hy Tutorial, most of the content was ** "What you write in Python, write this in Hy" **, but that too There are some parts that are not enough for practical use. In this article, I would like to provide a supplementary explanation of Hy's unique features, in other words, Hy's features ** and Hy's unique features ** that Python does not have. If you read this, I think it's enough to use Hy instead of Python for the time being.

I want to read it together

-Introduction to Lisp for Pythonista (Hy tutorial Japanese translation) -Squid Lisp written in Python: Hy -Implementing the Django app on Hyde -I made a tool to compile Hy natively

I found the following good articles other than Qiita, so please refer to them as well. (Previous post and Moro cover, this is more useful ...)

-[Hy (hylang) starting from zero](https://masatoi.github.io/2017/05/11/hy-tutorial#%E5%88%B6%E5%BE%A1%E6%A7%8B% E9% 80% A0)

Mold

It supports Python-like types. However, there are some differences between Python 2.x series and 3.x series. The behavior is the same, but the type is different.

Integer type

In Python 2.x, it is long, and in 3.x series, it is ʻint`.

Integer in Python2


=> (type 2)
<type 'long'>

Integer in Python 3


=> (type 2)
<class 'int'>

String type

In Python 2.x it is ʻunicode, and in 3.x series it is str`.

String in Python2


=> (type "a")
<type 'unicode'>

String in Python 3


=> (type "a")
<class 'str'>

By the way, the character string is enclosed in double quotation marks (" ). Quotation marks (') are not allowed. Also, unlike Python, line breaks are allowed in the middle.

nil Hy is Lisp, so I don't think it will start without nil. However, Hy is also Python, so it is rather Python-compliant in terms of types. Therefore ** there is no nil **. Use an empty list ([] or ()), an empty tuple ((,)), False, None, etc., depending on the context. If you are a Pythonista who is not Lisper, you can continue as before.

Bracket type

Like Clojure, unlike other Lisps, ** there is a distinction in parentheses **.

(...) Represents a list. However, since it is a slightly different concept from Python's list, we will call it * Lisp list * for the sake of distinction. In the * Lisp list *, the first element is a function and the remaining elements are applied as its arguments. Therefore, (function value1 value2 ...) is equivalent to function (* [value1, value2 ...]) in Python.

Lisp list


=> (+ 1 2 3)
6
=> (1 2 3)
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/hy/cmdline.py", line 102, in runsource
    ast_callback)
  File "/usr/local/lib/python3.5/dist-packages/hy/importer.py", line 176, in hy_eval
    return eval(ast_compile(expr, "<eval>", "eval"), namespace)
  File "<eval>", line 1, in <module>
TypeError: 'int' object is not callable

In the second example, we evaluated 1 (* [2, 3]) in Python, and of course 1 is not callable, so an error occurs. Use quotation marks (') when you don't want to evaluate as a function.

Lisp list with quotes


=> '(1 2 3)
(1 2 3)

[...] Represents a so-called list in Python. It is also called a vector, learning from Clojure. This is equivalent to the quoted * Lisp list * above.

Python list


=> [1 2 3]
[1, 2, 3]
=> (= [1 2 3] '(1 2 3))
True

{...} Like Python, it uses a dictionary (dict). Arrange the keys and elements alternately.

dict


=> {"a" 1 "b" 2}
{'b': 2, 'a': 1}

The key may also be in the form : key.

dict part 2


=> {:a 1 :b 2}
{'\ufdd0:a': 1, '\ufdd0:b': 2}

Use get to access the element.

Access elements of dict


=> (get {"a" 1 "b" 2} "a")
1
=> (get {:a 1 :b 2} :a)
1

Also, in the case of the second writing method (: key), the following access is also possible.

Access the elements of dict Part 2


=> (get {:a 1 :b 2} :a)
1
=> (:a {:a 1 :b 2})
1

Summary

The table up to this point is summarized. Also, although not explained, tuples are also included in the table.

Expression in Hy Corresponding Python representation
list
(function arg1 arg2 ...)
Function call
function(*[args1, arg2, ...])
Quarted list
'(elem1 elem2 ...)
list
[elem1, elem2, ...]
Vector
[elem1 elem2 ...]
list
[elem1, elem2, ...]
Tuple
(, elem1 elem2 ...)
Tuple
(elem1, elem2, ..., )
Dictionary 1
{"key1" elem1 "key2" elem2 ...}
dictionary
{"key1": elem1, "key2": elem2, ...}
Dictionary part 2
{:key1 elem1 :key2 elem2 ...}
dictionary
{"key1": elem1, "key2": elem2, ...}

Function overloading (overloading)

In Python, unlike C ++ and Java, it is not possible to switch the body of a function depending on the type and number of arguments. Hy has macros to achieve this in the modules under hy.contrib.

hy.contrib.multi.defn The built-in macro defn is extended to realize polymorphism depending on the number of arguments.

Number overload by defn


(require [hy.contrib.multi [defn]])

(defn function
  ([a](+ "a = " (str a)))
  ([a b](+ "a = " (str a) ", b = " (str b))))

(print (function 3))
;; > a = 3
(print (function 3 4))
;; a = 3, b = 4

In the above example, the processing is switched depending on whether the number of arguments is one or two. Please be assured that you can use it as it is without any problem if you use it in the same way as normal defn.

hy.contrib.multi.defmulti, defmethod, default-method It realizes specialization by multiple dispatch. This is the so-called multi-method.

Multi-method by defmulti


(require [hy.contrib.multi [defmulti defmethod default-method]])

(defmulti add [x y](, (type x) (type y)))

(defmethod add (, int str) [x y]
  (+ (str x) y))

(defmethod add (, str int) [x y]
  (+ x (str y)))

(default-method add [x y]
  (+ x y))

(print (add 1 "st"))
;; > 1st
(print (add "FF" 14))
;; > FF14
(print (add 2 4))
;; > 6
(print (add "Hello, " "world!"))
;; > "Hello, world!"

The defmulti macro defines the elements used for the argument conditions. In this case, the tuple (, (type x) (type y)), which takes two arguments and stores the type of the argument, is used as a trigger for dispatching. The defmethod macro sets conditions and defines the execution contents for each condition. Also, the default-method macro can define what to do if none of the conditions are met. In the above example, the type is the trigger, but you can trigger anything. For example, the following code is okay.

Multi-method by defmulti Part 2


(require [hy.contrib.multi [defmulti defmethod default-method]])

(defmulti funtion [&rest args](first args))

(defmethod funtion 1 [&rest args]
  (print "the list of arguments starts with 1"))

(defmethod funtion 2 [&rest args]
  (print "the list of arguments starts with 2"))

(default-method funtion [&rest args]
  (print "the list of arguments starts with something other than 1 and 2"))

(funtion 1 3 4)
;; > the list of arguments starts with 1
(funtion 2 3)
;; > the list of arguments starts with 2
(funtion 4 8 9 0)
;; > the list of arguments starts with something other than 1 and 2

In the above example, multiple arguments are received and the first element is dispatched.

Delay sequence

It's like a Python generator, but unlike a generator, you can access the same elements over and over again. Of course, it is lazy evaluated like the generator. In short, it's like a list with lazy evaluation + memoization. It can be defined with the hy.contrib.sequences.defseq macro. For example, Fibonacci can be written as follows.

Fibonacci with delayed sequence


(require [hy.contrib.sequences [defseq]])
(import [hy.contrib.sequences [Sequence]])
;;Since it is used after macro expansion, it must be imported.

(defseq fibonacci [n]
  (if (<= n 1) n
      (+ (get fibonacci (- n 1)) (get fibonacci (- n 2)))))

(print (get fibonacci 100))
;; > 354224848179261915075

The above code finishes in milliseconds thanks to lazy evaluation, but writing it normally in a recursive function is terribly time consuming.

Preceding dot syntax (2017/06/06 postscript)

In Hy, you can use * leading dot syntax * for method calls. A syntactic sugar that allows you to write (object.method args ...) as (. method object args ...).

Preceding dot syntax (method call)


=> (def a [1 2 3])
=> (.append a 4)
=> a
[1, 2, 3, 4]

Preceding dot syntax (access to module namespace)


=> (import ast)
=> (.parse ast "print(\"Hello, world!\")" :mode "eval")
<_ast.Expression object at 0xb6a2daec>

Thread macro (2017/06/06 postscript)

As mentioned at the end of Tutorial, Hy has a very useful feature called thread macros that improves readability. This is inherited from Clojure. There are several types, so I will explain them individually.

->, ->> You can write (func3 (func2 (func1))) as (-> func1 func2 func3). If ->, it will be chained as the first argument of the following expression, and if ->>, it will be chained as the last argument. A concrete example is shown.

->When->>Example


=> (def a 8.)
=> (-> a (/ 4) (- 1)) ;; (- (/ a 4) 1)
1.0
=> (->> a (/ 4) (- 1)) ;; (- 1 (/ 4 a))
0.5

as-> With -> and ->>, you can only pass arguments at the beginning or end. It cannot be handled when you want to pass it in the middle or when the position to pass changes depending on the function. It is ʻas->` that plays an active role there.

as->Example


=> (as-> a it
...      (/ it 4)
...      (- 1 it))
-1.0

Here, ʻa is given a temporary name of ʻit.

doto The coat colors are slightly different, but I will introduce them all together. It's like the With syntax in D, which simplifies a series of method calls to a single object. You can write (obj.method1) (obj.method2) ... as (doto obj .method1 .method2 ...).

doto example


=> (doto [2 3](.append 1) .sort)
[1, 2, 3]

How to use thread macros

This is especially useful when the parentheses are deeply nested. In Python, there are many cases where you can write expressions that are not good enough. Consider an example of converting " tanaka taro " to " Taro Tanaka ".

Taro Tanaka in Python


" ".join(map(str.capitalize, reversed("tanaka taro".split())))

It is a little troublesome to follow the processing flow. You can do the same thing with Hy as follows.

Taro Tanaka in Hy


(->> "tanaka taro" .split reversed (map str.capitalize) (.join " "))

Not only does it look very neat, but the process flow is now fluent from left to right. In short, thread macros are like Ruby's methodchain and D language UFCS. Readability will be greatly improved, so use it positively.

in conclusion

If you think of anything else, I will add it. If you have any mistakes, please let us know.

Recommended Posts

Getting Started with Lisp for Pythonista: Supplement
Getting Started with Julia for Pythonista
Getting Started with Python for PHPer-Functions
Getting Started with Python for PHPer-Super Basics
Getting started with Android!
1.1 Getting Started with Python
Getting Started with Golang 2
Getting Started with Golang 1
Getting Started with Python
Getting Started with Django 1
Getting Started with Optimization
Getting Started with Numpy
Getting started with Spark
Getting Started with Python
Getting Started with Pydantic
Getting Started with Golang 4
Getting Started with Jython
Getting Started with Django 2
[Translation] Getting Started with Rust for Python Programmers
Settings for getting started with MongoDB in python
Translate Getting Started With TensorFlow
Getting Started with Python Functions
Getting Started with Tkinter 2: Buttons
Getting Started with Go Assembly
Getting Started with PKI with Golang ―― 4
Getting Started with Python Django (1)
Getting Started with Python Django (4)
Getting Started with Python Django (3)
Getting Started with Python Django (6)
Getting Started with Django with PyCharm
Python3 | Getting Started with numpy
Getting Started with Python Django (5)
Getting Started with Python responder v2
Getting Started with Git (1) History Storage
Getting started with Sphinx. Generate docstring with Sphinx
Getting Started with Python Web Applications
Getting Started with Sparse Matrix with scipy.sparse
Getting Started with Python Basics of Python
Getting Started with Cisco Spark REST-API
Getting started with USD on Windows
Getting Started with Python Genetic Algorithms
Getting started with Python 3.8 on Windows
Getting Started with CPU Steal Time
Building a Windows 7 environment for getting started with machine learning with Python
Getting Started with python3 # 1 Learn Basic Knowledge
Getting Started with Python Web Scraping Practice
Getting Started with Python Web Scraping Practice
Getting started with Dynamo from Python boto
Getting Started with Heroku, Deploying Flask App
Getting Started with TDD with Cyber-dojo at MobPro
Getting started with Python with 100 knocks on language processing
Getting Started with Processing and p5.js (for those who have done other languages) 02
MongoDB Basics: Getting Started with CRUD in JAVA
Getting Started with Drawing with matplotlib: Writing Simple Functions
Getting started with Keras Sequential model Japanese translation
Getting Started with Processing and p5.js (for those who have done other languages) 01
Django Getting Started Part 2 with eclipse Plugin (PyDev)
Getting started with AWS IoT easily in Python
Getting Started with Python's ast Module (Using NodeVisitor)
~ Tips for Python beginners from Pythonista with love ② ~
Materials to read when getting started with Python