maiden 은 채팅 서버와 상호 작용하는 애플리케이션 및 라이브러리를 구축하는 데 도움이 되는 시스템 모음입니다. 채팅 봇이나 일반 채팅 클라이언트를 구축하는 데 도움이 될 수 있습니다. 또한 새로운 채팅 프로토콜을 위한 클라이언트 작성을 훨씬 쉽게 해주는 다양한 부분을 제공합니다.
maiden 사용하여 일종의 봇을 설정하는 데에만 관심이 있다면 그렇게 하는 단계는 다소 간단합니다. 먼저 maiden 과 봇에서 사용하려는 모든 모듈 및 구성 요소를 로드하려고 합니다.
(ql:quickload '( maiden maiden -irc maiden -commands maiden -silly))
그런 다음 원하는 대로 소비자 인스턴스를 추가하여 코어를 생성합니다.
(defvar *core* ( maiden :make-core
'(: maiden -irc :nickname " maiden Test" :host "irc.freenode.net" :channels ("##testing"))
: maiden -commands
: maiden -silly))
make-core 명령은 추가할 소비자의 패키지 이름(문자열 또는 기호) 또는 소비자의 직접 클래스 이름을 사용합니다. 전자의 경우 자체적으로 적절한 소비자 클래스 이름을 찾으려고 시도합니다.
그리고 그게 다입니다. make-core
코어를 생성하고, 모든 소비자를 인스턴스화하고, 여기에 추가하고, 모든 것을 시작합니다. maiden 에 제공되는 많은 모듈은 일종의 구성이나 영구 저장소를 활용합니다. 해당 관리에 대해서는 스토리지 하위 시스템을 참조하세요.
maiden 프레임워크로 사용하려면 먼저 프로젝트에 평소와 같이 자신의 시스템과 패키지를 정의해야 합니다. 지금은 maiden -user
패키지를 사용하여 실험해 보겠습니다. 다음으로 소비자를 정의하겠습니다. 이는 define-consumer
사용하여 수행할 수 있습니다.
(in-package #: maiden -user)
(define-consumer ping-notifier (agent)
())
일반적으로 에이전트를 정의하려고 합니다. 에이전트는 코어에 한 번만 존재할 수 있습니다. 나중에 클라이언트에 대한 예를 살펴보겠습니다. 이제부터 일반 CLOS 프로그래밍에 익숙했던 것처럼 소비자 클래스를 전문화하거나 이에 따라 작동하는 고유한 메서드와 함수를 정의할 수 있습니다. 다음으로 시스템에 "ping 요청"을 보내는 데 사용할 자체 이벤트를 정의하겠습니다.
(define-event ping (passive-event)
())
이벤트는 수행할 작업을 직접 요청하는 것이 아니라 발생 중인 ping을 시스템에 알리기 때문에 passive-event
로 정의됩니다. 이제 실제로 소비자가 이벤트 시스템과 상호 작용하도록 하기 위해 핸들러도 정의해야 합니다. 이는 define-handler
사용하여 수행할 수 있습니다.
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
이는 ping-notifier
소비자에 ping-receiver
라는 핸들러를 정의합니다. 또한 ping
유형의 이벤트를 수신하도록 지정합니다. 이후 arglist는 소비자 인스턴스가 c
에 바인딩되고 이벤트 인스턴스가 ev
에 바인딩되어 있음을 나타냅니다. 그런 다음 본문은 Verbose를 사용하여 정보 메시지를 기록합니다.
이것을 아주 빨리 테스트해 봅시다.
(defvar *core* (make-core 'ping-notifier))
(do-issue *core* ping)
그러면 예상대로 REPL에 상태 메시지가 인쇄됩니다. 이것이 바로 이 시스템을 사용하는 데 필요한 모든 것입니다. 실제로 유용한 작업을 수행하려면 maiden 프로젝트가 코어 외에 제공하는 기존 하위 시스템 중 일부를 활용하고 싶을 수도 있습니다. 이는 사용자, 채널, 계정, 명령, 네트워킹, 스토리지 등에 도움이 될 것입니다. 또한 핸들러에 대한 표현식 필터링과 같이 Deeds가 자체적으로 제공하는 기능도 사용할 수 있다는 점을 명심하세요.
이제 원시적인 종류의 클라이언트를 살펴보겠습니다. 클라이언트는 이벤트를 통해 간단히 파일에 쓸 수 있습니다.
(define-consumer file-client (client)
((file :initarg :file :accessor file))
(:default-initargs :file (error "FILE required.")))
(define-event write-event (client-event active-event)
((sequence :initarg :sequence))
(:default-initargs :sequence (error "SEQUENCE required.")))
write-event
우리가 쓰고자 하는 클라이언트에 특정해야 하기 때문에 client-event
만들었고, 뭔가 발생하도록 요청하기 때문에 active-event
로 만들었습니다. 이제 실제로 파일에 시퀀스를 쓰는 작업을 처리하는 핸들러를 정의해 보겠습니다.
(define-handler (file-client writer write-event) (c ev sequence)
:match-consumer 'client
(with-open-file (stream (file c) :direction :output :if-exists :append :if-does-not-exist :create)
(write-sequence sequence stream)))
:match-consumer
옵션은 필터가 현재 핸들러 인스턴스와 동일한 file-client
인스턴스를 포함하는 client
슬롯의 이벤트만 전달하는 방식으로 핸들러의 필터를 수정합니다. file-client
의 각 인스턴스가 코어에서 자체 핸들러 인스턴스를 수신하므로 이는 중요합니다. 이 옵션이 없으면 write-event
는 이벤트가 의도된 인스턴스에 관계없이 file-client
의 모든 인스턴스에서 처리됩니다. 또한 핸들러의 arglist에 sequence
인수를 추가했다는 점에 유의하세요. 이 인수는 이벤트의 적절한 슬롯으로 채워집니다. 해당 슬롯을 찾을 수 없으면 오류가 표시됩니다.
테스트해 볼 시간입니다. 위에서부터 코어를 재사용하겠습니다.
(add-to-core *core* '(file-client :file "~/foo" :name :foo)
'(file-client :file "~/bar" :name :bar))
(do-issue *core* write-event :sequence "foo" :client (consumer :foo *core*))
(do-issue *core* write-event :sequence "bar" :client (consumer :bar *core*))
(alexandria:read-file-into-string "~/foo") ; => "foo"
(alexandria:read-file-into-string "~/bar") ; => "bar"
보시다시피 이벤트는 우리가 원하는 클라이언트에 따라 적절한 핸들러 인스턴스로 전달되었으며 따라서 파일에는 우리가 기대하는 내용이 포함되어 있습니다.
마지막으로 런타임에 핸들러를 동적으로 추가 및 제거할 수도 있으며 특정 소비자와 연결되지 않은 핸들러에 대해서도 그렇게 할 수 있다는 점을 언급할 가치가 있습니다. 이는 어딘가에서 응답 이벤트를 기다려야 할 때 종종 유용합니다. 이 작업을 비동기적으로 처리하고 명령형 흐름의 느낌을 유지하기 위해 maiden Deeds와 마찬가지로 with-awaiting
매크로를 제공합니다. 다음과 같이 사용할 수 있습니다.
(with-awaiting (core event-type) (ev some-field)
(do-issue core initiating-event)
:timeout 20
some-field)
with-awaiting
이름을 취하지 않고 처음에 소비자 이름 대신 코어 또는 소비자 인스턴스가 필요하다는 점을 제외하면 define-handler
와 매우 유사합니다. 또한 사용되지 않는 추가 옵션인 :timeout
필요합니다. 또 다른 필수 추가 사항은 arglist 뒤의 "설정 양식"입니다. 모든 것을 적절하게 관리하고 시스템에서 경쟁 조건이 발생하지 않도록 하려면 이 설정 양식에서 최종 응답 이벤트를 표시하는 프로세스를 시작해야 합니다. 그 전에 시작하면 임시 핸들러가 시스템에 설정되기 전에 응답 이벤트가 전송될 수 있으며 전혀 도착하지 않은 것처럼 나타날 수 있습니다.
이것이 거의 모든 기본 사항입니다. 위에서 언급했듯이 이 프로젝트에 포함된 하위 시스템을 살펴보십시오. 이는 채팅 시스템 등과 관련된 모든 종류의 일반적인 작업 및 문제를 해결하는 데 도움이 될 것입니다.
maiden 이해하기 전에, 비록 표면적 수준에서라도 행위를 이해하는 것이 가치가 있습니다. maiden 그것을 기반으로 꽤 무겁게 짓는다.
core
는 maiden 구성의 중심 부분입니다. 시스템의 다른 구성 요소를 관리하고 조정하는 일을 담당합니다. 동일한 lisp 이미지 내에서 여러 코어를 동시에 실행할 수 있으며, 코어 간에 구성 요소를 공유할 수도 있습니다.
보다 구체적으로 말하면 Core는 이벤트 루프와 일련의 소비자로 구성됩니다. 이벤트 루프는 이벤트를 핸들러에 전달하는 역할을 합니다. 소비자는 이벤트 루프에 핸들러를 연결하는 일을 담당합니다. 따라서 코어에서 수행할 가능성이 가장 높은 작업은 다음과 같습니다. issue
통해 이벤트 발행, add-consumer
를 통해 소비자 추가, remove-consumer
통해 코어에서 소비자 제거.
소비자가 추가된 유용한 코어를 더 쉽게 생성하기 위해 make-core
및 add-to-core
기능을 사용할 수 있습니다.
event
는 시스템의 변화를 나타내는 개체입니다. 이벤트는 발생한 변경을 나타내거나 변경이 발생하도록 요청하는 데 사용될 수 있습니다. 이를 각각 passive-event
및 active-event
라고 합니다.
일반적으로 다음과 같은 방법으로 이벤트를 사용합니다.
consumer
시스템의 구성 요소를 나타내는 클래스입니다. 각 소비자는 시스템의 이벤트에 반응하는 다수의 핸들러를 연결할 수 있습니다. 소비자는 두 가지 기본 상위 유형인 agent
와 client
가 있습니다. 에이전트는 어떤 방식으로든 멀티플렉싱할 수 없는 기능을 구현하므로 코어에 한 번만 존재해야 하는 소비자입니다. 반면에 클라이언트는 외부 시스템에 대한 일종의 브리지를 나타내므로 당연히 동일한 코어에 여러 인스턴스를 가질 수 있어야 합니다.
따라서 일련의 명령이나 인터페이스를 개발하면 에이전트가 될 수 있고, XMPP와 같은 서비스와의 인터페이스를 사용하면 클라이언트가 될 수 있습니다.
소비자 정의는 표준 defclass
와 유사하지만 슈퍼클래스와 메타클래스가 올바르게 설정되었는지 확인하는 define-consumer
사용하여 이루어져야 합니다.
handler
는 특정 이벤트가 코어에 발생할 때 특정 작업을 수행하는 기능을 보유하는 객체입니다. 각 핸들러는 특정 소비자에 연결되어 있으며 소비자가 코어에 제거되거나 추가될 때 코어의 이벤트 루프에 제거되거나 추가됩니다.
핸들러 정의는 define-handler
, define-function-handler
, define-instruction
또는 define-query
중 하나를 통해 이루어집니다. 공통 요구 사항에 대한 더 광범위한 약칭을 제공하기 위해 각 항목은 마지막 항목을 연속적으로 구축합니다. 핸들러가 실제로 이벤트를 수신하는 방식은 다를 수 있습니다. 어떤 핸들러 클래스를 사용할 수 있는지 확인하려면 Deeds 문서를 살펴보세요.
maiden 프로젝트에는 핵심 기능을 확장하는 몇 가지 하위 시스템이 포함되어 있습니다.
maiden 프로젝트에는 즉시 사용할 수 있는 몇 가지 표준 클라이언트도 포함되어 있습니다.
마지막으로 이 프로젝트에는 채팅 봇 등을 만드는 데 유용한 기능을 제공하는 여러 에이전트 모듈이 있습니다. 그것들도 바로 사용할 수 있습니다.