TODO 마크왓슨 AI 클로저 프로그래밍
(마크 왓슨 2023)
- 마크 왓슨
- Practical Artificial Intelligence Programming With Clojure
mark-watson/Clojure-AI-Book-Code :2024
(마크 왓슨 [2021] 2024)
- 마크 왓슨
- Examples for Clojure AI book
마크왓슨 커먼리스프 프로그래밍
(마크 왓슨 2024)
-
마크 왓슨
-
Loving Common Lisp or the Savvy Programmer’s Secret Weapon
Loving Common Lisp
Read Loving Common Lisp, or the Savvy Programmer's Secret Weapon
소개: About the Book
This new edition (released January 2024) of my book "Loving Common Lisp, or the Savvy Programmer's Secret Weapon" adds Large Language Model (LLM) examples and other small changes. I removed some of the older material from the earlier editions and added application examples for deep learning, back-propagation and Hopfield neural networks, using the CLML machine learning library, heuristic search, and using Common Lisp clients for: MongoDB, Solr, and relational databases. For the older 3rd edition I added a chapter using my Natural Language Processing (NLP) library and a short chapter on information gathering. For the 5th edition I added an example application for generating Knowledge Graph data (RDF and Cypher for Neo4j graph database), and hybrid examples for using Python deep learning libraires (using a web service interface).
내 책 "Loving Common Lisp, or the Savvy Programmer's Secret Weapon"의 이 새 판(2024년 1월 출시)에는 대규모 언어 모델(LLM) 예제와 기타 작은 변경 사항이 추가되었습니다. 이전 버전에서 일부 이전 자료를 제거하고 CLML 기계 학습 라이브러리, 휴리스틱 검색을 사용하고 MongoDB, Solr 및 관계형에 대한 Common Lisp 클라이언트를 사용하여 딥 러닝, 역전파 및 Hopfield 신경망에 대한 애플리케이션 예제를 추가했습니다. 데이터베이스. 이전 3판에는 자연어 처리(NLP) 라이브러리를 사용하는 장과 정보 수집에 대한 짧은 장을 추가했습니다. 5판에서는 지식 그래프 데이터(Neo4j 그래프 데이터베이스용 RDF 및 Cypher)를 생성하기 위한 예제 애플리케이션과 Python 딥 러닝 라이브러리(웹 서비스 인터페이스 사용)를 사용하기 위한 하이브리드 예제를 추가했습니다.
The purpose of this book is to provide a quick introduction to Common Lisp and then provide the user with many fun and useful examples for using Common Lisp.
이 책의 목적은 Common Lisp에 대한 간략한 소개를 제공하고 사용자에게 Common Lisp 사용에 대한 재미있고 유용한 많은 예제를 제공하는 것입니다.
Before you buy this book you can first look at the programming examples at https://github.com/mark-watson/loving-common-lisp
The author has used Common Lisp professionally since 1982. 저자는 1982년부터 Common Lisp를 전문적으로 사용해 왔습니다.
All of my Leanpub books, including this book, can be read for FREE online at https://leanpub.com/u/markwatson
Cover Material, Copyright, and License
Copyright 2011-2023 Mark Watson. All rights reserved. This book may be shared using the Creative Commons “share and share alike, no modifications, no commercial reuse” license.
This eBook will be updated occasionally so please periodically check the leanpub.com web page for this book for updates.
If you read my eBooks free online then please consider tipping me https://markwatson.com/#tip.
This is the eighth edition released August 2022.
Please visit the author’s website.
If you found a copy of this book on the web and find it of value then please consider buying a copy at leanpub.com/lovinglisp.
If you would like to support my work please consider purchasing my books on Leanpub and star my git repositories that you find useful on GitHub. You can also interact with me on social media on Mastodon and Twitter.
Preface
Notes on the Eighth Edition Published August 2022
The main change is splitting the Knowledge Graph Navigator (KGN) chapter that features the LispWorks CAPI UI APIs into three chapters for a library for KGN functionality, a text based (console) UI, and a CAPI based UI. I added examples using the OpenAI GPT-3 APIs. There are other small corrections and improvements.
Notes on the Seventh Edition Published March 2021
I added two short chapters to the previous edition: Knowledge Graph Sampler for Creating Small Custom Knowledge Graphs and Using Common Lisp With Wolfram/One.
Notes on the Sixth Edition Published June 2020
Two examples optionally use the CAPI user interface toolkit provided with LispWorks Common Lisp and work with the free personal edition. The first CAPI application is Knowledge Graph Navigator and the second CAPI example is Knowledge Graph Creator. Both of these examples build up utilities for working with Knowledge Graphs and the Semantic Web.
I expand the Plot Library chapter to generate either PNG graphics files or if you are using the free personal edition of LispWorks you can also direct plotting output to a new window in interactive programs.
I added a new chapter on using the py4cl library to embed Python libraries and application code into a Common Lisp system. I provide new examples for embedding spaCy and TensorFlow applications in Common Lisp applications. In earlier editions, I used a web services interface to wrap Python code using spaCy and TensorFlow. I am leaving that chapter intact, renaming it from “Using Python Deep Learning Models In Common Lisp” to “Using Python Deep Learning Models In Common Lisp With a Web Services Interface.” The new chapter for this edition is “Using the PY4CL Library to Embed Python in Common Lisp.”
Notes on the Fifth Edition Published September 2019
There were two chapters added:
- A complete application for processing text to generate data for Knowledge Graphs (targeting the open source Neo4J graph database and also support RDF semantic web/linked data).
- A library for accessing the state of the art spaCy natural language processing (NLP) library and also a state of the art deep learning model. These models are implemented in thin Python wrappers that use Python libraries like spaCy, PyTorch, and TensorFlow. These examples replace a simple hybrid Java and Common Lisp example in previous editions.
I have added text and explanations as appropriate throughout the book and I removed the CouchDB examples.
I have made large changes to how the code for this book is packaged. I have reorganized the example code on GitHub by providing the examples as multiple Quicklisp libraries or applications. I now do this with all of my Common Lisp code and it makes it easier to write smaller libraries that can be composed into larger applications. In my own workflow, I also like to use Makefile targets to build standalone applications that can be run on other computers without installing Lisp development environments. Please follow the directions at the end of the Preface for configuring Quicklisp for easy builds and use of the example software for this book.
Why Use Common Lisp?
Why Common Lisp? Isn’t Common Lisp an old language? Do many people still use Common Lisp?
I believe that using Lisp languages like Common Lisp, Clojure, Racket, and Scheme are all secret weapons useful in agile software development. An interactive development process and live production updates feel like a breath of fresh air if you have development on heavy weight like Java Enterprise Edition (JEE).
Yes, Common Lisp is an old language but with age comes stability and extremely good compiler technology. There is also a little inconsistency between different Common Lisp systems in such things as handling threads but with a little up front knowledge you can choose which Common Lisp systems will support your requirements.
A Request from the Author
I spent time writing this book to help you, dear reader. I release this book under the Creative Commons License and set the minimum purchase price to $0.00 (free!) in order to reach the most readers. Under this license you can share a PDF version of this book with your friends and coworkers and I encourage you to do so. If you found this book on the web (or it was given to you) and if it provides value to you then please consider doing one of the following to support my future writing efforts and also to support future updates to this book:
- Purchase a copy of this book leanpub.com/lovinglisp/ or any other of my leanpub books at https://leanpub.com/u/markwatson
- Hire me as a consultant
I enjoy writing and your support helps me write new editions and updates for my books and to develop new book projects. Thank you!
Older Book Editions
The fourth edition of this book was released in May 2017 and the major changes were:
- Added an example application KGCreator that processes text data to automatically generate data for Knowledge Graphs. This example application supports the Neo4J graph database as well as semantic web/linked data systems. The major changes were:
- Added a backpropagation neural network example
- Added a deep learning example using the Java based Armed Bear Common Lisp with the popular DeepLearning4j library
- Added a heuristic search example
- Added two machine learning examples (K-Means clustering and SVM classification) using the CLML library
- A few edits to the previous text
The third edition was released in October 2014. The major changes made in the 2014 edition are:
- I reworked the chapter Common Lisp Basics.
- I added material to the chapter on using QuickLisp.
The second edition was released in 2013 and was derived from the version that I distributed on my web site and I moved production of the book to leanpub.com.
Acknowledgments
I would like to thank Jans Aasman for contributing as technical editor for the fourth edition of this book. Jans is CEO of Franz.com which sells Allegro Common Lisp as well as tools for semantic web and linked data applications.
I would like to thank the following people who made suggestions for improving previous editions of this book:
Sam Steingold, Andrew Philpot, Kenny Tilton, Mathew Villeneuve, Eli Draluk, Erik Winkels, Adam Shimali, and Paolo Amoroso.
I would like to also thank several people who pointed out typo errors in this book and for specific suggestions: Martin Lightheart, Tong-Kiat Tan, Rainer Joswig, Gerold Rupprecht, HN member rurban, David Cortesi. I would like to thank the following Reddit /r/lisp readers who pointed out mistakes in the fifth edition of this book: arnulfslayer, rpiirp, and itmuckel. I would like to thank Ted Briscoe for pointing out a problem with the spacy web client example in the 6th edition.
I would like to thank Paul Graham for coining the phrase “The Secret Weapon” (in his excellent paper “Beating the Averages”) in discussing the advantages of Lisp and giving me permission to reuse his phrase.
I would especially like to thank my wife Carol Watson for her fine work in editing this book.
DONE Setting Up Your Common Lisp Development System and Quicklisp
These instructions assume the use of SBCL. See comments for LispWorks, Franz Common Lisp, and Closure Common List at the end of this section. I assume that you have installed SBCL and Quicklisp by following the instructions at lisp-lang.org/learn/getting-started. These instructions also guide you through installing the Slime extensions for Emacs. I use both Emacs + Slime and VSCode with Common Lisp plugins for editing Common Lisp. If you like VSCode then I recommend Yasuhiro Matsumoto’s Lisp plugin for syntax highlighting. For both Emacs and VSCode I usually run a separate REPL in a terminal window and don’t run an editor-integrated REPL. I think that I am in the minority in using a separate REPL running in a shell.
이 지침에서는 SBCL을 사용한다고 가정합니다. 이 섹션의 끝에 있는 LispWorks, Franz Common Lisp 및 클로저 공통 목록에 대한 설명을 참조하세요. 여기서는 lisp-lang.org/learn/getting-started의 안내에 따라 SBCL 및 Quicklisp를 설치했다고 가정합니다. 이 지침은 또한 Emacs용 Slime 확장 프로그램을 설치하는 방법도 안내합니다. 저는 커먼 리스프 편집을 위해 Emacs + Slime과 커먼 리스프 플러그인이 포함된 VSCode를 모두 사용합니다. VSCode가 마음에 드신다면 구문 강조 표시를 위해 Yasuhiro Matsumoto의 Lisp 플러그인을 추천합니다. Emacs와 VSCode 모두에서 저는 보통 터미널 창에서 별도의 REPL을 실행하고 편집기에 통합된 REPL은 실행하지 않습니다. 저는 셸에서 실행되는 별도의 REPL을 사용하는 소수에 속한다고 생각합니다.
I have been using Common Lisp since about 1982 and Quicklisp (developed and maintained by Zach Beane) has been the most revolutionary change in my Common Lisp development (even more so than getting a hardware Lisp Machine and the availability of Coral Common Lisp on the Macintosh).
저는 1982년부터 커먼 리스프를 사용해 왔는데, https://www.xach.com/[잭 빈]이 개발하고 유지 관리한 퀵리스프는 제 커먼 리스프 개발에서 가장 혁신적인 변화였습니다(하드웨어 리스프 머신을 구입하고 매킨토시에서 코랄 커먼 리스프를 사용할 수 있게 된 것보다 더 큰 변화였죠).
You can follow the directions on the main GitHub repository for this book: https://github.com/mark-watson/loving-common-lisp to get the examples set up to run on your computer. Starting with the 8th edition, I have a new scheme for distributing the book examples on GitHub:
- A few short example Common Lisp code snippets are still kept in the main repository for the book: https://github.com/mark-watson/loving-common-lisp.
- The longer examples are now stored in separate GitHub repositories to facilitate using them as reusable Quicklisp libraries.
- Clone the main GitHub repository and copy the Makefile to the directory ~/quicklisp/local-projects/ on your computer.
- Change directory to ~/quicklisp/local-projects/ and run the Makefile target make fetch to copy all separate GitHub repositories to subdirectories of ~/quicklisp/local-projects/.
- You can now load any book example using Quicklisp, for example: (ql:quickload :sparql).
For example, the subdirectory loving-common-lisp/src/spacy-py4cl contains a package named spacy-py4cl that can now be accessed from any directory on your system using:
This example uses the deep learning NLP models in spaCy which is a Python library - see the chapter on NLP for details on installing the Python dependencies. Note that only a few examples in this book require Python dependencies.
이 예제에서는 Python 라이브러리인 spaCy의 딥러닝 NLP 모델을 사용하며, Python 종속성 설치에 대한 자세한 내용은 NLP 챕터를 참조하세요. 이 책에 포함된 몇 가지 예제만 Python 종속성을 필요로 한다는 점에 유의하세요.
저는 이 책에서 Common Lisp의 SBCL 구현을 사용했습니다. 프란츠, LispWorks, 클로저 커먼 리스프 등의 훌륭한 커먼 리스프 구현이 많이 있습니다. 저는 보통 전문 개발 작업에는 LispWorks를 사용합니다.
Introduction
This book is intended to get you, the reader, programming quickly in Common Lisp. Although the Lisp programming language is often associated with artificial intelligence, this introduction is on general Common Lisp programming techniques. Later we will look at general example applications and artificial intelligence examples.
The Common Lisp program examples are distributed on the github repo for this book.
Why Did I Write this Book?
왜 "러빙 커먼 리스프"라는 제목을 붙였을까요? 간단합니다! 저는 거의 40년 동안 Lisp를 사용해 왔고, 프로그래밍 언어와 프로그래밍 작업 사이에 이보다 더 잘 어울리는 언어를 거의 찾지 못했습니다. 하지만 저는 Lisp에 완전히 매료된 것은 아닙니다. 저는 딥 러닝을 위해 Python을 자주 사용합니다. 서버 측 프로그래밍에는 루비, 자바, 자바스크립트를 좋아하고, 몇 년 동안 SAIC와 디즈니의 닌텐도 비디오 게임과 가상 현실 시스템을 개발하면서 엄격한 런타임 성능 요구 사항 때문에 C++가 좋은 선택이라는 것을 알았습니다. 일부 작업에서는 로직 프로그래밍 패러다임이 유용하다고 생각했습니다: 저는 프롤로그(Prolog) 언어도 즐겨 사용합니다.
어쨌든 저는 Lisp, 특히 업계 표준인 Common Lisp로 프로그래밍하는 것을 좋아합니다. 10년 전에 이 책의 두 번째 판을 썼을 때 저는 의료 회사의 인공 지능 프로젝트와 상용 제품 개발에만 거의 독점적으로 커먼 리스프를 사용하고 있었습니다. 이 책의 3판을 집필하는 동안에는 전문적으로 Common Lisp를 사용하지 않았지만 Quicklisp Common Lisp 패키지 관리자가 출시된 이후에는 소규모 사이드 프로젝트에 더 많이 사용하게 되었습니다. 3판 예제 코드에서는 필요한 라이브러리를 쉽게 설치할 수 있도록 전체적으로 Quicklisp를 사용했습니다. 이 책의 4판과 5판에서는 신경망과 딥러닝을 사용하는 예제를 더 많이 추가했습니다. 이번 6판에서는 사용자 인터페이스에 CAP를 사용하는 완전한 애플리케이션을 추가했습니다.
프로그래머로서 우리 모두는 흥미로운 문제를 해결하기 위해 자신의 경험과 두뇌를 적용하는 것을 즐깁니다. 최근 아내와 저는 2박 3일간 7시간 동안 방영된 PBS 스페셜 "조셉 캠벨, 그리고 신화의 힘"을 시청했습니다 거의 40년 동안 대학 교수로 재직했던 캠벨은 학생들에게 항상 "자신의 행복을 따르라"며 자신이 진정으로 하고 싶지 않은 직업이나 직업에 안주하지 말라고 조언했다고 합니다. 즉, 저는 항상 업무에서 Java, Python 또는 Lisp 이외의 다른 언어를 사용해야 할 때, 그 일을 통해 많은 즐거움을 얻을 수 있더라도 제 행복을 따르지 않는다고 느낍니다.
이 책의 목표는 제가 가장 좋아하는 프로그래밍 언어 중 하나인 커먼 리스프에 대해 소개하는 것입니다. 이미 다른 언어로 프로그래밍하는 방법을 알고 있다고 가정하지만, 완전히 초보자라도 약간의 노력만 기울이면 이 책의 내용을 마스터할 수 있습니다. 여러분도 노력해 보시기 바랍니다.
Free Software Tools for Common Lisp Programming
There are several Common Lisp compilers and runtime tools available for free on the web:
웹에서 무료로 사용할 수 있는 여러 가지 Common Lisp 컴파일러와 런타임 도구가 있습니다:
- CLISP – licensed under the GNU GPL and is available for Windows, Macintosh, and Linux/Unix
- Clozure Common Lisp (CCL) – open source with good Mac OS X and Linux support
- CMU Common Lisp – open source implementation
- SBCL – derived from CMU Common Lisp
- ECL – compiles using a separate C/C++ compiler
- ABCL – Armed Bear Common Lisp for the JVM
There are also fine commercial Common Lisp products:
- LispWorks – high quality and reasonably priced system for Windows and Linux. No charge for distributing compiled applications lispworks.com
- Allegro Common Lisp - high quality, great support and higher cost. franz.com
- MCL – Macintosh Common Lisp. I used this Lisp environment in the late 1980s. MCL was so good that I gave away my Xerox 1108 Lisp Machine and switched to a Mac and MCL for my development work. Now open source but only runs on the old MacOS
I currently (mostly) use SBCL, CCL, and LispWorks. The SBCL compiler produces very fast code and the compiler warning can be of great value in finding potential problems with your code. Like CCL because it compiles quickly so is often preferable for development.
저는 현재 (주로) SBCL, CCL, LispWorks를 사용하고 있습니다. SBCL 컴파일러는 매우 빠른 코드를 생성하며 컴파일러 경고는 코드의 잠재적인 문제를 찾는 데 큰 도움이 될 수 있습니다. 컴파일이 빠르기 때문에 개발에 선호되는 CCL도 마찬가지입니다.
For working through this book, I will assume that you are using SBCL or CCL. For the example in the last chapter you will need LispWorks and the free Personal edition is fine for the purposes of experimenting with the example application and the CAPI user interface library.
이 책에서 작업할 때는 SBCL 또는 CCL을 사용한다고 가정하겠습니다. 마지막 장의 예제에는 LispWorks가 필요하며 예제 애플리케이션과 CAPI 사용자 인터페이스 라이브러리를 실험하기 위한 목적으로는 무료 개인용 버전이 좋습니다.
Making Book Examples Run Portably on Most Common Lisp Implementations
대부분의 커먼 리스프 구현에서 책 예제를 이식 가능하게 실행하기
Making Book Examples Run Portably on Most Common Lisp Implementations
이 책의 많은 예제에서는 웹 서비스 호출이 필요합니다. 일반적으로 REST 호출이 필요한 Common Lisp 애플리케이션을 작성할 때는 Drakma 또는 Dexador와 같은 타사 Common Lisp 라이브러리를 사용하는 것을 선호합니다. 그러나 libopenssl, libcrypto 등을 사용하여 다양한 운영 체제 및 CPU 아키텍처에서 Common Lisp를 설정하는 것은 때때로 약간 까다로울 수 있습니다. 이 때문에 책 예제에서는 uiop:run-program*을 사용하여 외부 *curl 프로그램을 실행하고 출력을 문자열로 수집한 다음 JSON 또는 CSV 데이터로 파싱합니다. 외부 프로세스를 시작할 때의 오버헤드는 웹 서비스를 호출하는 것에 비해 매우 적으므로 자체 애플리케이션에서 *curl*을 사용하는 예제를 따르거나, Drakma 또는 Dexador 라이브러리를 사용할 수 있습니다. MacOS에서 Apple M1 프로세서를 사용하면 특히 OpenSSL 문제가 발생할 수 있습니다.
저는 또한 몇 가지 책 예제에서 훌륭한 Common Lisp-Python 브리지 라이브러리 *py4cl*을 사용합니다. 보통 *py4cl*은 문제 없이 설치됩니다.
How is Lisp Different from Languages like Java and C++?
Lisp는 Java 및 C++와 같은 언어와 어떻게 다른가요? 이것은 까다로운 질문입니다! Lisp는 자동화된 메모리 관리로 인해 C++보다 Java와 약간 더 유사하므로 Lisp와 Java를 비교하는 것부터 시작하겠습니다.
Java에서는 변수가 강력하게 유형화되는 반면, Common Lisp에서는 값이 강력하게 유형화됩니다. 예를 들어 Java 코드를 생각해 보세요:
여기서 Java에서는 변수가 강력하게 유형화되어 있으므로 5번째 줄의 코드가 컴파일 오류를 생성할 수 있으므로 Float 유형의 변수 x에 문자열 값을 할당할 수 없습니다.
Lisp 코드는 변수에 값을 할당하고 다른 유형의 다른 값을 다시 할당할 수 있습니다. Java와 Lisp는 모두 자동 메모리 관리 기능을 제공합니다. 어느 언어에서든 새 데이터 구조를 만들 수 있으며 데이터가 더 이상 사용되지 않거나 더 이상 참조되지 않을 때 메모리를 확보할 걱정을 하지 않아도 됩니다.
Common Lisp는 ANSI 표준 언어입니다. 서로 다른 Common Lisp 구현과 다른 플랫폼 간의 이식성이 매우 뛰어납니다. 저는 Windows, Mac OS X 및 Linux에서 모두 잘 실행되는 Clozure Common Lisp, SBCL, Allegro Lisp(Franz Inc의 제품), LispWorks 및 CLISP를 사용해 왔습니다. Common Lisp 개발자는 도구와 플랫폼의 유연성이 매우 뛰어납니다.
ANSI Common Lisp는 ANSI 표준 언어가 된 최초의 객체 지향 언어입니다. Common Lisp 객체 시스템(CLOS)은 객체 지향 프로그래밍을 위한 최고의 플랫폼입니다.
C++ 프로그램에서 프로그램의 효율성에 영향을 미치는 일반적인 버그는 더 이상 사용되지 않는 메모리를 해제하는 것을 잊어버리는 것입니다. 가상 메모리 시스템에서 프로그램의 메모리 사용량 증가는 일반적으로 시스템 성능 저하로 이어지지만, 사용 가능한 가상 메모리가 모두 소진되면 시스템 충돌이나 장애로 이어질 수 있습니다. 더 나쁜 유형의 C++ 오류는 메모리를 확보한 후 사용하려고 시도하는 것입니다. "프로그램 충돌"이라고 할 수 있나요? C 프로그램도 동일한 유형의 메모리 관련 오류가 발생합니다.
컴퓨터 처리 능력은 일반적으로 소프트웨어 개발 비용보다 훨씬 저렴하기 때문에 런타임 효율성을 몇 퍼센트 포기하고 런타임 라이브러리의 프로그래밍 환경이 메모리를 대신 관리하도록 하는 것이 거의 항상 가치가 있습니다. 리스프, 루비, 파이썬, 자바 같은 언어는 자동 가비지 컬렉션을 수행한다고 합니다.
저는 Java에 관한 책을 6권이나 썼고, 제게는 Java로 프로그래밍하는 것이 C++로 프로그래밍하는 것보다 (시간 측면에서) 약 2배 더 효율적이라는 말을 들은 적이 있습니다. 이 말은 SAIC, PacBell, Angel Studios, Nintendo, Disney의 프로젝트에서 약 10년간의 C++ 경험을 바탕으로 한 것입니다. 저는 Common Lisp와 Clojure 및 Scheme과 같은 다른 Lisp 언어가 Java보다 약 2배 더 효율적이라고 생각합니다(다시 말하지만, 제 시간 기준으로). 맞습니다. 저는 C++에 비해 Common Lisp를 사용할 때 프로그래밍 생산성이 4배 증가한다고 주장하고 있습니다.
프로그래밍 생산성이란 무엇을 의미하나요? 간단히 말해, 주어진 작업을 위해 소프트웨어를 설계, 코딩, 디버깅하고 나중에 유지 관리하는 데 걸리는 시간을 의미합니다.
Advantages of Working in a Lisp Environment
이 책의 시작 부분에서는 Lisp 프로그래밍의 기초를 소개합니다. Lisp는 단순한 언어가 아니라 프로그래밍 환경이자 런타임 환경이기도 합니다. 이후 장에서는 다른 언어와 프로그래밍 환경에서는 구현하기 어려운 흥미롭고 사소하지 않은 프로그램을 커먼 리스프에서 개발할 것입니다.
Lisp 환경에서 프로그래밍할 때 가장 큰 장점은 환경을 설정하고 대화형으로 새 코드를 작성하고 새 코드를 조금씩 테스트할 수 있다는 것입니다. 대량의 데이터를 사용한 프로그래밍은 [BROKEN LINK: nlp_chapter]에서 다루겠지만, 제가 하는 작업의 일반적인 사용 사례를 공유하여 Lisp에서 훨씬 더 효율적으로 작업할 수 있도록 하려고 합니다: 제 Lisp 프로그래밍의 대부분은 회사(www.knowledgebooks.com)에서 상용 자연어 처리(NLP) 프로그램을 작성하는 것이었습니다. 예를 들어 다양한 유형의 단어에 대한 해시 테이블, 텍스트 분류를 위한 해시 테이블, 지명(도시, 카운티, 강 등)에 대한 20만 개의 고유 명사, 다양한 국적의 약 4만 개의 일반적인 이름과 성 등 많은 양의 메모리 상주 데이터를 사용하는 Lisp NLP 코드가 있습니다.
If I was writing my NLP products in C++, I would probably use a relational database to store this data because if I read all of this data into memory for each test run of a C++ program, I would wait 30 seconds every time that I ran a program test. When I start working in any Common Lisp environment, I do have to load the linguistic data into memory one time, but then can code/test/code/test… for hours with no startup overhead for reloading the data that my programs need to run. Because of the interactive nature of Lisp development, I can test small bits of code when tracking down errors and when writing new code.
It is a personal preference, but I find the combination of the stable Common Lisp language and an iterative Lisp programming environment to be much more productive than other languages and programming environments.
Common Lisp Basics
The material in this chapter will serve as an introduction to Common Lisp. I have attempted to make this book a self contained resource for learning Common Lisp and to provide code examples to perform common tasks. If you already know Common Lisp and bought this book for the code examples later in this book then you can probably skip this chapter.
For working through this chapter we will be using the interactive shell, or repl, built into SBCL and other Common Lisp systems. For this chapter it is sufficient for you to download and install SBCL. Please install SBCL right now, if you have not already done so.
Getting Started with SBCL
When we start SBCL, we see an introductory message and then an input prompt. We will start with a short tutorial, walking you through a session using SBCL repl (other Common LISP systems are very similar). A repl is an interactive console where you type expressions and see the results of evaluating these expressions. An expression can be a large block of code pasted into the repl, using the load function to load Lisp code into the repl, calling functions to test them, etc. Assuming that SBCL is installed on your system, start SBCL by running the SBCL program:
We started by defining a new variable x in line 11. Notice how the value of the defvar macro is the symbol that is defined. The Lisp reader prints X capitalized because symbols are made upper case (we will look at the exception later).
In Lisp, a variable can reference any data type. We start by assigning a floating point value to the variable x, using the + function to add 1 to x in line 17, using the setq function to change the value of x in lines 23 and 29 first to another floating point value and finally setting x to a string value. One thing that you will have noticed: function names always occur first, then the arguments to a function. Also, parenthesis is used to separate expressions.
I learned to program Lisp in 1976 and my professor half-jokingly told us that Lisp was an acronym for “Lots-of Irritating Superfluous Parenthesis.” There may be some truth in this when you are just starting with Lisp programming, but you will quickly get used to the parenthesis, especially if you use an editor like Emacs that automatically indents Lisp code for you and highlights the opening parenthesis for every closing parenthesis that you type. Many other editors support coding in Lisp but I personally use Emacs or sometimes VScode (with Common Lisp plugins) to edit Lisp code.
Before you proceed to the next chapter, please take the time to install SBCL on your computer and try typing some expressions into the Lisp listener. If you get errors, or want to quit, try using the quit function:
If you get an error you can enter help to get options for handling an error. When I get an error and have a good idea of what caused the error then I just enter :a: to abort out of the error).
As we discussed in the introduction, there are many different Lisp programming environments that you can choose from. I recommend a free set of tools: Emacs, Quicklisp, slime, and SBCL. Emacs is a fine text editor that is extensible to work well with many programming languages and document types (e.g., HTML and XML). Slime is an Emacs extension package that greatly facilitates Lisp development. SBCL is a robust Common Lisp compiler and runtime system that is often used in production.
We will cover the Quicklisp package manager and using Quicklisp to setup Slime and Emacs in a later chapter.
I will not spend much time covering the use of Emacs as a text editor in this book since you can try most of the example code snippets in the book text by copying and then pasting them into a SBCL repl and by loading the book example source files directly into a repl. If you already use Emacs then I recommend that you do set up Slime sooner rather than later and start using it for development. If you are not already an Emacs user and do not mind spending the effort to learn Emacs, then search the web first for an Emacs tutorial. That said, you will easily be able to use the example code from this book using any text editor you like with a SBCL repl. I don’t use the vi or vim editors but if vi is your weapon of choice for editing text then a web search for “common lisp vi vim repl” should get you going for developing Common Lisp code with vi or vim. If you are not already an Emacs or vi user then using VSCode with a Common Lisp plugin is recommended.
Here, we will assume that under Windows, Unix, Linux, or Mac OS X you will use one command window to run SBCL and a separate editor that can edit plain text files.
Making the repl Nicer using rlwrap
While reading the last section you (hopefully!) played with the SBCL interactive repl. If you haven’t played with the repl, I won’t get too judgmental except to say that if you do not play with the examples as you read you will not get the full benefit from this book.
Did you notice that the backspace key does not work in the SBCL repl? The way to fix this is to install the GNU rlwrap utility. On OS X, assuming that you have homebrew installed, install rlwrap with:
If you are running Ubuntu Linux, install rlwrap using:
You can then create an alias for bash or zsh using something like the following to define a command rsbcl:
This is fine, just remember to run sbcl if you don’t need rlwrap command line editing or run rsbcl when you do need command line editing. That said, I find that I always want to run SBCL with command line editing, so I redefine sbcl on my computers using:
This alias is different on my laptops and servers, since I don’t usually install SBCL in the default installation directory. For each of my computers, I add an appropriate alias in my .zshrc file (if I am running zsh) or my .bashrc file (if I am running bash).
The Basics of Lisp Programming
Although we will use SBCL in this book, any Common Lisp environment will do fine. In previous sections, we saw the top-level Lisp prompt and how we could type any expression that would be evaluated:
Notice that when we defined the function my-add-one in lines 7 and 8, we split the definition over two lines and on line 8 you don’t see the “*” prompt from SBCL – this lets you know that you have not yet entered a complete expression. The top level Lisp evaluator counts parentheses and considers a form to be complete when the number of closing parentheses equals the number of opening parentheses and an expression is complete when the parentheses match. I tend to count in my head, adding one for every opening parentheses and subtracting one for every closing parentheses – when I get back down to zero then the expression is complete. When we evaluate a number (or a variable), there are no parentheses, so evaluation proceeds when we hit a new line (or carriage return).
The Lisp reader by default tries to evaluate any form that you enter. There is a reader macro ‘ that prevents the evaluation of an expression. You can either use the ‘ character or quote:
Lisp supports both global and local variables. Global variables can be declared using defvar:
One thing to be careful of when defining global variables with defvar: the declared global variable is dynamically scoped. We will discuss dynamic versus lexical scoping later, but for now a warning: if you define a global variable avoid redefining the same variable name inside functions. Lisp programmers usually use a global variable naming convention of beginning and ending dynamically scoped global variables with the * character. If you follow this naming convention and also do not use the * character in local variable names, you will stay out of trouble. For convenience, I do not always follow this convention in short examples in this book.
Lisp variables have no type. Rather, values assigned to variables have a type. In this last example, the variable x was set to a string, then to a floating-point number. Lisp types support inheritance and can be thought of as a hierarchical tree with the type t at the top. (Actually, the type hierarchy is a DAG, but we can ignore that for now.) Common Lisp also has powerful object oriented programming facilities in the Common Lisp Object System (CLOS) that we will discuss in a later chapter.
Here is a partial list of types (note that indentation denotes being a subtype of the preceding type):
We can use the typep function to test the type of value of any variable or expression or use type-of to get type information of any value):
A useful feature of all ANSI standard Common Lisp implementations’ top-level listener is that it sets * to the value of the last expression evaluated. For example:
All Common Lisp environments set * to the value of the last expression evaluated. This example may be slightly confusing because * is also the prompt character in the SBCL repl that indicates that you can enter a new expression for evaluation. For example in line 3, the first * character is the repl prompt and the second * we type in to see that value of the previous expression that we typed into the repl.
Frequently, when you are interactively testing new code, you will call a function that you just wrote with test arguments; it is useful to save intermediate results for later testing. It is the ability to create complex data structures and then experiment with code that uses or changes these data structures that makes Lisp programming environments so effective.
Common Lisp is a lexically scoped language that means that variable declarations and function definitions can be nested and that the same variable names can be used in nested let forms; when a variable is used, the current let form is searched for a definition of that variable and if it is not found, then the next outer let form is searched. Of course, this search for the correct declaration of a variable is done at compile time so there need not be extra runtime overhead. We should not nest defun special form inside each other or inside let expressions. Instead we use the special forms flet and labels to define functions inside a scoped environment. Functions defined inside a labels special form can be recursive while functions defined inside a flet special form cannot be recursive. Consider the following example in the file nested.lisp (all example files are in the src directory):
We define a top level flet special form in lines 1-5 that defines two nested functions add-one and add-two and then calls each nested function in the body of the flet special form. For many years I have used nested defun special forms inside let expressions for defining local functions but I now try to avoid doing this. Functions defined inside defun special forms have global visibility so they are not hidden in the local context where they are defined. The example of a nested defun in lines 7-12 shows that the function test2 has global visibility inside the current package.
Functions defined inside of a flet special form have access to variables defined in the outer scope containing the flet (also applies to labels). We see this in lines 14-24 where the local variables x and y defined in the let expression are visible inside the function nested-function defined inside the flet.
The final example in lines 26-31 shows a recursive function defined inside a labels special form.
Assuming that we started SBCL in the src directory we can then use the Lisp load function to evaluate the contents of the file nested.lisp in the sub-directory code_snippets_for_book using the load function:
The function load returned a value of t (prints in upper case as T) after successfully loading the file.
We will use Common Lisp vectors and arrays frequently in later chapters, but will also briefly introduce them here. A singly dimensioned array is also called a vector. Although there are often more efficient functions for handling vectors, we will just look at generic functions that handle any type of array, including vectors. Common Lisp provides support for functions with the same name that take different argument types; we will discuss this in some detail when we cover this in the later chapter on CLOS. We will start by defining three vectors v1, v2, and v3:
In line 1, we are defining a one-dimensional array, or vector, with three elements. In line 3 we specify the default value assigned to each element of the array v2. In line 5 I use the form for specifying array literals using the special character #. The function aref can be used to access any element in an array:
Notice how indexing of arrays is zero-based; that is, indices start at zero for the first element of a sequence. Also notice that array elements can be any Lisp data type. So far, we have used the special operator setq to set the value of a variable. Common Lisp has a generalized version of setq called setf that can set any value in a list, array, hash table, etc. You can use setf instead of setq in all cases, but not vice-versa. Here is a simple example:
When writing new code or doing quick programming experiments, it is often easiest (i.e., quickest to program) to use lists to build interesting data structures. However, as programs mature, it is common to modify them to use more efficient (at runtime) data structures like arrays and hash tables.
Symbols
We will discuss symbols in more detail the [BROKEN LINK: package_system]. For now, it is enough for you to understand that symbols can be names that refer to variables. For example:
Note that the first defvar returns the defined symbol as its value. Symbols are almost always converted to upper case. An exception to this “upper case rule” is when we define symbols that may contain white space using vertical bar characters:
Operations on Lists
Lists are a fundamental data structure of Common Lisp. In this section, we will look at some of the more commonly used functions that operate on lists. All of the functions described in this section have something in common: they do not modify their arguments.
In Lisp, a cons cell is a data structure containing two pointers. Usually, the first pointer in a cons cell will point to the first element in a list and the second pointer will point to another cons representing the start of the rest of the original list.
The function cons takes two arguments that it stores in the two pointers of a new cons data structure. For example:
The first form evaluates to a cons data structure while the second evaluates to a cons data structure that is also a proper list. The difference is that in the second case the second pointer of the freshly created cons data structure points to another cons cell.
First, we will declare two global variables l1 and l2 that we will use in our examples. The list l1 contains five elements and the list l2 contains four elements:
You can also use the function list to create a new list; the arguments passed to function list are the elements of the created list:
The function car returns the first element of a list and the function cdr returns a list with its first element removed (but does not modify its argument):
Using combinations of car and cdr calls can be used to extract any element of a list:
Notice that we can combine calls to car and cdr into a single function call, in this case the function cadr. Common Lisp defines all functions of the form cXXr, cXXXr, and cXXXXr where X can be either a or d.
Suppose that we want to extract the value 5 from the nested list l1. Some experimentation with using combinations of car and cdr gets the job done:
The function last returns the last cdr of a list (i.e., the last element, in a list):
Common list supplies alternative functions to car and cdr that you might find more readable: first, second, third, fourth, and rest. Here are some examples:
The function nth takes two arguments: an index of a top-level list element and a list. The first index argument is zero based:
The function cons adds an element to the beginning of a list and returns as its value a new list (it does not modify its arguments). An element added to the beginning of a list can be any Lisp data type, including another list:
The function append takes two lists as arguments and returns as its value the two lists appended together:
A frequent error that beginning Lisp programmers make is not understanding shared structures in lists. Consider the following example where we generate a list y by reusing three copies of the list x:
When we change the shared structure referenced by the variable x that change is reflected three times in the list y. When we create the list stored in the variable z we are not using a shared structure.
Using Arrays and Vectors
Using lists is easy but the time spent accessing a list element is proportional to the length of the list. Arrays and vectors are more efficient at runtime than long lists because list elements are kept on a linked-list that must be searched. Accessing any element of a short list is fast, but for sequences with thousands of elements, it is faster to use vectors and arrays.
By default, elements of arrays and vectors can be any Lisp data type. There are options when creating arrays to tell the Common Lisp compiler that a given array or vector will only contain a single data type (e.g., floating point numbers) but we will not use these options in this book.
Vectors are a specialization of arrays; vectors are arrays that only have one dimension. For efficiency, there are functions that only operate on vectors, but since array functions also work on vectors, we will concentrate on arrays. In the next section, we will look at character strings that are a specialization of vectors.
We could use the generalized make-sequence function to make a singularly dimensioned array (i.e., a vector). Restart sbcl and try:
In this example, notice the print format for vectors that looks like a list with a proceeding # character. As seen in the last section, we use the function make-array to create arrays:
Notice the print format of an array: it looks like a list proceeded by a # character and the integer number of dimensions.
Instead of using make-sequence to create vectors, we can pass an integer as the first argument of make-array instead of a list of dimension values. We can also create a vector by using the function vector and providing the vector contents as arguments:
The function aref is used to access sequence elements. The first argument is an array and the remaining argument(s) are array indices. For example:
Using Strings
It is likely that even your first Lisp programs will involve the use of character strings. In this section, we will cover the basics: creating strings, concatenating strings to create new strings, for substrings in a string, and extracting substrings from longer strings. The string functions that we will look at here do not modify their arguments; rather, they return new strings as values. For efficiency, Common Lisp does include destructive string functions that do modify their arguments but we will not discuss these destructive functions here.
We saw earlier that a string is a type of vector, which in turn is a type of array (which in turn is a type of sequence). A full coverage of the Common Lisp type system is outside the scope of this tutorial introduction to Common Lisp; a very good treatment of Common Lisp types is in Guy Steele’s “Common Lisp, The Language” which is available both in print and for free on the web. Many of the built in functions for handling strings are actually more general because they are defined for the type sequence. The Common Lisp Hyperspec is another great free resource that you can find on the web. I suggest that you download an HTML version of Guy Steele’s excellent reference book and the Common Lisp Hyperspec and keep both on your computer. If you continue using Common Lisp, eventually you will want to read all of Steele’s book and use the Hyperspec for reference.
The following text was captured from input and output from a Common Lisp repl. First, we will declare two global variables s1 and space that contain string values:
One of the most common operations on strings is to concatenate two or more strings into a new string:
Notice that the first argument of the function concatenate is the type of the sequence that the function should return; in this case, we want a string. Another common string operation is search for a substring:
If the search string (first argument to function search) is not found, function search returns nil, otherwise search returns an index into the second argument string. Function search takes several optional keyword arguments (see the next chapter for a discussion of keyword arguments):
For our discussion, we will just use the keyword argument :start2 for specifying the starting search index in the second argument string and the :from-end flag to specify that search should start at the end of the second argument string and proceed backwards to the beginning of the string:
The sequence function subseq can be used for strings to extract a substring from a longer string:
Here, the second argument specifies the starting index; the substring from the starting index to the end of the string is returned. An optional third index argument specifies one greater than the last character index that you want to extract:
It is frequently useful to remove white space (or other) characters from the beginning or end of a string:
The character #\space is the space character. Other common characters that are trimmed are #\tab and #\newline. There are also utility functions for making strings upper or lower case:
We have not yet discussed equality of variables. The function eq returns true if two variables refer to the same data in memory. The function eql returns true if the arguments refer to the same data in memory or if they are equal numbers or characters. The function equal is more lenient: it returns true if two variables print the same when evaluated. More formally, function equal returns true if the car and cdr recursively equal to each other. An example will make this clearer:
For strings, the function string= is slightly more efficient than using the function equal:
Common Lisp strings are sequences of characters. The function char is used to extract individual characters from a string:
Using Hash Tables
Hash tables are an extremely useful data type. While it is true that you can get the same effect by using lists and the assoc function, hash tables are much more efficient than lists if the lists contain many elements. For example:
The second argument to function assoc is a list of cons cells. Function assoc searches for a sub-list (in the second argument) that has its car (i.e., first element) equal to the first argument to function assoc. The perhaps surprising thing about this example is that assoc seems to work with an integer as the first argument but not with a string. The reason for this is that by default the test for equality is done with eql that tests two variables to see if they refer to the same memory location or if they are identical if they are numbers. In the last call to assoc we used “:test #’equal” to make assoc use the function equal to test for equality.
The problem with using lists and assoc is that they are very inefficient for large lists. We will see that it is no more difficult to code with hash tables.
A hash table stores associations between key and value pairs, much like our last example using the assoc function. By default, hash tables use eql to test for equality when looking for a key match. We will duplicate the previous example using hash tables:
Notice that gethash returns multiple values: the first value is the value matching the key passed as the first argument to function gethash and the second returned value is true if the key was found and nil otherwise. The second returned value could be useful if hash values are nil.
Since we have not yet seen how to handle multiple returned values from a function, we will digress and do so here (there are many ways to handle multiple return values and we are just covering one of them):
Assuming that variables a and b are already declared, the variable a will be set to the first returned value from gethash and the variable b will be set to the second returned value.
If we use symbols as hash table keys, then using eql for testing for equality with hash table keys is fine:
However, we saw that eql will not match keys with character string values. The function make-hash-table has optional key arguments and one of them will allow us to use strings as hash key values:
Here, we are only interested in the first optional key argument :test that allows us to use the function equal to test for equality when matching hash table keys. For example:
It is often useful to be able to enumerate all the key and value pairs in a hash table. Here is a simple example of doing this by first defining a function my-print that takes two arguments, a key and a value. We can then use the maphash function to call our new function my-print with every key and value pair in a hash table:
The function my-print is applied to each key/value pair in the hash table. There are a few other useful hash table functions that we demonstrate here:
The function hash-table-count returns the number of key and value pairs in a hash table. The function remhash can be used to remove a single key and value pair from a hash table. The function clrhash clears out a hash table by removing all key and value pairs in a hash table.
It is interesting to note that clrhash and remhash are the first Common Lisp functions that we have seen so far that modify any of its arguments, except for setq and setf that are macros and not functions.
Using Eval to Evaluate Lisp Forms
We have seen how we can type arbitrary Lisp expressions in the Lisp repl listener and then they are evaluated. We will see in the [BROKEN LINK: input_output] that the Lisp function read evaluates lists (or forms) and indeed the Lisp repl uses function read.
In this section, we will use the function eval to evaluate arbitrary Lisp expressions inside a program. As a simple example:
Using the function eval, we can build lists containing Lisp code and evaluate generated code inside our own programs. We get the effect of “data is code”. A classic Lisp program, the OPS5 expert system tool, stored snippets of Lisp code in a network data structure and used the function eval to execute Lisp code stored in the network. A warning: the use of eval is likely to be inefficient in non-compiled code. For efficiency, the OPS5 program contained its own version of eval that only interpreted a subset of Lisp used in the network.
Using a Text Editor to Edit Lisp Source Files
I usually use Emacs, but we will briefly discuss the editor vi also. If you use vi (e.g., enter “vi nested.lisp”) the first thing that you should do is to configure vi to indicate matching opening parentheses whenever a closing parentheses is typed; you do this by typing “:set sm” after vi is running.
If you choose to learn Emacs, enter the following in your .emacs file (or your _emacs file in your home directory if you are running Windows):
Now, whenever you open a file with the extension of “lisp”, “lsp”, or “cl” (for “Common Lisp”) then Emacs will automatically use a Lisp editing mode. I recommend searching the web using keywords “Emacs tutorial” to learn how to use the basic Emacs editing commands - we will not repeat this information here.
I do my professional Lisp programming using free software tools: Emacs, SBCL, Clozure Common Lisp, and Clojure. I will show you how to configure Emacs and Slime in the last section of the [BROKEN LINK: quicklisp].
Recovering from Errors
When you enter forms (or expressions) in a Lisp repl listener, you will occasionally make a mistake and an error will be thrown. Here is an example where I am not showing all of the output when entering help when an error is thrown:
Here, I first used the backtrace command :bt to print the sequence of function calls that caused the error. If it is obvious where the error is in the code that I am working on then I do not bother using the backtrace command. I then used the abort command :a to recover back to the top level Lisp listener (i.e., back to the greater than prompt). Sometimes, you must type :a more than once to fully recover to the top level greater than prompt.
Garbage Collection
Like other languages like Java and Python, Common Lisp provides garbage collection (GC) or automatic memory management.
In simple terms, GC occurs to free memory in a Lisp environment that is no longer accessible by any global variable (or function closure, which we will cover in the next chapter). If a global variable variable-1 is first set to a list and then if we later then set variable-1 to, for example nil, and if the data referenced in the original list is not referenced by any other accessible data, then this now unused data is subject to GC.
In practice, memory for Lisp data is allocated in time ordered batches and ephemeral or generational garbage collectors garbage collect recent memory allocations far more often than memory that has been allocated for a longer period of time.
Loading your Working Environment Quickly
When you start using Common Lisp for large projects, you will likely have many files to load into your Lisp environment when you start working. Most Common Lisp implementations have a function called defsystem that works somewhat like the Unix make utility. While I strongly recommend defsystem for large multi-person projects, I usually use a simpler scheme when working on my own: I place a file loadit.lisp in the top directory of each project that I work on. For any project, its loadit.lisp file loads all source files and initializes any global data for the project.
The last two chapters of this book provide example applications that are configured to work with Quicklisp, which we will study in the next chapter.
Another good technique is to create a Lisp image containing all the code and data for all your projects. There is an example of this in the first section of the [BROKEN LINK: nlp_chapter]. In this example, it takes a few minutes to load the code and data for my NLP (natural language processing) library so when I am working with it I like to be able to quickly load a SBCL Lisp image.
All Common Lisp implementations have a mechanism for dumping a working image containing code and data.
Functional Programming Concepts
There are two main styles for doing Common Lisp development. Object oriented programming is well supported (see the [BROKEN LINK: clos_chapter]) as is functional programming. In a nut shell, functional programming means that we should write functions with no side effects. First let me give you a non-functional example with side effects:
This example using CLOS is non-functional because we modify the value of an argument to the function. Some functional languages like the Lisp Clojure language and the Haskell language dissuade you from modifying arguments to functions. With Common Lisp you should make a decision on which approach you like to use.
Functional programming means that we avoid maintaining state inside of functions and treat data as immutable (i.e., once an object is created, it is never modified). We could modify the last example to be function by creating a new car object inside the function, copy the attributes of the car passed as an object, change the color to “red” of the new car object, and return the new car instance as the value of the function.
Functional programming prevents many types of programming errors, makes unit testing simpler, and makes programming for modern multi-core CPUs easier because read-only objects are inherently thread safe. Modern best practices for the Java language also prefer immutable data objects and a functional approach.
TODO WAITING
아래에서 가져 올 것!
- 파일: Read Loving Common Lisp, or the Savvy Programmer's Secret Weapon
Related-Notes
- @마크왓슨 #개인지식관리 #시맨틱웹
- #마크왓슨: #하스켈 #튜토리얼
- #마크왓슨: #하이랭: #파이썬 #리스프 #튜토리얼
- 2024-06-20
- 2024-07-12
- #커먼리스프
- #원서
- #튜토리얼
- #인박스제로: #고민 #흔적 #아이디어 #정리필요
- #강좌: #인공지능 #자연어처리 #커먼리스프
- #인공지능 #커먼리스프
- #튜토리얼: #조직모드 배움의길
- #활용법 #가이드 #매뉴얼 #튜토리얼 #용어 #차이
References
마크 왓슨. 2023. 마크왓슨 AI 클로저 프로그래밍. Leanpub. https://leanpub.com/clojureai/read.
———. 2024. 마크왓슨 커먼리스프 프로그래밍. https://leanpub.com/lovinglisp/read.
———. (2021) 2024. “Mark-Watson/Clojure-AI-Book-Code.” https://github.com/mark-watson/Clojure-AI-Book-Code.