LYnLab

소개블로그취미로그

[수업정리] LISP의 기초

LISP은 LIST Programming의 약자로, 1958년 MIT에서 개발된 역사 깊은 함수형 언어이다. 모든 자료는 연결 리스트로 처리하며, 컴파일 개념 없이 인터프리터 상에서 동작한다.

2015-10-09#프로그래밍

💡 이 글은 작성된지 1년 이상 지났습니다. 정보글의 경우 최신 내용이 아닐 수 있음에 유의해주세요.

개요

LISP은 LIST Programming의 약자로, 1958년 MIT에서 개발된 역사 깊은 함수형 언어이다. 모든 자료는 연결 리스트로 처리하며, 컴파일 개념 없이 인터프리터 상에서 동작한다.

함수형 언어

함수형 언어(Functional Language)는 모든 연산 작업을 함수의 호출을 통해 수행하는 언어이다. Java에서 모든 변수가 하나의 객체이듯이, 함수형 언어는 모든 변수를 함수의 리턴으로 정의한다. 순차적 프로그래밍이나 OOP 이전에 등장한 개념이므로, 순차나 객체에 대한 개념은 가지고 있지 않다.

기능을 함수 단위로 구현함을 통해 빠르고 직관적으로 프로토타입을 만들 수 있다. 하지만 모든 연산을 함수 호출을 통해 처리하므로 연산 속도가 느리고 효율이 다소 떨어진다.

LISP의 사투리

LISP은 명확한 표준이 존재하지 않아 여러 사투리가 생겨났다. 이러한 사투리들을 하나로 표준화시키기 위해 등장한 것이 Common LISP인데 지금부터 작성하는 모든 코드들은 Common LISP의 문법을 따를 것이다. Common LISP은 다른 사투리와는 달리 절차적/객체지향적 프로그래밍을 지원하기는 하나, 그닥 권장되지는 않는다.

Common LISP의 기본

기본 명령어

  • :pwd - (Print Working Directory; UNIX와 동일) 현재 작업중인 디렉토리 보기
  • :cd - (Change Directory; UNIX와 동일) 디렉토리 이동
  • :ld filename - (LOAD) 소스코드를 불러오기, 확장자는 생략

기본 자료형과 연산

자료형의 단위는 아톰리스트 두 가지이다. 아톰은 숫자가 문자열 등의 다양한 데이터 구조가 동적 자료형으로 구성된 것이다. 리스트는 아톰이나 리스트를 구성 요소로 가진다. 즉, 리스트 자체는 재귀적으로 구성이 가능하다.

LISP은 기본적으로 모든 연산식을 전위로 표현한다. 아래는 사칙 연산을 LISP으로 구현하는 예시이다.

(+ 2 3) → 5
(* 4 5 2) → 40
(* 2 (+ 2 4)) → 12

그 밖에도 exp, expt, log, sqrt, sin, cos, tan, max, min, abs, mod 등 이름만 들어도 직관적으로 무슨 기능인지 파악할 수 있는 수학 연산 함수들이 대거 내장되어있다.

비교 연산

참은 T, 거짓을 nil을 통해 표현한다. 대부분의 언어와 마찬가지로 등호(=), 부등호(>, <), 등호가 포함된 부등호(<=, >=)를 사용한다.

(< 3 5) → T
(< 1 2.5 3) → T

자료형 관련 함수

  • ATOM : 피연산자가 아톰인지 확인한다
  • NULL : 피연산자가 nil인지 확인한다.
  • LISTP : 피연산자가 리스트이거나 nil인지 확인한다.
  • NUMPERP : 피연산자가 숫자인지 확인한다.

함수

내장 함수

  • CAR : 리스트의 첫 번째 element를 반환
  • CDR : 리스트의 첫 번째를 제외한 element를 반환
  • CADR : 리스트의 두 번째 element를 반환
  • CADDR : 리스트의 세 번째 element를 반환
    • D의 개수가 계속 늘어남에 따라 진행 가능
  • NTH : 리스트의 n번 째 element를 반환
  • CONS : 아톰과 리스트를 병합. (리스트와 리스트의 병합이 아님에 주의!)
    • 예) (CONS 1 (CONS 2 nil)) → (1 2)
    • 예) (CONS '(A) '(B C)) → ((A) B C)
  • LIST : 여러 아톰을 병합하여 리스트를 생성
  • APPEND : 리스트를 병합
  • LENGTH : 리스트의 element 개수 반환
  • MEMBER : 리스트에 해당하는 element가 있는지 판별.
    • 존재하면 해당 element부터 마지막까지 반환
    • 존재하지 않으면 nil을 반환

사용자 정의 함수

우선 문법은 다음과 같다.

(DEFUN *functiontame* (*param1 param2 ... paramN*) (*연산 내용*))

아래와 같이 사용 예를 보자.

(DEFUN three () 3)
(three) → 3

(DEFUN square (x) (* x x))
(square 5) → 25

(DEFUN sum (x y) (+ x y))
(sum 12 4) → 16

조건문

COND

타 언어에서의 switch-case 구문과 매우 유사한 역할을 한다.

(COND (state1 exp1) (state2 exp2) ... expN)

state1이 T이면 exp1의 연산을 실행한다. 만약 그렇지 않다면, state2를 확인한 후 이것이 T이면 exp2 연산을 실행한다. 이러한 과정을 반복하여, 모든 state가 거짓일 경우 가장 마지막의 expN을 실행한다.

(COND
    ((= 1 2) 5)
    ((= 2 2) (+ 4 2))
    ((= 3 2) (sqrt 4))
    (CAR '(NONE NO))
)
→ 6

IF문

마찬가지로 다른 언어의 IF문과 동일하다. 하지만 LISP에서는 대부분 COND문을 사용한다.

(IF state exp1 exp2)

만약 state가 참이면 exp1을, 아니면 exp2를 실행하게 된다.

(IF (> 4 2) (min 2 3) (max 2 3))
→ 2

그 밖의 문법

PROGN (순차 수행문)

(PROGN exp1 exp2 ... expN)

exp1부터 expN까지를 순차적으로 수항하고, expN의 결과값만 반환한다.

출력

출력에는 PRINT, PRINC 두 가지가 있다. 두 함수는 모두 출력한 값을 반환하므로 결과적으로 인터프리터에는 값이 두 번 나오게 된다. PRINC는 사실상 PRINT와 기능은 똑같으나, 문자열을 출력할 때 따옴표를 제거하여 출력한다는 차이점이 있다.

입력

(READ)

문자열 처리

문자열의 형식은 FORMAT을 이용해 처리한다.

(FORMAT nil "~$" (* 2 PI)) → "6.28"
(FORMAT nil "~:d" 10000) → "10,000"

(FORMAT t "~$ is ~$" 'PI 3.14) → "PI is 3.14"를 출력
(FORMAT t "~#$ ~$" PI PI) → "3.14 3.14"를 출력

TRACE

함수의 호출 과정을 추적, 출력한다.

관련된 글

Rails와 GitHub Actions에 커버리지 레포트를 달아보자

이 블로그의 CMS이기도 한 Shiori를 대폭 리팩토링하면서 테스트가 얼마나 잘 작성되어있는지 궁금해졌습니다.

Rails Global ID로 전역 객체 식별하기

Global ID는 Rails의 모든 객체를 식별할 수 있는 URI(Uniform Resource Identifier)입니다.

Ruby on WebAssembly: 살짝 맛보기

Ruby 3.2에 추가된 WebAssembly 지원을 간단하게 테스트해봅시다.

작성한 댓글은 giscus를 통해 GitHub Discussion에 저장됩니다.

크리에이티브 커먼즈 라이선스크리에이티브 커먼즈 저작자표시크리에이티브 커먼즈 동일조건변경허락

본 사이트의 저작물은 별도의 언급이 없는 한 크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.

© 2011 - 2024 Hoerin Doh, All rights reserved.

LYnLab 로고About MeGitHubTwitterInstagram