0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「control-structures」非同期制御構造モジュール作ってみた

Last updated at Posted at 2015-06-04

READMEはこれからだ

npm登録した。複数の引数の引き渡しをするようにしてテストして、とか適当にやっていたら、ずいぶんかかった。exception handlingが、変なハマリ方した。ブラウザから利用できるようにしようと色々やってたら、なおのことハマった。なのでREADMEはこれからだ!→書いた

原理や使ったときの見た目は、【保存版】制御構造別非同期プログラミング完全制覇(サーバサイドJavascript・CoffeeScript)を参照。このページにもおいおい書いてく。

機能一覧

simple continuation

_(firstargs,func1,func2,...)
引数に取った非同期関数を順次実行します
firstargs
func1に渡す引数を配列に納めたものです [arg1,arg2]を渡すと、func1(func2,arg1,arg2)が呼ばれます この配列の要素数は可変です
func1(next,...)
最初に実行する関数です。次へ進むときはこの関数内でnextを呼びます。 next(arg3,arg4) を実行すると、func2(func3,arg3,arg4)が呼ばれます この引数の要素数は可変です
func2(next,...)
以下、同様に好きな回数だけ続けられます。

用例はこちら。直前のブロックでのnextへの引数は、直後のブロックの追加引数になります。(この仕様は、他の関数も同じです)

PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._ ['arg1','arg2']
,(next,str1,str2) ->

  PRINT '[1st block]',->
    PRINT 'str1:'+str1,->
      PRINT 'str2:'+str2,->
        next 'err'

,(next,errstr) ->

  PRINT '[2nd block]',->
    PRINT 'errstr:'+errstr,->
      next()

,(next) ->

  PRINT '[last block]',->
    next()

if,switch後の合流もできます。

PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._ ['arg1','arg2']
,(next,str1,str2) ->

  PRINT '[1st block]',->
    if str1 == str2
      PRINT 'str1 and str2:'+str1,->
        next()
    else
      PRINT 'str1:'+str1,->
        PRINT 'str2:'+str2,->
          next()

,(next) ->

  PRINT '[2nd block]',->
    next()

Each (並行実行)

\_each(obj, applyargs_array, applyfunc, endfunc)
ArrayまたはObjectの各要素に、並行して同じ関数を適用します
obj
処理するArrayまたはObject
applyargs_array
applyfuncに渡すその他の引数を配列にして指定します。 [arg1,arg2]を指定すると、applyfunc(val,index,next,arg1,arg2)が呼ばれます。
applyfunc(val,index,next,...)
各要素に適用する関数です。objがArrayの場合、valは要素、indexは序数です。 処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて endfuncに渡されます。
applyfunc(val,index,next,...)
各要素に適用する関数です。objがObjectの場合、keyは要素のキー、valueは要素の値です。 処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて endfuncに渡されます。
endfunc(...)
結果を受け取る関数です。next('foo','bar')が2要素から呼ばれる場合、 endfunc(['foo','foo'],['bar','bar'])となります。配列内の順序は、各処理の 終了順です。弁別する必要のある場合、indexやkeyをnextの引数に加えて下さい。
PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._each [2,3,5,7,11]
,['arg1']
,(val,i,next,str1) ->

  PRINT 'apply',->
    next(val,i)

,(array_of_args_from_next1,array_of_args_from_next2) ->
  
  for num in array_of_args_from_next1
    console.log 'value: '+num.toString()
  for num in array_of_args_from_next2
    console.log 'counter: '+num.toString()
  next()

for Loop

\_for(index, f_judge, f_iter, firstarg_array, loopfunc, endfunc)
初期値・条件・イテレータを用いて、loopfuncを繰り返します。
index
カウンタの初期値です。
f_judge(n) 返値=true,false
カウンタに対する条件です。falseが返るとループが終了します。 ※この関数は同期関数でなければなりません。
f_iter(n) 返値=カウンタ値
カウンタのイテレータです。引数がカウンタの現在の値で、返値を返すと、 カウンタに代入されます。 ※この関数は同期関数でなければなりません。
firstarg_array
loopfuncへの初回の引数を、配列で示します。 [arg1,arg2]を渡すと、loopfunc(n,next,arg1,arg2)が呼ばれます。
loopfunc(n,\_break,\_next,...)
繰り返し実行する内容です。nはカウンタの現在値です。 続行する場合、\_next(...)を呼び出します。\_nextの引数は、次回のloopfuncの 呼び出しの際、引き渡されます。たとえば、\_next(arg3,arg4)を呼び出せば、 loopfunc(n,next,arg3,arg4)が呼ばれます。 繰り返しを中断する場合、\_break(...)を呼び出します。すると、 endfunc(n,...)が呼び出されます。
endfunc(n,...)
繰り返しの終了後に実行される関数です。nは終了時のカウンタの値です。
PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._for 0,((n)-> n<10),((n)-> n+1)
,[]
,(n,_break,_next) ->

  PRINT 'counter: '+n.toString(),->
    _next()

,(n) ->
  
  PRINT 'last counter: '+n.toString(),->
    return

for in Array or Object

\_for_in(obj, firstargs, loopfunc, endfunc)
配列またはオブジェクトの各要素に順々に関数を適用します。
firstargs
初回のloopfuncの引数を配列にして示します。 [arg1,arg2]が渡されると、loopfunc(key,val,\_break,\_next,arg1,arg2)が呼ばれます。
loopfunc(key,val,\_break,\_next,...)
繰り返し実行する関数です。Objectの場合、key,valが得られます 続行する場合\_next(...)、中断する場合\_break(...)を呼び出します。 \_nextの引数は、次回のloopfuncに渡されます。 \_breakの引数は、endfuncに渡されます。
endfunc(...)
繰り返しの終了時に呼ばれます。
PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._for_in {bro:'ani',sis:'imo',dad:'tousan',mom:'kaasan'}
,[] #first args
,(key,val,_break,_next) ->

  PRINT key+': '+val,->
    _next()

,->
  
  PRINT 'end',->
    return

while Loop

\_while(firstarg_array, loopfunc, endfunc)
繰り返しを行います。リトライループなどです。loopfuncの処理の終了時、\_breakと\_nextを呼び分ければ良いです。冒頭で分岐すれば普通のwhileのように、末尾(最奥)で分岐すればdo...whileのように振る舞います。
firstarg_array
初回のf_judge, loopfuncの引数を配列にして示します。 [arg1,arg2]が渡されると、f_judge(arg1,arg2)が評価され、 trueならば、loopfunc(\_break,\_next,arg1,arg2)が呼ばれ、 falseならば、endfunc(arg1,arg2)が呼ばれます。
loopfunc(\_break,\_next,...)
繰り返し行う非同期処理です。 繰り返しを中断するときは\_break(...)を呼ぶと、endfunc(...)が呼ばれます。 繰り返しを続行するときは\_next(...)を呼ぶと、f_judge(...)が評価され、 trueならば、loopfunc(\_break,\_next,arg1,arg2)が呼ばれ、 falseならば、endfunc(arg1,arg2)が呼ばれます。
endfunc(...)
次の処理です。上記の通り、引数を受け取ることができます。
PRINT = (str,next) -> console.log str; setTimeout(next, 200)

cs._while [25*25, 25*88]
,(_break,_next,arg1,arg2) ->

  PRINT arg1.toString(),->
    PRINT arg2.toString(),->
      if (arg2 % arg1) == 0
        _break(arg1)
      else
        _next(arg2 % arg1,arg1)

,(arg) ->
  
  PRINT 'result: '+arg.toString(),->
    return

exception handling

myexc = new exc
例外管理オブジェクトを生成します。ここではmyexcとします。
myexc.\_try(block_args_array,f_block,e_array,f_catch,f_finally,f_next)
例外スコープを開始します。実処理はblock内、例外処理はf_catch内、 終了処理はf_finally内で行い、f_nextへと進みます。 現在のところ、f_catch,f_finally内で\_throwすると、終了処理は完了しません
block_args_array
blockに渡す引数を、配列にして指定します。 [arg1,arg2]を渡すと、f_block(myexc,arg1,arg2)が呼ばれます。
f_block(...)
実処理を行うスコープです。このexc例外を発生しうる関数を 呼び出すときはmyexcを渡して下さい。次へ進むときは myexc.\_finally(...)を呼べば、f_finally(myexc,next,...)が呼ばれます。
e_array
f_catchで処理する例外の種類を列挙した配列です。 可読性のある文字列をお勧めします。
f_catch(_e,...)
\_throwに渡された例外の種類と、その他の\_throwに渡された引数が 得られます。e_arrayで列挙した例外は全て処理して下さい。 処理を終えたらmyexc.\_finally(...)を呼んで下さい。f_blockと同様です。
f_finally(next,...)
終了処理を簡潔に行います。処理を終えたら、next(...)を呼んで下さい。 f_next(...)が呼ばれます。基本的にはmyexcを引数に指定して下さい。
f_next(...)
次の処理を行う関数です。
myexc.\_throw(\_e,...)
例外を発生させます。引数はf_catchに渡されます。
myexc.\_finally(...)
例外スコープのスタックをpopして、ユーザ終了処理を呼び出します。 引数はf_finallyに渡されます。
PRINT = (str,next) -> console.log str; setTimeout(next, 200)

myexc = new cs.exc
myexc._try([]
,->
  #block
  PRINT 'NEST1-1',-> 
    PRINT 'NEST1-2',->
      myexc._try([]
      ,->
        #block
        PRINT 'NEST2-1',->
          PRINT 'NEST2-2',->
            if true
              myexc._throw 'err1'
            else
              PRINT 'NEST2-3',->
                myexc._finally()
      ,['err2','err3']
      ,(_e)->
        #catch
        console.log _e
        PRINT 'NEST2-CATCH',->
          myexc._finally()
      ,(fnext)->
        #finally
        PRINT 'NEST2-FINALLY',->
          fnext()
      ,->
        PRINT 'NEST1-3',->
          myexc._finally()
      )
,['err1']
,(_e)->
  #catch
  console.log _e
  PRINT 'NEST1-CATCH',->
    myexc._finally()
,(fnext)->
  #finally
  PRINT 'NEST1-FINALLY',
    fnext()
,next)

y combinator

y(closure)
closureの引数にclosure自身を渡します=再帰関数にします

無名クロージャを再帰関数にすることができます。

dummy = null
index = 10
(cs.y (func) ->
  return (dummy)->
    index -= 1
    A ->
      PRINT index.toString(),->
        if index == 0
          return next()
        else
          func(dummy)
)(dummy)

Todo

引数チェック
firstarg_arrayが配列でないor(null)のときの処理など 関数を取る場合のチェック&nullチェック(devのみ?)
返値チェック
イテレータや終了条件など、同期関数の返値チェック
ループ関数に同期関数を入れられたとき
stack overflowを避ける方法が有ればやる。さもなくば分かりやすくあぼん
Javascript版の無意味な返値を消去
楽勝なはず?

CoffeeScript版ソースコード

ん?引用だけだと右の目次に出ない?

control-structures.coffee
((root, factory) ->
  if FORCE_CLIENTSIDE?
    root.cs = factory()
  else if (typeof define == 'function' && define.amd)
    define([], factory)
  else if (typeof exports == 'object')
    module.exports = factory()
  else
    root.cs = factory()
)(this, () ->

  ###
     y combinator
  ###
  y: (func) ->
    return ((p) ->
      return ->
        return func(p(p)).apply(this,arguments)
    )((p) ->
      return ->
        return func(p(p)).apply(this,arguments)
    )

  ###
     simple continuation
     (継続のネストが深くなりすぎるのを防ぐため、
      または、if,switchの後、容易に合流するために用います)
     
     _(firstargs,func1,func2,...)
        引数に取った非同期関数を順次実行します
        firstargs
           func1に渡す引数を配列に納めたものです
           [arg1,arg2]を渡すと、func1(func2,arg1,arg2)が呼ばれます
           この配列の要素数は可変です
        func1(next,...)
           最初に実行する関数です。次へ進むときはこの関数内でnextを呼びます。
           next(arg3,arg4) を実行すると、func2(func3,arg3,arg4)が呼ばれます
           この引数の要素数は可変です
        func2(next,...)
           以下、同様に好きな回数だけ続けられます。
  ###
  _: ->
    args = [].slice.call(arguments)
    firstargs = args.shift()
    (@.y (func) ->
      return ->
        arg = args.shift()
        if arg?
          passargs = [].slice.call(arguments)
          passargs.unshift(func)
          arg.apply(this,passargs)
    ).apply(this,firstargs)

  ###
     Each (simultaneous)
     
     _each(obj, applyargs_array, applyfunc, endfunc)
        ArrayまたはObjectの各要素に、並行して同じ関数を適用します
        obj
           処理するArrayまたはObject
        applyargs_array
           applyfuncに渡すその他の引数を配列にして指定します。
           [arg1,arg2]を指定すると、applyfunc(val,i,next,arg1,arg2)が呼ばれます。
        applyfunc(val,i,next,...)
           各要素に適用する関数です。objがArrayの場合、valは要素、iは序数です。
           処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて
           endfuncに渡されます。
        applyfunc(val,i,next,...)
           各要素に適用する関数です。objがObjectの場合、keyは要素のキー、valueは要素の値です。
           処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて
           endfuncに渡されます。
        endfunc(...)
           結果を受け取る関数です。next('foo','bar')が2要素から呼ばれる場合、
           endfunc(['foo','foo'],['bar','bar'])となります。配列内の順序は、各処理の
           終了順です。弁別する必要のある場合、iやkeyをnextの引数に加えて下さい。
  ###
  _each: (obj, applyargs_array, applyfunc, endfunc) ->
    ((nextc) ->
      isarr = ('Array' == Object.prototype.toString.call(obj).slice(8, -1))
      num = 0
      next = nextc()
      if isarr
        for val,i in obj
          num++
          applyfunc.apply this,[val,i,->
            next.apply this,[num,endfunc].concat([].slice.call(arguments))
          ].concat(applyargs_array)
      else
        for key,val of obj
          num++
          applyfunc.apply this,[key,val,->
            next.apply this,[num,endfunc].concat([].slice.call(arguments))
          ].concat(applyargs_array)
    )(->
      count = 0
      result = []
      return (num,next) ->
        count++
        if arguments.length > 0
          args = [].slice.call(arguments).shift().shift()
          for arg,i in args
            if result.length <= i
              result.push([])
            result[i].push(arg)
        if count == num
          next.apply this,result
    )

  ###
     for Loop
     
     _for(i, f_judge, f_iter, firstarg_array, loopfunc, endfunc)
        初期値・条件・イテレータを用いて、loopfuncを繰り返します。
        i
           カウンタの初期値です。
        f_judge(n) 返値=true,false
           カウンタに対する条件です。falseが返るとループが終了します。
           ※この関数は同期関数でなければなりません。
        f_iter(n) 返値=カウンタ値
           カウンタのイテレータです。引数がカウンタの現在の値で、返値を返すと、
           カウンタに代入されます。
           ※この関数は同期関数でなければなりません。
        firstarg_array
           loopfuncへの初回の引数を、配列で示します。
           [arg1,arg2]を渡すと、loopfunc(n,break,next,arg1,arg2)が呼ばれます。
        loopfunc(n,_break,_next,...)
           繰り返し実行する内容です。nはカウンタの現在値です。
           続行する場合、_next(...)を呼び出します。_nextの引数は、次回のloopfuncの
           呼び出しの際、引き渡されます。たとえば、_next(arg3,arg4)を呼び出せば、
           loopfunc(n,next,arg3,arg4)が呼ばれます。
           繰り返しを中断する場合、_break(...)を呼び出します。すると、
           endfunc(n,...)が呼び出されます。
        endfunc(n,...)
           繰り返しの終了後に実行される関数です。nは終了時のカウンタの値です。
  ###
  _for: (i, f_judge, f_iter, firstarg_array, loopfunc, endfunc) ->
    if firstarg_array?
      firstarg_array = []
    (@.y (func) ->
      return ->
        if f_judge i
          loopfunc.apply this,[i,->
            #_break
            endfunc.apply this,[i].concat([].slice.call(arguments))
          ,->
            #_next
            i = f_iter i
            func.apply this,arguments
          ].concat([].slice.call(arguments))
        else
          endfunc.apply this,[i].concat([].slice.call(arguments))
    ).apply this,firstarg_array

  ###
     for in Array or Object
     
     _for_in(obj, firstargs, loopfunc, endfunc)
        firstargs
           初回のloopfuncの引数を配列にして示します。
           [arg1,arg2]が渡されると、loopfunc(key,val,_break,_next,arg1,arg2)が呼ばれます。
        loopfunc(key,val,_break,_next,...)
           繰り返し実行する関数です。Objectの場合、key,valが得られます
           続行する場合_next(...)、中断する場合_break(...)を呼び出します。
           _nextの引数は、次回のloopfuncに渡されます。
           _breakの引数は、endfuncに渡されます。
        endfunc(...)
           繰り返しの終了時に呼ばれます。
  ###
  _for_in: (obj, firstargs, loopfunc, endfunc) ->
    i = 0
    isarr = ('Array' == Object.prototype.toString.call(obj).slice(8, -1))
    if isarr
      indexlimit = obj.length
    else
      indexlimit = Object.keys(obj).length
    (@.y (func) ->
      return ->
        if i < indexlimit
          if isarr
            loopfunc.apply this,[obj[i],i, ->
              endfunc.apply this,arguments
            ,->
              func.apply this,arguments
            ].concat([].slice.call(arguments))
          else
            key = Object.keys(obj)[i]
            value = obj[key]
            loopfunc.apply this,[key, value, ->
              endfunc.apply this,arguments
            ,->
              func.apply this,arguments
            ].concat([].slice.call(arguments))
        else
          endfunc.apply this,arguments
    )(firstargs)

  ###
     while Loop
     
     _while(firstarg_array, loopfunc, endfunc)
        終了条件のみを指定した繰り返しを行います。リトライループなどです。
        firstarg_array
           初回のf_judge, loopfuncの引数を配列にして示します。
           [arg1,arg2]が渡されると、loopfunc(_break,_next,arg1,arg2)が呼ばれます。
        loopfunc(_break,_next,...)
           繰り返し行う非同期処理です。
           繰り返しを中断するときは_break(...)を呼ぶと、endfunc(...)が呼ばれます。
           繰り返しを続行するときは_next(...)を呼ぶと、loopfunc(_break,_next,arg1,arg2)が呼ばれます。
        endfunc(...)
           次の処理です。上記の通り、引数を受け取ることができます。
  ###
  _while: (firstarg_array, loopfunc, endfunc) ->
    if firstarg_array?
      firstarg_array = []
    (@.y (func) ->
      return ->
        loopfunc.apply this,[->
          #_break
          endfunc.apply this,[].slice.call(arguments)
        ,->
          #_next
          func.apply this,arguments
        ].concat([].slice.call(arguments))
    ).apply this,firstarg_array

  ###
     exception handling
     
     myexc = new exc
        例外管理オブジェクトを生成します。ここではmyexcとします。
     myexc._try(block_args_array,f_block,e_array,f_catch,f_finally,f_next)
        例外スコープを開始します。実処理はblock内、例外処理はf_catch内、
        終了処理はf_finally内で行い、f_nextへと進みます。
        現在のところ、f_catch,f_finally内で_throwすると、終了処理は完了しません
        block_args_array
           blockに渡す引数を、配列にして指定します。
           [arg1,arg2]を渡すと、f_block(myexc,arg1,arg2)が呼ばれます。
        f_block(myexc,...)
           実処理を行うスコープです。このexc例外を発生しうる関数を
           呼び出すときはmyexcを渡して下さい。次へ進むときは
           myexc._finally(...)を呼べば、f_finally(myexc,next,...)が呼ばれます。
        e_array
           f_catchで処理する例外の種類を列挙した配列です。
           可読性のある文字列をお勧めします。
        f_catch(myexc,_e,...)
           _throwに渡された例外の種類と、その他の_throwに渡された引数が
           得られます。e_arrayで列挙した例外は全て処理して下さい。
           処理を終えたらmyexc._finally(...)を呼んで下さい。f_blockと同様です。
        f_finally(myexc,next,...)
           終了処理を簡潔に行います。処理を終えたら、next(...)を呼んで下さい。
           f_next(...)が呼ばれます。基本的にはmyexcを引数に指定して下さい。
        f_next(...)
           次の処理を行う関数です。
     myexc._throw(_e,...)
        例外を発生させます。引数はf_catchに渡されます。
     myexc._finally(...)
        例外スコープのスタックをpopして、ユーザ終了処理を呼び出します。
        引数はf_finallyに渡されます。
  ###
  exc: class exc
    _stack = []
    constructor: ->

    _try: (block_args_array,block,e_array,arg_catch,arg_finally,arg_next) ->
      _stack.push({e_array:e_array,_catch:arg_catch,_finally:arg_finally,next:arg_next})
      block.apply this,block_args_array

    fa = ->
      stackpop = _stack.pop()
      stackpop._finally.apply this,[].slice.call(arguments)

    _finally: ->
      stackpop = _stack.pop()
      stackpop._finally.apply this,[stackpop.next].concat([].slice.call(arguments))

    y = (func) ->
      return ((p) -> return -> return func(p(p)).apply(this,arguments)
      )((p) ->       return -> return func(p(p)).apply(this,arguments)
      )
    
    _w = (firstarg_array, loopfunc, endfunc) ->
      if firstarg_array?
        firstarg_array = []
      (y (func) ->
        return ->
          loopfunc.apply this,[->
            #_break
            endfunc.apply this,[].slice.call(arguments)
          ,->
            #_next
            func.apply this,arguments
          ].concat([].slice.call(arguments))
      ).apply this,firstarg_array

    _throw: (_e) =>
      catchset = {}
      _w [],(_break,_next)=>
        catchset = _stack.pop()
        if catchset?
          _stack.push catchset
          result = catchset.e_array.indexOf _e
          if result != -1
            catchset._catch.apply this,[_e].concat([].slice.call(arguments))
          else
            if arguments.length <= 2
              fa.apply this,[_next]
            else
              fa.apply this,[].slice.call(arguments).shift().shift().unshift(_next)
        else
          _break()
      ,->
        console.log 'uncaught exception: '+_e.toString()
        process.exit 1

)

javascript版ソースコード

CoffeeScript版が安全な感じになったらこっち特有の変更もする。(余計な返値の抑制)

control-structures.js
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

(function(root, factory) {
  if (typeof FORCE_CLIENTSIDE !== "undefined" && FORCE_CLIENTSIDE !== null) {
    return root.cs = factory();
  } else if (typeof define === 'function' && define.amd) {
    return define([], factory);
  } else if (typeof exports === 'object') {
    return module.exports = factory();
  } else {
    return root.cs = factory();
  }
})(this, function() {
  var exc;
  return {

    /*
       y combinator
     */
    y: function(func) {
      return (function(p) {
        return function() {
          return func(p(p)).apply(this, arguments);
        };
      })(function(p) {
        return function() {
          return func(p(p)).apply(this, arguments);
        };
      });
    },

    /*
       simple continuation
       (継続のネストが深くなりすぎるのを防ぐため、
        または、if,switchの後、容易に合流するために用います)
       
       _(firstargs,func1,func2,...)
          引数に取った非同期関数を順次実行します
          firstargs
             func1に渡す引数を配列に納めたものです
             [arg1,arg2]を渡すと、func1(func2,arg1,arg2)が呼ばれます
             この配列の要素数は可変です
          func1(next,...)
             最初に実行する関数です。次へ進むときはこの関数内でnextを呼びます。
             next(arg3,arg4) を実行すると、func2(func3,arg3,arg4)が呼ばれます
             この引数の要素数は可変です
          func2(next,...)
             以下、同様に好きな回数だけ続けられます。
     */
    _: function() {
      var args, firstargs;
      args = [].slice.call(arguments);
      firstargs = args.shift();
      return (this.y(function(func) {
        return function() {
          var arg, passargs;
          arg = args.shift();
          if (arg != null) {
            passargs = [].slice.call(arguments);
            passargs.unshift(func);
            return arg.apply(this, passargs);
          }
        };
      })).apply(this, firstargs);
    },

    /*
       Each (simultaneous)
       
       _each(obj, applyargs_array, applyfunc, endfunc)
          ArrayまたはObjectの各要素に、並行して同じ関数を適用します
          obj
             処理するArrayまたはObject
          applyargs_array
             applyfuncに渡すその他の引数を配列にして指定します。
             [arg1,arg2]を指定すると、applyfunc(val,i,next,arg1,arg2)が呼ばれます。
          applyfunc(val,i,next,...)
             各要素に適用する関数です。objがArrayの場合、valは要素、iは序数です。
             処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて
             endfuncに渡されます。
          applyfunc(val,i,next,...)
             各要素に適用する関数です。objがObjectの場合、keyは要素のキー、valueは要素の値です。
             処理が終わったらnext(...)を呼び出します。nextの各引数は、配列に代入されて
             endfuncに渡されます。
          endfunc(...)
             結果を受け取る関数です。next('foo','bar')が2要素から呼ばれる場合、
             endfunc(['foo','foo'],['bar','bar'])となります。配列内の順序は、各処理の
             終了順です。弁別する必要のある場合、iやkeyをnextの引数に加えて下さい。
     */
    _each: function(obj, applyargs_array, applyfunc, endfunc) {
      return (function(nextc) {
        var i, isarr, j, key, len, next, num, results, results1, val;
        isarr = 'Array' === Object.prototype.toString.call(obj).slice(8, -1);
        num = 0;
        next = nextc();
        if (isarr) {
          results = [];
          for (i = j = 0, len = obj.length; j < len; i = ++j) {
            val = obj[i];
            num++;
            results.push(applyfunc.apply(this, [
              val, i, function() {
                return next.apply(this, [num, endfunc].concat([].slice.call(arguments)));
              }
            ].concat(applyargs_array)));
          }
          return results;
        } else {
          results1 = [];
          for (key in obj) {
            val = obj[key];
            num++;
            results1.push(applyfunc.apply(this, [
              key, val, function() {
                return next.apply(this, [num, endfunc].concat([].slice.call(arguments)));
              }
            ].concat(applyargs_array)));
          }
          return results1;
        }
      })(function() {
        var count, result;
        count = 0;
        result = [];
        return function(num, next) {
          var arg, args, i, j, len;
          count++;
          if (arguments.length > 0) {
            args = [].slice.call(arguments).shift().shift();
            for (i = j = 0, len = args.length; j < len; i = ++j) {
              arg = args[i];
              if (result.length <= i) {
                result.push([]);
              }
              result[i].push(arg);
            }
          }
          if (count === num) {
            return next.apply(this, result);
          }
        };
      });
    },

    /*
       for Loop
       
       _for(i, f_judge, f_iter, firstarg_array, loopfunc, endfunc)
          初期値・条件・イテレータを用いて、loopfuncを繰り返します。
          i
             カウンタの初期値です。
          f_judge(n) 返値=true,false
             カウンタに対する条件です。falseが返るとループが終了します。
             ※この関数は同期関数でなければなりません。
          f_iter(n) 返値=カウンタ値
             カウンタのイテレータです。引数がカウンタの現在の値で、返値を返すと、
             カウンタに代入されます。
             ※この関数は同期関数でなければなりません。
          firstarg_array
             loopfuncへの初回の引数を、配列で示します。
             [arg1,arg2]を渡すと、loopfunc(n,break,next,arg1,arg2)が呼ばれます。
          loopfunc(n,_break,_next,...)
             繰り返し実行する内容です。nはカウンタの現在値です。
             続行する場合、_next(...)を呼び出します。_nextの引数は、次回のloopfuncの
             呼び出しの際、引き渡されます。たとえば、_next(arg3,arg4)を呼び出せば、
             loopfunc(n,next,arg3,arg4)が呼ばれます。
             繰り返しを中断する場合、_break(...)を呼び出します。すると、
             endfunc(n,...)が呼び出されます。
          endfunc(n,...)
             繰り返しの終了後に実行される関数です。nは終了時のカウンタの値です。
     */
    _for: function(i, f_judge, f_iter, firstarg_array, loopfunc, endfunc) {
      if (firstarg_array != null) {
        firstarg_array = [];
      }
      return (this.y(function(func) {
        return function() {
          if (f_judge(i)) {
            return loopfunc.apply(this, [
              i, function() {
                return endfunc.apply(this, [i].concat([].slice.call(arguments)));
              }, function() {
                i = f_iter(i);
                return func.apply(this, arguments);
              }
            ].concat([].slice.call(arguments)));
          } else {
            return endfunc.apply(this, [i].concat([].slice.call(arguments)));
          }
        };
      })).apply(this, firstarg_array);
    },

    /*
       for in Array or Object
       
       _for_in(obj, firstargs, loopfunc, endfunc)
          firstargs
             初回のloopfuncの引数を配列にして示します。
             [arg1,arg2]が渡されると、loopfunc(key,val,_break,_next,arg1,arg2)が呼ばれます。
          loopfunc(key,val,_break,_next,...)
             繰り返し実行する関数です。Objectの場合、key,valが得られます
             続行する場合_next(...)、中断する場合_break(...)を呼び出します。
             _nextの引数は、次回のloopfuncに渡されます。
             _breakの引数は、endfuncに渡されます。
          endfunc(...)
             繰り返しの終了時に呼ばれます。
     */
    _for_in: function(obj, firstargs, loopfunc, endfunc) {
      var i, indexlimit, isarr;
      i = 0;
      isarr = 'Array' === Object.prototype.toString.call(obj).slice(8, -1);
      if (isarr) {
        indexlimit = obj.length;
      } else {
        indexlimit = Object.keys(obj).length;
      }
      return (this.y(function(func) {
        return function() {
          var key, value;
          if (i < indexlimit) {
            if (isarr) {
              return loopfunc.apply(this, [
                obj[i], i, function() {
                  return endfunc.apply(this, arguments);
                }, function() {
                  return func.apply(this, arguments);
                }
              ].concat([].slice.call(arguments)));
            } else {
              key = Object.keys(obj)[i];
              value = obj[key];
              return loopfunc.apply(this, [
                key, value, function() {
                  return endfunc.apply(this, arguments);
                }, function() {
                  return func.apply(this, arguments);
                }
              ].concat([].slice.call(arguments)));
            }
          } else {
            return endfunc.apply(this, arguments);
          }
        };
      }))(firstargs);
    },

    /*
       while Loop
       
       _while(firstarg_array, loopfunc, endfunc)
          終了条件のみを指定した繰り返しを行います。リトライループなどです。
          firstarg_array
             初回のf_judge, loopfuncの引数を配列にして示します。
             [arg1,arg2]が渡されると、loopfunc(_break,_next,arg1,arg2)が呼ばれます。
          loopfunc(_break,_next,...)
             繰り返し行う非同期処理です。
             繰り返しを中断するときは_break(...)を呼ぶと、endfunc(...)が呼ばれます。
             繰り返しを続行するときは_next(...)を呼ぶと、loopfunc(_break,_next,arg1,arg2)が呼ばれます。
          endfunc(...)
             次の処理です。上記の通り、引数を受け取ることができます。
     */
    _while: function(firstarg_array, loopfunc, endfunc) {
      if (firstarg_array != null) {
        firstarg_array = [];
      }
      return (this.y(function(func) {
        return function() {
          return loopfunc.apply(this, [
            function() {
              return endfunc.apply(this, [].slice.call(arguments));
            }, function() {
              return func.apply(this, arguments);
            }
          ].concat([].slice.call(arguments)));
        };
      })).apply(this, firstarg_array);
    },

    /*
       exception handling
       
       myexc = new exc
          例外管理オブジェクトを生成します。ここではmyexcとします。
       myexc._try(block_args_array,f_block,e_array,f_catch,f_finally,f_next)
          例外スコープを開始します。実処理はblock内、例外処理はf_catch内、
          終了処理はf_finally内で行い、f_nextへと進みます。
          現在のところ、f_catch,f_finally内で_throwすると、終了処理は完了しません
          block_args_array
             blockに渡す引数を、配列にして指定します。
             [arg1,arg2]を渡すと、f_block(myexc,arg1,arg2)が呼ばれます。
          f_block(myexc,...)
             実処理を行うスコープです。このexc例外を発生しうる関数を
             呼び出すときはmyexcを渡して下さい。次へ進むときは
             myexc._finally(...)を呼べば、f_finally(myexc,next,...)が呼ばれます。
          e_array
             f_catchで処理する例外の種類を列挙した配列です。
             可読性のある文字列をお勧めします。
          f_catch(myexc,_e,...)
             _throwに渡された例外の種類と、その他の_throwに渡された引数が
             得られます。e_arrayで列挙した例外は全て処理して下さい。
             処理を終えたらmyexc._finally(...)を呼んで下さい。f_blockと同様です。
          f_finally(myexc,next,...)
             終了処理を簡潔に行います。処理を終えたら、next(...)を呼んで下さい。
             f_next(...)が呼ばれます。基本的にはmyexcを引数に指定して下さい。
          f_next(...)
             次の処理を行う関数です。
       myexc._throw(_e,...)
          例外を発生させます。引数はf_catchに渡されます。
       myexc._finally(...)
          例外スコープのスタックをpopして、ユーザ終了処理を呼び出します。
          引数はf_finallyに渡されます。
     */
    exc: exc = (function() {
      var _stack, _w, fa, y;

      _stack = [];

      function exc() {
        this._throw = bind(this._throw, this);
      }

      exc.prototype._try = function(block_args_array, block, e_array, arg_catch, arg_finally, arg_next) {
        _stack.push({
          e_array: e_array,
          _catch: arg_catch,
          _finally: arg_finally,
          next: arg_next
        });
        return block.apply(this, block_args_array);
      };

      fa = function() {
        var stackpop;
        stackpop = _stack.pop();
        return stackpop._finally.apply(this, [].slice.call(arguments));
      };

      exc.prototype._finally = function() {
        var stackpop;
        stackpop = _stack.pop();
        return stackpop._finally.apply(this, [stackpop.next].concat([].slice.call(arguments)));
      };

      y = function(func) {
        return (function(p) {
          return function() {
            return func(p(p)).apply(this, arguments);
          };
        })(function(p) {
          return function() {
            return func(p(p)).apply(this, arguments);
          };
        });
      };

      _w = function(firstarg_array, loopfunc, endfunc) {
        if (firstarg_array != null) {
          firstarg_array = [];
        }
        return (y(function(func) {
          return function() {
            return loopfunc.apply(this, [
              function() {
                return endfunc.apply(this, [].slice.call(arguments));
              }, function() {
                return func.apply(this, arguments);
              }
            ].concat([].slice.call(arguments)));
          };
        })).apply(this, firstarg_array);
      };

      exc.prototype._throw = function(_e) {
        var catchset;
        catchset = {};
        return _w([], (function(_this) {
          return function(_break, _next) {
            var result;
            catchset = _stack.pop();
            if (catchset != null) {
              _stack.push(catchset);
              result = catchset.e_array.indexOf(_e);
              if (result !== -1) {
                return catchset._catch.apply(_this, [_e].concat([].slice.call(arguments)));
              } else {
                if (arguments.length <= 2) {
                  return fa.apply(_this, [_next]);
                } else {
                  return fa.apply(_this, [].slice.call(arguments).shift().shift().unshift(_next));
                }
              }
            } else {
              return _break();
            }
          };
        })(this), function() {
          console.log('uncaught exception: ' + _e.toString());
          return process.exit(1);
        });
      };

      return exc;

    })()
  };
});
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?