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.
-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)
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.
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'>
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.
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
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 callfunction(*[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, ...} |
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.
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.
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>
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]
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.
If you think of anything else, I will add it. If you have any mistakes, please let us know.
Recommended Posts