Typescript感想 + Underscoreを扱えるようにしてみた

主に僕が一番使っているcoffee-scriptとの比較

書き味

構文の硬さがJavaが10 Python6 Rubyが3という記事をどっかで見たけど、同じように当てはめると、JS8 typescript6 coffee2という感じ。 coffee-scriptの超ゆるふわスタイルに慣れてると結構辛い感じはあるが、自分多分Coffeeについてある程度極まってる感じなので、あんまり比較しないほうがよさそう。 高階関数(後述)が簡単に定義できるのでJS特有のパラダイムには素直に応えられる。

””” multiline text “”” がない デフォルト引数がない

型アノテーション

クラス志向というよりかはインターフェース志向?Golangっぽさも若干感じる。とりあえずJSの邪魔をしないanyが便利。

JavaScriptのスーパーセット

CoffeeScriptの辛かった点である、「jsコピペしてすぐ動かない」問題が解決した。 とりあえずコピペしてから徐々に置き換えるという段階を踏める。

末尾セミコロン

JSと同じような扱い。classフィールドでは;なしだと構文解析されない。基本末尾セミコロン嫌いなので可能な限り省略して書いてみたが、結局必要そう…

Lambda

結局自分はfunctionと書きたくなくてcoffeescriptを使ってるフシがあるのだけど、どのぐらい省略できるか調べてみた。

自分自身を返す f(x) = x はこう書ける

1
(i) => {return i}

ブロックだと複数行書けるが、returnが必須

もしくはブロック省略して

1
(i) => i

こちらはreturnが要らない

CoffeeScriptと違って 引数なしの()は省略できない

1
2
3
[1,2,3].forEach((i) => {
  console.log(i);
})

補完

宗教上の理由(一方が好きというかは一方が嫌い)でVSが使えないのだが、SublimeTextで書いていたが補完はなかった。 まあ簡単そうなのでそのうち誰かが作るだろう。

instance

公式リポジトリのサンプルに型ファイル情報が少しついてる

  • express.d.ts
  • jquery.d.ts
  • jqueryui.d.ts
  • node.d.ts

todomvcにはBackboneモジュールに対して型アノテーションを追加していて参考になる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
declare module Backbone {
    export class Model {
        constructor (attr? , opts? );
        get(name: string): any;
        set(name: string, val: any): void;
        set(obj: any): void;
        save(attr? , opts? ): void;
        destroy(): void;
        bind(ev: string, f: Function, ctx?: any): void;
        toJSON(): any;
    }
    export class Collection {
        constructor (models? , opts? );
        bind(ev: string, f: Function, ctx?: any): void;
        collection: Model;
        length: number;
        create(attrs, opts? ): Collection;
        each(f: (elem: any) => void ): void;
        fetch(opts?: any): void;
        last(): any;
        last(n: number): any[];
        filter(f: (elem: any) => any): Collection;
        without(...values: any[]): Collection;
    }
    export class View {
        constructor (options? );
        $(selector: string): any;
        el: HTMLElement;
        $el: any;
        model: Model;
        remove(): void;
        delegateEvents: any;
        make(tagName: string, attrs? , opts? ): View;
        setElement(element: HTMLElement, delegate?: bool): void;
        tagName: string;
        events: any;

        static extend: any;
    }
}

underscoreに型アノテーションつけてみた

練習がてらやってみた。 underscore type definition — Gist https://gist.github.com/3831467

クライアント

1
2
///<reference path='underscore.d.ts'/>
declare var _ :underscore;

サーバー(Node)

1
2
3
///<reference path='node.d.ts'/>
///<reference path='underscore.d.ts'/>
var _ : underscore = require('./underscore-min')

ドキュメントみながら脳みそ止めて適当に作ったのでテストはしてないがひと通り動くはず。

underscore.d.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
interface underscore {
  //collections
  each(obj:any, f: Function): void;
  each(obj:any, f: Function, context:any): void;
  forEach(obj:any, f: Function): void;
  forEach(obj:any, f: Function, context:any): void;

  map(obj:any, f: Function): any[];
  map(obj:any, f: Function, context:any): any[];
  collect(obj:any, f: Function): any[];
  collect(obj:any, f: Function, context:any): any[];

  reduce(obj:any, f: Function, memo:any): any;
  reduce(obj:any, f: Function, memo:any, context:any): any;
  inject(obj:any, f: Function, memo:any): any;
  inject(obj:any, f: Function, memo:any, context:any): any;
  foldl(obj:any, f: Function, memo:any): any;
  foldl(obj:any, f: Function, memo:any, context:any): any;

  reduceRight(obj:any, f: Function, memo:any): any;
  reduceRight(obj:any, f: Function, memo:any, context:any): any;
  foldr(obj:any, f: Function, memo:any): any;
  foldr(obj:any, f: Function, memo:any, context:any): any;

  find(obj:any, f: Function): any;
  find(obj:any, f: Function, context:any): any;
  detect(obj:any, f: Function): any;
  detect(obj:any, f: Function, context:any): any;

  reject(obj:any, f: Function): any[];
  reject(obj:any, f: Function, context:any): any[];

  filter(obj:any, f: Function): any[];
  filter(obj:any, f: Function, context:any): any[];
  select(obj:any, f: Function): any[];
  select(obj:any, f: Function, context:any): any[];

  all(obj:any, f: Function): bool;
  all(obj:any, f: Function, context:any): bool;
  every(obj:any, f: Function): bool;
  every(obj:any, f: Function, context:any): bool;

  any(obj:any): bool;
  any(obj:any, f: Function): bool;
  any(obj:any, f: Function, context:any): bool;
  some(obj:any): bool;
  some(obj:any, f: Function): bool;
  some(obj:any, f: Function, context:any): bool;

  include(obj:any, f: Function): bool;
  contains(obj:any, f: Function): bool;

  invoke(obj:any, f: Function): any[];
  invoke(obj:any, f: Function, args:any[]): any[];

  pluck(obj:any, prop: string): any[];

  max(obj:number[]): number;
  max(obj:number[], context:any): number;

  min(obj:number[]): number;
  min(obj:number[], context:any): number;

  sortBy(obj:any, f: Function): any[];
  sortBy(obj:any, f: Function, context:any): any[];

  groupBy(obj:any, f: Function): any[];

  sortedIndex(obj:any, f: Function): any[];
  sortedIndex(obj:any, f: Function, iter:Function): any[];

  shuffle(obj:any[]): any[];
  toArray(obj:any[]): any[];
  size(obj:any[]): number;

  //array
  first(obj: any[]): any;
  head(obj: any[]): any;

  last(obj: any[]): any;

  rest(obj: any[]): any[];
  tail(obj: any[]): any[];

  compact(obj:any[]): any[];

  flatten(obj:any[]): any[];
  flatten(obj:any[], shallow:number): any[];

  without(obj:any[]): any[];
  intersection(...obj:any[]): any[];
  difference(...obj:any[]): any[];

  uniq(obj:any[]): any[];
  unique(obj:any[]): any[];

  zip(...obj:any[]): any[];
  indexOf(obj:any[], val:any): any;
  range(stop:number): number[];
  range(start:number, stop:number): number[];
  range(start:number, stop:number, step:number): number[];

  //object
  keys(obj:any): string[];
  values(obj:any): any[];
  functions(obj:any): string[];
  extend(obj:any, ...sources:any[]): any;
  pick(obj:any, ...keys:string[]): any;
  defaults(obj:any, ...defaults:any[]): any;
  clone(obj:any): any;
  tap(obj:any, intercepter:Function): any;
  has(obj:any, key:string): bool;

  //functions
  bind(f:Function, obj:Object):void;
  bind(f:Function, obj:Object, ...args:string[]):void;
  bindAll(obj:Object, ...methodNames:string[]):void;
  memoize(f:Function):any;
  memoize(f:Function, ...hashFunctions:any[]):any;

  delay(f:Function, wait:number):any;
  delay(f:Function, wait:number, ...arguments:any[]):any;
  defer(f:Function):any;
  defer(f:Function, ...arguments:any[]):any;
  throttle(f:Function, wait:number):any;
  debounce(f:Function, wait:number):any;
  debounce(f:Function, wait:number, ...immediate:any[]):any;
  once(f:Function):any;
  after(count:number, f:Function):any;
  wrap(f:Function, wrapper:Function):any;
  compose(...fs:Function[]):Function;

  //isX
  isEqual(obj:any, other:any): bool;
  isEmpty(obj:any): bool;
  isElement(obj:any): bool;
  isArray(obj:any): bool;
  isObject(obj:any): bool;
  isArguments(obj:any): bool;
  isFunction(obj:any): bool;
  isString(obj:any): bool;
  isNumber(obj:any): bool;
  isFinite(obj:any): bool;
  isBoolean(obj:any): bool;
  isDate(obj:any): bool;
  isRegExp(obj:any): bool;
  isNaN(obj:any): bool;
  isNull(obj:any): bool;
  isUndefined(obj:any): bool;

  //utility
  noConflict():bool;
  identity(x:any):any;
  times(n:number, f:Function):void;
  mixin(obj:any):void;
  uniqueId(prefix:string[]): string;
  escape(str:string): string;
  result(obj:any, key:string): any;
  template(template:string, bindings:any): string;

  //chaining
  chain(obj:any):any;
  //value is useless
}

追記: カリー化したかった

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// it doesnt work
Function.prototype.proc = function() {
  var args, target, v;
  var __slice = [].slice;
  args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  target = this;
  while (v = args.shift()) {
    target = target(v);
  }
  return target;
};

var fuga: (number) => (string) => number
  = (a) => (b) => a * b.length;

// it works
console.log(fuga(3)("dafa"))

// it doesnt work
console.log(fuga.proc(3, "dafa"))

var fuga: (number) => (string) => number

なんていうかっこいい型定義ができるんだからcurry化したいなーと思っていじってたんだけど、Function型定義がprocを認識しない。 どうにかしてdeclareすればできるのかと思ったんだけどよくわからない。誰かタスケテほしい。

こういうことをしたかったんだけど

Test_game

Memo2

Octopressテーマの作り方

ディレクトリ構成

仕組みとしては超単純で、 .themes 以下に掘ったディレクトリに対して、

1
rake install['テーマ名']

すると .themes/hogehoge 以下の内容が現在のディレクトリに上書きされる。それだけ。

実際に開発する場合は scss, source以下のファイルをpreview状態で編集して、最後にsourceとscssだけ抜いたディレクトリをコピーして終わり

たとえばこれは、ほぼslashテーマのクローンだが

1
2
3
4
5
6
7
8
9
10
.themes/mizchi/
├── README.md
├── sass
... ├── _includes
... (中略)
└── source
    ├── _includes
... (中略)

16 directories, 76 files

sassとsourceが上書きされる

エントリポイント

たぶん source/_layout/default.html

ページを作成する際は、yamlのメタ情報で展開するテンプレートを指定する。(defaultはたぶん指定しなかった時も展開されるんだろう) たとえばこのページのyamlはこうなっている

1
2
3
4
5
layout: post
title: "Octopressテーマの作り方"
date: 2012-09-23 21:07
comments: true
categories: 

で、postは

1
2
layout: default
single: true

singleはなんだろう… あとで調べる

include

includeされるテンプレートは_include以下におく。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.themes/mizchi/source/_includes/
├── after_footer.html
├── archive_post.html
├── article.html
├── banner.html
├── custom
│   ├── after_footer.html
│   ├── category_feed.xml
│   ├── footer.html
│   ├── head.html
│   ├── header.html
│   └── navigation.html
├── disqus.html
├── fancybox.html
├── footer.html
├── google_analytics.html
├── head.html
├── header.html
├── navigation.html
└── post
    ├── author.html
    ├── categories.html
    ├── date.html
    ├── disqus_thread.html
    └── sharing.html

2 directories, 22 files

まあ色々と入っているけど、includeされた順に追っていけばそんなに難しくない。あとはファイル名から適当に類推できる。

Octopress導入メモ

基本的には Deploying to Github Pages - Octopress http://octopress.org/docs/deploying/github/ を参考にしつつ

Octopress

Jeykilをバックエンドに静的ページを動的に生成するブログエンジン。なにをいってるかわかんねーだろーがry

rakeコマンドで記事生成したり、erbでテンプレートを編集したりできる。要は生成部分は動的だけど成果物は静的。

プラグインもそこそこある。けどとくに制約がないので自分で書いたほうが早そう。

Github PagesやHerokuにデプロイするのが常套っぽい。まあ静的ページだからどこでもいいんですけどね。

きっかけ

俺もだいぶJS書けるようになってきたのでJS書きまくれるブログでサブドメインはいいよなーとか考えてて

そういえば mizchi.github.comでなんかできないかなーと調べてたらmarkdownならoctopressってのがあるらしい => やるか githubドメインよい。

installの準備

いきなり躓いた。ruby 1.9.3だと、RedCloth 4.9.3? のエラーかなんだかでbundle installできない。ぐぐるといろんなところでRuby 1.9.2使えと言われるのでおとなしく入れる(公式には1.9.3使えと書いてある…)

僕ルビーストじゃないんで原因よくわからんす(会社でRails使ってるはずなんだけど)。とりあえずRVM使ってるんで1.9.2いれておく。

1
rvm install 1.9.2

rakeとzshが相性悪いのを少し修正

1
2
alias rake="noglob rake"
compdef -d rake

rakeの補完、ファイル読みに行ってるようで超遅い。auto-fuつかってると特に。

install

1
2
3
4
5
6
7
8
9
git clone git://github.com/imathis/octopress.git
cd octopress
rvm use 1.9.2
gem install bundler
bundle install
rake setup_github_pages
# デプロイURLを求められるので ~.github.comを指定
rake generate
rake deploy

記事を更新

1
rake new_post['test']

source/_posts/~~~.markdownが生成される。 エディタでmarkdownで書く。ヘッダのハイフンで囲われてる領域はyamlでメタ情報生成用らしい

1
2
3
4
5
6
7
8
9
10
11
---
layout: post
title: "Octopress導入メモ"
date: 2012-09-22 15:25
comments: true
categories: ruby octopress
---

## はじめに

てst

記事書いたらrake gen_deploy (generate と deploy同時にやる)

1
rake gen_deploy

記事書いて生成してgen_deploy

サーバー建てて動的に変更確認することもできる

1
2
rake preview
open http://localhost:4000

とりあえずやったこと

テーマを入れ替えてみた

tommy351/Octopress-Theme-Slash

CoffeeScript

1
<script type="text/coffeescript" src='hoge.coffee'></script>

とかやりたかったのでJSをmy以下に突っ込んでsource/_include/head.htmlにパス追加。myってのは酷いのであとでどっかに動かす。

1
2
3
4
<script src="/my/coffee-script.js"></script>
<script src="/my/underscore-min.js"></script>
<script src="/my/backbone-min.js"></script>
<script type='text/coffeescript' src="/my/main.coffee"></script>

JS自由に読み込めるのいいですね、危ないけど

おまけ

coffee-scriptとBackboneで無理やりpushstateさせるプラギン書いてみた。ヘッダで読みこめば動く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@App =
  View: {}
@app = null

class App.Workspace extends Backbone.Router
  routes:
    '': 'index'
    'blog/:year/:month/:day/:title/': 'entry'
    'blog/archives': 'archives'
    'blog/categories/:tag/': 'category'

  constructor: ->
    super
    @header = new App.View.Header
    @initialized = false

  index: =>
    unless @initialized
      @updateContent location.pathname
    else
      @makeArticles()
      @initialized = true

  category: (tag) =>
    unless @initialized
      @updateContent location.pathname
    else
      @makeArticles()
      @initialized = true

  entry: (year, month, day, title) =>
    unless @initialized
      url = "/blog/#{year}/#{month}/#{day}/#{title}/"
      @updateContent url
    else
      @makeArticles()
      @initialized = true

  archives: =>
    unless @initialized
      @updateContent '/blog/archives'
    else
      @makeArticles()
      @initialized = true

  makeArticles: ->
    @articles = $('article.post').map (index, el) =>
      new App.View.Article el: el

  updateContent: (url) ->
    $.get url, (data) =>
      $el = $('<div>').html(data)
      html = $el.find('#content').html()
      $('#content').html html

      title = $el.find('title').text()
      $('title').html title

class App.View.Header extends Backbone.View
  el: '#header'
  events:
    'click h1 > a': 'index'
    "click a[href='/']": 'index'
    "click a[href='/blog/archives']": 'toArchives'

  toArchives: (event) =>
    event.preventDefault()
    app.navigate 'blog/archives', trigger: true

  index: (event) =>
    event.preventDefault()
    app.navigate '', trigger: true

class App.View.Article extends Backbone.View
  events:
    'click h1.title a': 'toEntry'
    "click a.category": 'toCategory'

  toEntry: (event, el) =>
    event.preventDefault()
    href = @$el.find('h1.title > a').attr('href')
    app.navigate href.substr(1), trigger: true

  toCategory: (event, el) =>
    console.log arguments...
    event.preventDefault()
    href = @$(event.target).attr('href')
    app.navigate href.substr(1), trigger: true

$ =>
  @app = new App.Workspace()
  Backbone.history.start pushState: true

テーマの作り方

あとで書く。よくわからない。

テスト

仏説摩訶般若波羅蜜多心経 観自在菩薩行深般若波羅蜜多時、照見五蘊皆空、度一切苦厄。舎利子。色不異空、空不異色、色即是空、空即是色。受・想・行・識亦復如是。舎利子。是諸法空相、不生不滅、不垢不浄、不増不減。是故空中、無色、無受・想・行・識、無眼・耳・鼻・舌・身・意、無色・声・香・味・触・法。無眼界、乃至、無意識界。無無明、亦無無明尽、乃至、無老死、亦無老死尽。無苦・集・滅・道。無智亦無得。以無所得故、菩提薩埵、依般若波羅蜜多故、心無罣礙、無罣礙故、無有恐怖、遠離一切顛倒夢想、究竟涅槃。三世諸仏、依般若波羅蜜多故、得阿耨多羅三藐三菩提。故知、般若波羅蜜多、是大神呪、是大明呪、是無上呪、是無等等呪、能除一切苦、真実不虚。故説、般若波羅蜜多呪。 即説呪曰、羯諦羯諦波羅羯諦波羅僧羯諦菩提薩婆訶。般若心経

1
2
3
仏説摩訶般若波羅蜜多心経
観自在菩薩行深般若波羅蜜多時、照見五蘊皆空、度一切苦厄。舎利子。色不異空、空不異色、色即是空、空即是色。受・想・行・識亦復如是。舎利子。是諸法空相、不生不滅、不垢不浄、不増不減。是故空中、無色、無受・想・行・識、無眼・耳・鼻・舌・身・意、無色・声・香・味・触・法。無眼界、乃至、無意識界。無無明、亦無無明尽、乃至、無老死、亦無老死尽。無苦・集・滅・道。無智亦無得。以無所得故、菩提薩埵、依般若波羅蜜多故、心無罣礙、無罣礙故、無有恐怖、遠離一切顛倒夢想、究竟涅槃。三世諸仏、依般若波羅蜜多故、得阿耨多羅三藐三菩提。故知、般若波羅蜜多、是大神呪、是大明呪、是無上呪、是無等等呪、能除一切苦、真実不虚。故説、般若波羅蜜多呪。
即説呪曰、羯諦羯諦波羅羯諦波羅僧羯諦菩提薩婆訶。般若心経
2, 3, test