Hatena::ブログ(Diary)

あどけない話

2009-06-30

高階関数と無名関数

Ruby ではブロックが自慢らしい。制御構造を自由に作れる感じがするのだと言う。

obj.foo do |x,y|
    x を使う
    y を使う
end

これは、高階関数の最後の引数として無名関数を渡していることに他ならない。他の言語でどうなるか考えてみる。

JavaScript ではこうなる。

foo(arg,function(x,y) {
    x を使う;
    y を使う;
});

たしかに、function とか閉じ括弧が冗長な印象を受ける。

Haskell で書くとこうなる。

foo xs $ \x y -> do
    x を使う
    y を使う

end がない分、Ruby よりもすっきりした感じ。

Python でも同種のレイアウト規則を採用しているので、すっきり書けるかと期待したのだが、無名関数は一行に納めないといけないそうだ。

ここで関数の名前について考えよう。ruby では、ブロックに名前をつけられないけれど、Haskell ではできる。

bar x y = do
    x を使う
    y を使う

foo xs bar

名前が付いた関数であれば、高階関数の第一引数として渡したい気になるけど、Haskell では引数の順番はいかようにでも変更できる。

実際の例を挙げるなら、forM は mapM を flip したものだ。

mapM :: (a -> m b) -> [a] -> m [b]
forM :: [a] -> (a -> m b) -> m [b]
forM = flip mapM

という訳で、Ruby のブロックは、Haskell では当たり前の機能っぽい。

間違いや誤解があれば、指摘して下さい。

2008-03-04

Electric な Ruby の end (4)

という訳で、最新版です。関数呼び出しの括弧の中に if があっても大丈夫なはずです。

(defvar ruby-elect-keyword
  '("def" "if" "class" "module" "unless" "case"
    "while" "do" "until" "for" "begin" "end"))

(defvar ruby-elect-regex (mapconcat (lambda (x) (format "\\<%s\\>" x)) ruby-elect-keyword "\\|"))

(defun ruby-elect-end ()
  (interactive)
  (insert "d")
  (when (and (char-equal (char-before (1- (point))) ?n)
	     (char-equal (char-before (- (point) 2)) ?e))
    (ruby-indent-command)
    (let ((orig (point)) open)
      (forward-char -3)
      (when (looking-at "\\<end\\>")
	(setq open (ruby-elect-begin))
	(when open
	  (goto-char open)
	  (sit-for 0.3)))
      (goto-char orig))))

(defun ruby-elect-begin ()
  (let ((level 0) pos)
    (catch 'loop
      (while (re-search-backward ruby-elect-regex nil t)
	(setq pos (match-beginning 0))
	(cond
	 ((string= (match-string 0) "end")
	  (setq level (1+ level)))
	 ((member (match-string 0) '("if" "unless" "while" "until"))
	  (when (ruby-elect-if)
	    (if (= level 0) (throw 'loop pos))
	    (setq level (1- level))))
	 (t
	  (if (= level 0) (throw 'loop pos))
	  (setq level (1- level))))
	(if (< level 0) (throw 'loop nil))))))

(defun ruby-elect-if ()
  (save-excursion
    (catch 'loop
      (while (/= (point) (point-min))
	(forward-char -1)
	(let ((c (char-after)))
	  (cond
	   ((or (char-equal c ?\n) (char-equal c ?\()) (throw 'loop t))
	   ((or (char-equal c 32) (char-equal c ?\t)) ())
	   (t (throw 'loop nil))))))))

(add-hook 'ruby-mode-hook
	  (lambda ()
	    (define-key ruby-mode-map "d" 'ruby-elect-end)))

2008-02-27

Electric な Ruby の end (3)

Electric な Ruby の end (2)で気付いたことを直しただけで、公開していませんでしたが、これが現在のバージョンです。コメントが付いたので、思い出しました。(_ _)

(defvar ruby-elect-keyword
  '("def" "if" "class" "module" "unless" "case"
    "while" "do" "until" "for" "begin" "end"))

(defvar ruby-elect-regex (mapconcat (lambda (x) (format "\\<%s\\>" x)) ruby-elect-keyword "\\|"))

(defun ruby-elect-end ()
  (interactive)
  (insert "d")
  (when (and (char-equal (char-before (1- (point))) ?n)
	     (char-equal (char-before (- (point) 2)) ?e))
    (ruby-indent-command)
    (let ((orig (point)) open)
      (forward-char -3)
      (when (looking-at "\\<end\\>")
	(setq open (ruby-elect-begin))
	(when open
	  (goto-char open)
	  (sit-for 0.3)))
      (goto-char orig))))

(defun ruby-elect-begin ()
  (let ((level 0) pos)
    (catch 'loop
      (while (re-search-backward ruby-elect-regex nil t)
	(setq pos (match-beginning 0))
	(cond
	 ((string= (match-string 0) "end")
	  (setq level (1+ level)))
	 ((member (match-string 0) '("if" "unless" "while" "until"))
	  (when (ruby-elect-if pos)
	    (if (= level 0) (throw 'loop pos))
	    (setq level (1- level))))
	 (t
	  (if (= level 0) (throw 'loop pos))
	  (setq level (1- level))))
	(if (< level 0) (throw 'loop nil))))))

(defun ruby-elect-if (pos)
  (save-excursion
    (beginning-of-line)
    (and (looking-at "^[ \t]*")
	 (= (match-end 0) pos))))

(add-hook 'ruby-mode-hook
	  (lambda ()
	    (define-key ruby-mode-map "d" 'ruby-elect-end)))

2008-02-03

Electric な Ruby の end (2)

Electric な Ruby の endですが、ブックマークのコメントにバグが指摘してあったので、直してみました。

バグの報告は、ブログのコメントに書き込んで頂けると嬉しいです。(_ _)

(defvar ruby-elct-regex "def\\|if\\|class\\|module\\|unless\\|case\\|while\\|do\\|until\\|for\\|begin\\|end")

(defun ruby-elect-end ()
  (interactive)
  (insert "d")
  (when (and (char-equal (char-before (1- (point))) ?n)
	     (char-equal (char-before (- (point) 2)) ?e))
    (ruby-indent-command)
    (let ((orig (point)) open)
      (forward-char -3)
      (when (looking-at "\\bend\\b")
	(setq open (ruby-elect-begin))
	(when open
	  (goto-char open)
	  (sit-for 0.3)))
      (goto-char orig))))

(defun ruby-elect-begin ()
  (let ((level 0) pos)
    (catch 'loop
      (while (re-search-backward ruby-elct-regex nil t)
	(setq pos (match-beginning 0))
	(cond
	 ((string= (match-string 0) "end")
	  (setq level (1+ level)))
	 ((string= (match-string 0) "if")
	  (when (ruby-elect-if pos)
	    (if (= level 0) (throw 'loop pos))
	    (setq level (1- level))))
	 (t
	  (if (= level 0) (throw 'loop pos))
	  (setq level (1- level))))))))

(defun ruby-elect-if (pos)
  (save-excursion
    (beginning-of-line)
    (and (looking-at "^[ \t]*\\(if\\)")
	 (= (match-beginning 1) pos))))

(add-hook 'ruby-mode-hook
	  (lambda ()
	    (define-key ruby-mode-map "d" 'ruby-elect-end)))

2008-02-01

Electric な Ruby モードの end

Ruby に付いてくる ruby-electric.el では、たとえば class と打った後に空白を入れると end が挿入されます。僕が欲しいエレクトリックな機能は、これじゃありません。

end と押すと対応する単語に飛んでほしいのです。というわけで作ってみました。別ファイルにすると設定方法を説明するのが面倒なので、とりあえず以下を .emacs に入れて下さい。

(defvar ruby-elct-regex "def\\|if\\|class\\|module\\|unless\\|case\\|while\\|do\\|until\\|for\\|begin\\|end")

(defun ruby-elect-end ()
  (interactive)
  (insert "d")
  (when (and (char-equal (char-before (1- (point))) ?n)
	     (char-equal (char-before (- (point) 2)) ?e))
    (ruby-indent-command)
    (let ((orig (point)) open)
      (forward-char -3)
      (when (looking-at "\\bend\\b")
	(setq open (ruby-elect-begin))
	(when open
	  (goto-char open)
	  (sit-for 0.3)))
      (goto-char orig))))

(defun ruby-elect-begin ()
  (let ((level 0))
    (catch 'loop
      (while (re-search-backward ruby-elct-regex nil t)
	(cond
	 ((string= (match-string 0) "end")
	  (setq level (1+ level)))
	 (t
	  (if (= level 0) (throw 'loop (match-beginning 0)))
	  (setq level (1- level))))))))

(add-hook 'ruby-mode-hook
	  (lambda ()
	    (define-key ruby-mode-map "d" 'ruby-elect-end)))

あんまりテストしていません。飛んで一瞬留まる方法も正しいのか調べていません。

こういう構想は、Pascal を使っていた学生時代に持っていたんですが、そのときの実力では実現できませんでした。

今日、書いてみたら 10 分ぐらいでできたので、成長したんだなぁと思います。。。(Pascal を使わなくなったとはいえ、構想の実現に 15 年以上もかかるとは。。。)