COMMON LISP JP > Archives > 2018/06/02

2018/06/02 00:27:55 UTChojo
#
teratailでも質問させていただいたのですが https://teratail.com/questions/128960 JSCLを使ってサーバもクライアントもLISPな環境を作って見たいと考えています。しかしHelloworld的なものが見つからないので、ブラウザにhelloworld!と表示させる方法すらわからず、困っています。こちらでこのような質問をさせていただいてよろしいでしょうか。
2018/06/02 01:02:18 UTCshiro
#
JSCLは使ったことないですが、最終的にはjavascriptの下位関数を呼び出さないとならないですよね。ソース見ると #j:xxx でjavascriptのxxxにアクセスできるように見えますが、例えば (#j:document.write "Hello World!") とかするとどうなります?
2018/06/02 09:26:49 UTChojo
#
shiro様、ご返事ありがとうございます!恥ずかしながら、サーバでJSCLを動かす環境自体整えられないため、 (#j:document.write "Hello World!")のコードも実行できる状態ないというのが現状です。現時点でわかったこととしまして、どうやらCLISPなどの処理系をサーバに導入し、JSCLのソースを読み込んでからプロジェクトファイルを読み込むとコンパイルが始まってhtmlが出力されるので、そのhtmlをApacheなどを利用してブラウザからアクセス可能な状態にすればサーバもクライアントもCommon Lispな環境が作れるのだろうと認識しているのですが、CLISPなどの処理系にJSCLを読み込む方法がわからないため、そこで躓いています(汗
2018/06/02 09:42:52 UTChojo
#
jsclのgithub(https://github.com/jscl-project/jscl)のBuildのステップでは、load jscl.lisp in your Lispと記載がありました。見よう見まねでgit cloneして作られたjsclのプロジェクトディレクトリのルートでsbclコマンドを打ってreplを立ち上げ、(in-package "jscl.lisp")などとしてみたのですが「The name "jscl.lisp" does not designate any package.」というエラーが表示されてしまいました。
2018/06/02 09:54:58 UTCshiro
#
えーとまず前提の確認なんですが、githubの記述だと「JSCLをハックする場合はcommon lisp処理系にjscl.lispをロードせよ」とありますよね。これは素直に読めばJSCLそのものを改造したい場合の話だと思います。Common LispのサブセットからJavaScriptへとコンパイルしてnodejsなりブラウザなりで走らせる部分は、既にjscl.jsというJavaScriptファイルになっているので、JSCL上でCommon Lispプログラムを動かすにはそれだけでいいんじゃないでしょうか。
2018/06/02 10:06:45 UTCshiro
#
ふーむ、jscl.jsロードするとevalはできるんだけどcompile関数にアクセスする方法がわからないなあ。コンパイラ (CL->JS)自体は読み込まれてるっぽいんだけど
2018/06/02 10:11:36 UTCshiro
#
cl:compileはシンボルが定義されてるけど関数定義が無いっぽい? 内部的にはjscl::compile-toplevelが CL->JS をやってくれるとこまでは確認できたが
#
ふむ。CLファイルを読んでJSファイルを書き出すのは、jscl:compile-applicationという関数が定義されてるが、これはjscl.js自体には含まれてないっぽい。ということはアプリケーション自身をコンパイルするにはやっぱり別のCL処理系が必要なのか。
2018/06/02 10:28:12 UTCshiro
#
とりあえずCL->JSはできた。けど、どうもまだそのへんは開発中っぽい。
$ sbcl
* (load "jscl")
* (setf jscl::*environment* (jscl::make-lexenv))
* (jscl:compile-application '("foo.cl") "foo.js")
#
これでカレントディレクトリの foo.cl が foo.js にコンパイルされる。jscl::*environment* のあたり、内部シンボルに触らないとならないのが怪しいんで、まだcompile-application自体を外から使える段階に達してないのかも。
2018/06/02 11:05:53 UTCshiro
#
念のため、上で「サーバにCL処理系を導入し」とありますが、JSCLでアプリケーションのlispコードをjsにコンパイルすること自体は手元でできるので、理屈の上ではサーバにできたjsを置けば良いはずで、サーバ上でCL処理系を走らせる必要はないんじゃないかと推測します。
2018/06/02 11:44:28 UTChojo
#
shiro様、ありがとうございます!
2018/06/02 11:49:49 UTChojo
#
おっとチェックを外さないとEnter押したら投稿されてしまうのですね(汗)勉強不足で認識が誤っていないかいくつか確認させていただいてもよろしいでしょうか。
2018/06/02 11:54:26 UTChojo
#
まず、前提として自分が行っていたGithubのBuildの項目は、JSCL自体の拡張(?)を行う際に必要な手順であって、単にJSCLにLISPのコードを評価させたいだけの場合は、適当なnode環境でrequire('jscl')すればeval関数を呼び出すせるということでよろしかったでしょうか。jsclはcoffeescriptのようなトランスパイラだと思い込んでいましたが、どうやら前提から誤解していたようです。ご指摘いただきありがとうございます!
2018/06/02 11:56:42 UTCshiro
#
あ、それがですね、実際に走らせてみたところ、jscl.jsに含まれているコンパイラはjscl::compile-toplevelだけで、cl:evalはそれを呼び出しています。なのでeval (内部的にトップレベル式をコンパイルして実行) だけならjscl.jsだけでいいんですが、ファイル丸ごとjsに変換したい場合は手元のCL処理系にjscl.lispを読み込まないとならないようです。
2018/06/02 12:01:49 UTChojo
#
なるほど、つまりcoffeescriptのようなトランスパイラとしての機能はインタプリタ(インタプリタと言えるのか不明ですがnode環境でevalする)環境では実行できず、トランスパイルするには、何らかの処理系からjscl.lispを読み込むことで実行可能ということですね?つまりjsclは、インタプリタとコンパイラ(トランスパイラ)の両方が備わっており、コンパイラとしての利用をするには処理系を必要とする、という認識でよろしいでしょうか。
2018/06/02 12:02:21 UTCshiro
#
少なくとも今のところはそのように見えますね。
2018/06/02 12:13:42 UTChojo
#
なるほど、GithubのREADME.mdの内容が(あと僕の英語力が)乏しいので、本当に助かります!shiro様の助言を元に、一度頭をリセットしてから、もう一度色々試して見たいと思います!

LISPは勉強中でまだ詳しくないのでLISP界ではCompile関数が使えることが常識なのかもしれないのですが、もしnodeでrequireしたJSCL環境でもcompile関数が利用できたら、なんかそれってトテツモナイですね(具体的な凄さは脳がパンクしてわりませんが、アプリからコンパイル機能を使えるようになるから、ユーザが動的に書いたコードをキャッシュして高速化できるとかになるんですかね...うーん..とっても奇妙だ。すみません!余談でした!)

また不明点があったら質問させていただいてよろしいでしょうか。
2018/06/02 12:13:53 UTCt-cool
#
外から失礼しますー。

jsclをjsにコンパイルしてGithub Pagesにアップしたので、もし良ければ遊んでみてください。
https://github.com/t-cool/jscl

jsからlispのコードを呼び出すには、ブラウザのコンソールでjscl.evaluateStringを使うとLispの環境をさわれます。
jscl.evaluateString("(+ 1 1)")とすれば2とかえってきますし、
jscl.evaluateString("(defun f1()(+ 1 1))")としてからREPLで(f1)とすれば2がかえってきます。

逆に、disassembleをつかうと、lispをJSにしてくれるので、
CL-USER> (defun f1(x)(+ x 1))
F1
CL-USER> (disassemble 'f1)
function JSCL_USER_F(values,v1){internals.checkArgs(arguments.length-1,1);
var v2=this;
return (function(){return (function(){var x1=v1;
if (typeof x1!='number') throw 'Not a number!';
return x1+1;
})();
})();
}
NIL
CL-USER>
#
みたいな感じで、どのようにlispがjsに変換されているかが、把握できるかと思います。
2018/06/02 12:15:09 UTCshiro
#
evaluateStringはわかるんですが、hojoさんがやりたいのはバッチでのCL->JS変換ですよね。
#
あとjscl.jsはnpm installで入手できますね。
2018/06/02 12:18:00 UTCt-cool
#
npmでもいけるとおもいますー。

npm install -g jscl
としてから、
jscl-repl
とすれば、REPLで遊べるはずですー。
#
CL->JS変換では、Parenscriptもあるかと思います。
https://qiita.com/t-cool/items/db0dfd3dc54a148162c5
Parenscriptでpsを使えば、CL->JS変換ができるかと思います。
#
* (ql:quickload :parenscript)
To load "parenscript":
  Load 1 ASDF system:
    parenscript
; Loading "parenscript"
..................................................
[package parenscript].............................
[package ps-js-symbols]...........................
[package ps-dom1-symbols].........................
[package ps-dom2-symbols].........................
[package ps-window-wd-symbols]....................
[package ps-dom-nonstandard-symbols]..............
[package ps-dhtml-symbols]........................
[package ps-js]...................................
....................
(:PARENSCRIPT)
* (in-package :ps)

#<PACKAGE "PARENSCRIPT">
* (ps (defun f1(x)(+ x 1)))

"function f1(x) {
    return x + 1;
};"
*
2018/06/02 12:21:40 UTCshiro
#
REPLの使い方はREADMEに書いてあるんですが、それじゃなくてバッチの変換のやり方が知りたいわけです。
で、jscl.lispのcompile-applicationはexportされてるので多分それを呼ばせるのを想定してるんだと思うんですが、ただそれを呼んだだけじゃ jscl::*environment* が設定されてなくてエラーになるんですよ。そいつを(jscl::make-lexenv)で初期化しとかないとならない。
これが、単にまだそこまで書いてないだけなのか、それとも直接jscl:compile-applicationを呼ぶ以外のやり方があるのか、そのへんは作者に聞いてみないとわからないかも。
2018/06/02 12:21:53 UTChojo
#
おおっと!t-coolのNoguchiさんですよね?初めまして!hojoと申しますm(_ _)m 数時間前にJSCL Playgroundをcloneしてローカル環境でいじらせていただいてました!ちょっと初心者すぎていま文章をひとつずつ読んでいるので、流れてしまう前に一度ここで投稿させていただきますね(^^;;
2018/06/02 12:22:20 UTCt-cool
#
shiroさんすいません、脱線してしましましたー...
#
hojoさん
あー、そうです。野口です
#
すいません、JSCLが話題に上がっていたので、浅はかな知識で顔を出してしまった感じです。
2018/06/02 12:23:21 UTCshiro
#
parenscriptはバッチ変換できます? できるならそっちの方が手っ取り早いかもしれないですね。
2018/06/02 12:23:46 UTChojo
#
いえいえ、まさか投稿があると思っていなかったのでビックリしました!自分もまだJSCLで何ができるのか?ということを調査している段階なので具体的何をしたいというよりはJSCLを詳しく知りたいと思っています!なので、色々教えていただけるとありがたいですm(_ _)m
2018/06/02 12:26:07 UTCt-cool
#
shiroさん

バッチ変換の用語がよくわかっていないので、間違えていたらすいません。
フィボナッチ関数をps関数で変換すると、つぎのようになります。
これがバッチ変換ならばhojoさんが探しているものでしょうか。

(ps (defun fibonacci (n &optional (a 0) (b 1) (acc ()))
  (if (zerop n)
      (nreverse acc)
      (fibonacci (1- n) b (+ a b) (cons a acc)))))

"function fibonacci(n, a, b, acc) {
    if (a === undefined) {
        a = 0;
    };
    if (b === undefined) {
        b = 1;
    };
    return zerop(n) ? nreverse(acc) : fibonacci(n - 1, b, a + b, cons(a, acc));
};"
2018/06/02 12:29:30 UTChojo
#
「自分も」っておかしかったですね(汗) 「自分は」です(´・ω・`;) 色々検索してJSCLに関する日本の情報はNoguchiさんの情報しかほとんど出てこなかったので「浅はかな知識」とおっしゃいましたが、誰よりも詳しいのではないでしょうか(∩´∀`∩)
2018/06/02 12:34:57 UTChojo
#
(いただいた投稿を全部読み切れてないので一度ROMします!)
2018/06/02 12:35:16 UTCshiro
#
あ、私の考えていたのは、CLで書かれたアプリケーションファイルをjsファイルに変換して、それをサーバに置く、という使い方です。
#
もちろん1フォームごとにjsに変換してファイルに書き出すコードを書けばできますが、on JSな処理系ってそういうことをするツールが標準でついてくるのが普通だろうなと思うんで、そのやりかたを探してたわけです。
2018/06/02 12:38:58 UTCmyao
#
どうも、こんにちは。ずっとROMってたのですが、一応手元でparenscript動かしてみたので。parenscriptには、ps-compile-file という関数があり、ファイル単位で、JSのコードに変換してくれる関数があるようですよ。ただ、依存性解決までしてくれるかどうかは分からないですね。少なくともql:quickloadは単に、quickloadという関数の呼び出しとして処理されてました。
2018/06/02 12:39:04 UTCt-cool
#
その使い方であれば、現状ではParenscriptかと思います。
Parenscriptのチュートリアル(https://qiita.com/t-cool/items/db0dfd3dc54a148162c5)では、
画像のスライドショーをParenscript(とcl-who)で作ってサーバで提供...っていう流れでした。
2018/06/02 12:40:30 UTCshiro
#
なるほどー。既にあるならparenscriptで行く方が楽っぽいですね。
#
JSCLもcl:compile-fileとかシンボルの定義はjscl.jsに含まれてるけど実体がないっぽいんですよね。これから作るということかなあ?
2018/06/02 12:48:27 UTCt-cool
#
shiroさん
一度、Davidさんに聞いてみますー。
t-coolは、若手CLプログラマの応援企画でJSCLに参加しています。
今は、tinyCLOSをJSCLに載せる話が進んでいまして、irisのasciianさんが取り組んでいるところです。
#
https://github.com/orgs/jscl-project/teams/dev/discussions/1
2018/06/02 12:49:55 UTCshiro
#
おお、ではこれから充実が期待されますね!
2018/06/02 12:53:55 UTCt-cool
#
はい! 
私自身は、盛り上げる役だと思っています。
まだまだ勉強不足ですが、若手のみなさんに助けてもらいながら、
JSCLが充実していくのが楽しみですー。
2018/06/02 13:02:13 UTCt-cool
#
shiroさん

Turing Complete FM、楽しく聞かせていただいてます!
また次回が楽しみにです!
https://turingcomplete.fm
僕は、Lispをさわるきっかけになったのが、
shiroさんが翻訳されたハッカーと画家とLand of Lispでした。
Turing Complete FMをきかせていただいてから、
プログラミングGaucheを買って読んでいるところです。

hojoさん
shiroさんの書籍は関西Lispユーザ会にも持っていってまして、
Lisp本の話題でも盛り上がってますー。
#
そろそろ子守りしてきます。では、失礼しますー。
2018/06/02 13:20:33 UTChojo
#
shiroさんってハッカーと画家とLand of Lispを翻訳された方なのですね!両方とも愛読させて頂いてます!(Land of Lispは今読んでる最中です)
2018/06/02 13:27:28 UTChojo
#
JSCLに興味を持ったのが、サーバもクライアントもLISPで書ける!という点に魅力を感じたからなのですが、parenscriptというものがあるのは初耳でした。parenscriptはトランスパイラという認識でよろしいですか?つまり、ブラウザでevalすることはできないという認識で良いでしょうか。
2018/06/02 21:40:11 UTCt-cool
#
hojoさん

このサイトに詳しくのってます。
https://common-lisp.net/project/parenscript/
2018/06/02 21:59:09 UTChojo
#
t-coolさん、ありがとうございます!(英語に弱いので翻訳して読んでみます!)
2018/06/02 22:35:26 UTChojo
#
すみません、質問できる場所が無いのでここで質問させてください><。以下のようにしてみたのですが、リクエストするとエラーが表示されてしまい、うまくいきません。nodeのバージョンはv10.1.0です(´・ω・`;)
#
const http = require('http')
const jscl = require('jscl')

http.createServer(async (req, res) => {

  res.writeHead(200, {'Content-Type':'text/plain'})
  res.end(jscl.evaluateString('(+ 1 2)'))
  
}).listen(3000)
#
(node:9704) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be one of type string or Buffer. Received type number
    at ServerResponse.end (_http_outgoing.js:702:13)
    at Server.http.createServer (/mnt/sdb/www/vhosts/myproject/index.js:10:7)
    at Server.emit (events.js:182:13)
    at parserOnIncoming (_http_server.js:652:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:109:17)
(node:9704) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:9704) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
2018/06/02 22:51:36 UTChojo
#
nodeコマンドから対話モードで実行した際には問題なく jscl.evaluateString('(+ 1 2)') の結果が得られるのですが、エラーの内容を見るとチャンク関数?の引数の型に誤りがあると言われている気がします。何がいけないのでしょうか?初歩的な質問で申し訳ないです。
2018/06/02 23:09:12 UTCshiro
#
node使ってないので推測ですが、jscl.evaluateString()が数値を返してるのに対し、res.end()は文字列かBufferを期待している、という可能性は? jscl.evaluateString() + '' とかにするとどうなります?
2018/06/02 23:35:55 UTChojo
#
shiro様、解決しました!ありがとうございます!お恥ずかしながら、盲点になっておりました!