<<
<
2017 年 8 月
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

2016/1/1 から日記を書いています。
今まで 594 件の日記を書きました。

この日記で公開されている写真は 279 枚です。
良かったらギャラリーもご覧になってください。

素の AsyncTask は冗長過ぎる

例えば「重い処理をワーカースレッドで行い戻ってきた文字列を Toast で表示する」といった処理を AsyncTask で共通化せずに無名クラスで実装すると以下のようになる:

object : AsyncTask<Unit, Unit, String>() {
    override fun doInBackground(vararg p0: Unit?): String {
        Thread.sleep(3000)
        return "めっせーじ"
    }

    override fun onPostExecute(result: String?) {
        Toast.makeText(context, result, Toast.LENGTH_LONG).show()
    }
}.execute()

Kotlin かつちょっとした処理を書こうとしているだけなのにえらく冗長なコードになっている。 この問題点は AsyncTask が 3 つも raw type (ジェネリクスの型パラメーター) を要求する抽象クラスになっていることに加え、Kotlin の場合無名クラスを定義するのに object : (継承元クラス) といった割と仰々しい構文を使わなければならないことだ。

このような無名クラスを使うのではなく、Kotlin のシンプルなラムダ式を渡せばいいようにしたい。

SimpleAsyncTask

以下、内部的には AsyncTask を使用するが各種メソッドの override が必要な時のみラムダ式を渡せばいいように実装してみた:

class SimpleAsyncTask<R>(private val background: () -> R,
        private val pre: () -> Unit = {}, private val post: (R) -> Unit = {},
        private val cancelled: () -> Unit = {}) : AsyncTask<Unit, Unit, R>() {
    override fun onPreExecute() = pre()
    override fun doInBackground(vararg p0: Unit?): R = background()
    override fun onPostExecute(result: R) = post(result)
    override fun onCancelled() = cancelled()
}

AsyncTask が持つ経過表示を行うための機能 onProgressUpdate() はほとんど使用しないのでバッサリ切った。 また、パラメータを AsyncTask().execute(param) のように渡せる機能も共通化しないのであれば必要ないので、今回即時実行するケースのみにターゲットを絞り削った。 これで以下のように使える:

// 一番最初の AsyncTask の例と等価のコード
SimpleAsyncTask(background = {
    Thread.sleep(3000)
    "めっせーじ"
}, post = { Toast.makeText(context, it, Toast.LENGTH_LONG).show() }).execute()

あとすこし改善

かなりシンプルにはなったが AsyncTaskexecute() をコールし忘れて発火されないという凡ミスがよくある。 どうせパラメータを渡さないのだから execute() を書く必要はない。 そこで、以下の関数をトップレベルに配置する:

/**
 * SimpleAsyncTask を使用し非同期で実行する.
 *
 * @param R 非同期実行スレッドの戻り型
 * @param background 非同期実行処理
 * @param pre 非同期実行前処理 (未セットならば処理を行わない)
 * @param post 非同期実行後処理 (未セットならば処理を行わない)
 * @param cancelled 非同期実行キャンセル時処理 (未セットならば処理を行わない)
 */
fun <R> doAsync(background: () -> R, pre: () -> Unit = {}, post: (R) -> Unit = {}, cancelled: () -> Unit = {})
    = SimpleAsyncTask<R>(background, pre, post, cancelled).execute()

これで以下のように書ける:

doAsync(background = {
    Thread.sleep(3000)
    "めっせーじ"
}, post = { Toast.makeText(context, it, Toast.LENGTH_LONG).show() })

Kotlin の型推論が強力なおかげで <R> を明示する必要はない。 とてもシンプルで素晴らしい。

私が今までやってきたプログラミング言語としては Java, Kotlin, PHP, Swift, Python, VBA, JavaScript, TypeScript といったところで Scala, Haskell も勉強したことはあるが実際に作品で使用したことはない。 このうち仕事ですぐにでも使える自信があるのは、今仕事で使っているので当然ではあるのだが Kotlin, Java, PHP, JavaScript である。 TypeScript はタイピング向けサイトで実験的に使用しているのみで構文を詳しく覚えているわけではない……がそんなに難しいこともないので仕事で使うのもそんなに問題はないだろう。 Python に関してはこの Blog の開発言語であり個人的に好きな言語なのだが仕事では一切使用したことがないので習熟度もそれなりといった感じになっている。

そういえば一時期本気で Python プログラマになろうと転職を考えたこともあったのだが、結局今の職場で働いている。 勿論言語の選定も重要なのだが、開発で一番大切なのはプログラミング言語ではないことは明らかである。 デキるプログラマはどの言語を使っても綺麗なコードを書くし、逆もまた真であるといえる。 「どの配列を選んでもタイピング速度に大して影響はない」というタイピングに似ている。

習熟度も高く言語仕様としても好きな言語は今は Kotlin が出来たので (JetBrains や日本での普及を目指したエンジニアに) 正直感謝している。 Kotlin の全てが好きだとはとても言えないが Java に比べると非常にシンプルに書けとてもモダンなテイストのコードに仕上がる。 ただでさえ仕事のコーディングは大変なのだから、少しでも好きな言語・環境を使用して精神的な負担を軽減すべきだ。

社会人は仕事をしている時間が大半を占める。 その時間内で触れている言語が一番使用時間が長いということになるので望んでいるいないに関わらず一番上達する。

慣れている PHP で作るのか。 好きな Python を使うのか。 毎回悩ましい問題だ。 開発環境 (PhpStorm, PyCharm) も有料だ。 趣味で使うだけなのにそんなにお金をつぎ込むのもなかなか気が進まない……。

タイプウェルオリジナル総合 SG

ずっと上げることができなかった大小文字のみを今日やっと上げることができ、タイプウェルオリジナルの総合レベルが SG となった。 大小のみを上げることができたおかげで、一気に SG の中間までポイントが溜まった。 後は大小文字混合、すべてのキー、数字のどれかを程よく上げれば SF に到達できそうに見える。

正直練習を続けるのがなかなか厳しくなってきた印象ではあるが、オールラウンダーを目指しているので最低 SS で出来れば XJ 以上まで頑張りたいところだ。

順位 ポイント 総レ 達成日
- 935729 SG SH SG SI SE 17/08/14
- 920095 SH SJ SG SJ SF 17/08/09
- 913348 SI SJ SH SJ SH 17/08/06
593 900067 SJ A SH B SI 17/08/03

今日は迎え盆なので実家に行って墓参りをした。 やはり東京方面に帰ってくる時の渋滞はかなり凄かった……。

昼食は久々のお寿司を頂いた。 11:30 頃お店に行ったのだが、既に 4 組ほど待ちが発生していた。 12 時前に入ればすぐいけると思っていたので意外だった。 その後 30 分ほど待ってようやく席につけた。

旅行に行こうという話になって、まだ夏なので涼しいところがいいだろうという事で今月に蓼科高原に行くことにした。 今月家族旅行に行くのであれば尚更直近のツーリングはお預けになるだろうか。

天気予報的には良くなったが

連休に入る前は金曜、土曜と天気が悪いという予報だったが、今見ると天気としてはとりあえず雨は降らないということになっていた。 出かけても良かったのだが、昨日も適当な街乗りで済ませて今日もそうなりそうだったので自粛しておいた。 首都圏ツーリングプランを使うにしても 2 日間有効というのが曲者で、1 日だけ使うのは何かちょっと損した気分になるというのもある。

PHP か Python か

業務で使用するのは PHP や Java, Kotlin なのでそちらの勉強をしてもいいのだが、個人的には Python が好きなので Effective Python など引っ張り出してきて読んでいた。 実はこの自作 Blog も Python で出来ているのだが、そろそろ何かカスタマイズしてもいい頃か、とも思った。

PHP も 7.0, 7.1 になってかなり便利になったが、そうはいっても Python の言語仕様を見てしまうと依然として Python に惹かれるのは確かだ。

今日は久々にバイクに乗りたかったが、天気が悪そうだったので普通に街乗りをすることにした。 8 月とは思えないほど涼しく、メッシュジャケットだと少し肌寒いほどでバイクに乗るには最適な日ではあったが空模様がどうにも良くない……。

栃木の山にでも行こうかなとある程度北に行ったところで少し思案してそのまま高速で帰宅した。 高速の道路情報を参照すると下り線 (東京から栃木に行く方面) は物凄く渋滞していた。 3 連休初日で帰省する人が多いのだろう。

明日も天気が悪そうなので大人しく家でゆっくりすることになりそうである。

onSaveInstanceState 後に表示しようとするとクラッシュする DialogFragment

DialogFragment は例えば以下のように表示できる:

// MainActivity 内のコード
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    MyDialogFragment().show(supportFragmentManager, null)  // 表示処理
}

class MyDialogFragment : AppCompatDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog
            = AlertDialog.Builder(activity).setMessage("Hello World").create()
}

通常はこれで問題ないのだが、例えば以下のようにして画面を開いた直後に「スリープ状態にする」「画面を閉じる」など行うとクラッシュする:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // 5 秒待ってから表示. この間にスリープやバックグラウンドに回すとクラッシュ!!
    Handler().postDelayed({ MyDialogFragment().show(supportFragmentManager, null) }, 5000)
}

class MyDialogFragment : AppCompatDialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog
            = AlertDialog.Builder(activity).setMessage("Hello World").create()
}

上記のように数秒待ってからダイアログ表示することなどそうそうないと思われるかもしれないが、昨今は通信を伴ったアプリの開発が当たり前であり通信の結果やエラー表示をダイアログで行っていたりするとよくこのエラーを目にすることとなる。

クラッシュの内容は java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState といった感じで恐らく Fragment を使っている人であればよく見ているのではないかと思う。 要するに Activity や Fragment の onPause() から onResume() 間には Fragment (DialogFragment) を操作できないというわけであるが、これの回避が思ったより難しい。

onPause() から onResume() 間に操作しようとしている DialogFragment の show()dismiss()ouResume() の後に pending するという方法でうまく回避できたのでここに記しておく。

前提

今回は前提として私が Qiita に以前書いた DialogFragment の共通化処理を入れているものとする。 例えば以下のような MyDialogFragment が用意されているものとする:

class MyDialogFragment : AppCompatDialogFragment() {

    /** コールバック */
    private val mCallback: Callback by lazy {
        if (targetFragment is Callback) {
            targetFragment as Callback
        } else if (activity is Callback) {
            activity as Callback
        } else {
            throw UnsupportedOperationException()
        }
    }

    /** リクエストコード */
    private val mRequestCode: Int
        get() = if (arguments.containsKey("request_code")) arguments.getInt("request_code") else targetRequestCode

    /**
     * MyDialog で何か処理が起こった場合にコールバックされるリスナ.
     */
    interface Callback {

        /**
         * MyDialog で positiveButton, NegativeButton, リスト選択など行われた際に呼ばれる.
         *
         * @param requestCode MyDialogFragment 実行時 mRequestCode
         * @param resultCode DialogInterface.BUTTON_(POSI|NEGA)TIVE 若しくはリストの position
         * @param params MyDialogFragment に受渡した引数
         */
        fun onMyDialogSucceeded(requestCode: Int, resultCode: Int, params: Bundle) = Unit

        /**
         * MyDialog がキャンセルされた時に呼ばれる.
         *
         * @param requestCode MyDialogFragment 実行時 mRequestCode
         * @param params MyDialogFragment に受渡した引数
         */
        fun onMyDialogCancelled(requestCode: Int, params: Bundle) = Unit

        /**
         * MyDialog で multiChoice した際に呼ばれる.
         *
         * @param requestCode MyDialogFragment 実行時 mRequestCode
         * @param position チェック位置
         * @param checked チェックされたか否か
         */
        fun onMyDialogMultiChoiceClicked(requestCode: Int, position: Int, checked: Boolean) = Unit

        /**
         * MyDialog で dateSet した際に呼ばれる.
         *
         * @param requestCode MyDialogFragment 実行時 mRequestCode
         * @param year 年
         * @param month 月
         * @param date 日
         */
        fun onMyDialogDateSet(requestCode: Int, year: Int, month: Int, date: Int) = Unit
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val listener = DialogInterface.OnClickListener { _, which ->
            dismiss()
            mCallback.onMyDialogSucceeded(mRequestCode, which, arguments.getBundle("params"))
        }
        val isProgress = arguments.getBoolean("is_progress")
        val title = arguments.getString("title")
        val message = arguments.getString("message")
        val positive = arguments.getString("positive")
        val negative = arguments.getString("negative")
        val items = arguments.getStringArray("items")
        val multiChoiceItems = arguments.getStringArray("multi_choice_items")
        val multiChoiceItemValues = arguments.getBooleanArray("multi_choice_item_values")
        val date = arguments.getSerializable("date") as Date?
        isCancelable = arguments.getBoolean("cancelable")

        if (isProgress) {
            return ProgressDialog(activity).apply { message.let { setMessage(it) } }
        } else if (date != null) {
            val cal = Calendar.getInstance(Locale.getDefault())
            cal.time = date
            val picker = DatePickerDialog(activity, { _, year, month, date ->
                mCallback.onMyDialogDateSet(mRequestCode, year, month, date)
            }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE))
            title?.let { picker.setTitle(it) }
            message?.let { picker.setMessage(it) }
            return picker
        } else {
            val builder = AlertDialog.Builder(activity)
            title?.let { builder.setTitle(it) }
            message?.let { builder.setMessage(it) }
            positive?.let { builder.setPositiveButton(positive, listener) }
            negative?.let { builder.setNegativeButton(negative, listener) }
            items?.let { builder.setItems(items, listener) }
            multiChoiceItems?.let {
                builder.setMultiChoiceItems(multiChoiceItems, multiChoiceItemValues) {
                    _, position, checked ->
                    mCallback.onMyDialogMultiChoiceClicked(mRequestCode, position, checked)
                }
            }
            return builder.create()
        }
    }

    override fun onCancel(dialog: DialogInterface?) {
        mCallback.onMyDialogCancelled(mRequestCode, arguments.getBundle("params") ?: Bundle())
    }
}

通常のタイトルやメッセージを持ったダイアログ表示に加えて、複数のアイテムから 1 つを選択するものや複数チェックできるもの、あと DatePickerDialog での日付選択や ProgressDialog での経過表示も対応している。 これを呼ぶ場合用にもショートカット用のメソッドを以下のように用意する:

/**
 * MyDialogFragment を表示する.
 *
 * @param requestCode リスクエストコード
 * @param params Bundle パラメータ
 * @param title タイトル
 * @param message メッセージ
 * @param positive positiveButton ラベル
 * @param negative negativeButton ラベル
 * @param items シングルタップ選択用の items
 * @param multiChoiceItems 複数選択用の items
 * @param multiChoiceItemValues 複数選択用の items (値)
 * @param date DatePicker 用の初期値
 * @param tag Tag
 * @param cancelable キャンセル出来るか否か
 */
fun showDialog(requestCode: Int = -1, params: Bundle = Bundle(), title: Any? = null,
        message: Any? = null, positive: Any? = null, negative: Any? = null,
        items: Any? = null, multiChoiceItems: Any? = null, multiChoiceItemValues: BooleanArray? = null,
        date: Date? = null, tag: String = "default", cancelable: Boolean = true) {
    fun getStringArray(x: Any): Array<String> = when (x) {
        is Int -> resources.getStringArray(x)
        is ArrayList<*> -> x.toArray(arrayOfNulls(0))
        is Array<*> -> x.map(Any?::toString).toTypedArray()
        else -> throw IllegalArgumentException()
    }
    if (this !is MyDialogFragment.Callback) {
        throw UnsupportedOperationException("Using MyDialogFragment must implement Callback to Fragment")
    }
    val args = Bundle()
    title?.let { args.putString("title", if (it is Int) getString(it) else it.toString()) }
    message?.let { args.putString("message", if (it is Int) getString(it) else it.toString()) }
    items?.let { args.putStringArray("items", getStringArray(it)) }
    multiChoiceItems?.let { args.putStringArray("multi_choice_items", getStringArray(it)) }
    multiChoiceItemValues?.let { args.putBooleanArray("multi_choice_item_values", it) }
    date?.let { args.putSerializable("date", it) }
    positive?.let { args.putString("positive", if (it is Int) getString(it) else it.toString()) }
    negative?.let { args.putString("negative", if (it is Int) getString(it) else it.toString()) }
    args.putBoolean("cancelable", cancelable)
    args.putBundle("params", params)

    MyDialogFragment().apply { arguments = args }.show(supportFragmentManager, tag)
}

ちょっと複雑そうに見えるのは、各種引数をリソース ID と文字列の双方で渡せるように考慮を入れているからである (その為多くの型を Any? にしている)。 このメソッドを使用して前述の MyDialogFragment を show() するには以下のように書く:

// 5 秒待ってから表示
Handler().postDelayed({ showDialog(message = "Hello World") }, 5000)

この時点では onPause() 後の pending の対応が入っていないため先ほどと同じようにスリープにしたり画面を閉じたりすると IllegalStateException がスローされる。

対処

この対処を行う上で残念なのが結局 Fragment を拡張するようなパターンが必要 (BaseFragment のようなもの) になってしまうということだ。 どうしても onResume() と密に結びついてしまうので仕方がない。 これを避けようとすると作成した Activity や Fragment の onResume() の前に毎回お決まりのコードを入れなければならないので若干辛いものがある。

abstract class MyFragment : Fragment(), MyDialogFragment.Callback {

    /** 復帰のために貯めておく PTDialogFragment の表示データ */
    private val mDialogData: MutableList<Bundle> = mutableListOf()

    /** 復帰のために貯めておく dismissDialog のデータ */
    private val mDismissingTags: MutableList<String> = mutableListOf()

    /**
     * ProgressDialog を dismiss する.
     *
     * @param tag Tag
     */
    fun dismissDialog(tag: String = "progress") {
        if (isResumed) {
            fragmentManager.findFragmentByTag(tag)?.let {
                if (it is AppCompatDialogFragment) {
                    it.dismiss()
                } else {
                    throw UnsupportedOperationException()  // DialogFragment ではない
                }
            }
        } else {
            mDismissingTags += tag  // resume でない場合は pending する
        }
    }

    /**
     * ProgressDialog を表示する.
     *
     * @param message メッセージ
     * @param tag Tag
     * @param cancelable キャンセルできるか否か
     */
    fun showProgressDialog(message: Any? = null, tag: String = "progress", cancelable: Boolean = true) {
        val args = Bundle()
        args.putBoolean("is_progress", true)
        message?.let { args.putString("message", if (it is Int) getString(it) else it.toString()) }
        args.putBoolean("cancelable", cancelable)

        // resume でない場合は pending する
        if (isResumed) {
            with(MyDialogFragment()) {
                arguments = args
                setTargetFragment(this@MyFragment, 0)
                show(this@MyFragment.fragmentManager, tag)
            }
        } else {
            // 復帰させるために requestCode, tag を格納する
            args.putString("tag", tag)
            mDialogData += args
        }
    }

    /**
     * PTDialogFragment を表示する.
     *
     * @param requestCode リスクエストコード
     * @param params Bundle パラメータ
     * @param title タイトル
     * @param message メッセージ
     * @param positive positiveButton ラベル
     * @param negative negativeButton ラベル
     * @param items シングルタップ選択用の items
     * @param multiChoiceItems 複数選択用の items
     * @param multiChoiceItemValues 複数選択用の items (値)
     * @param date DatePicker 用の初期値
     * @param tag Tag
     * @param cancelable キャンセル出来るか否か
     */
    fun showDialog(requestCode: Int = -1, params: Bundle = Bundle(), title: Any? = null,
            message: Any? = null, positive: Any? = null, negative: Any? = null,
            items: Any? = null, multiChoiceItems: Any? = null, multiChoiceItemValues: BooleanArray? = null,
            date: Date? = null, tag: String = "default", cancelable: Boolean = true) {
        fun getStringArray(x: Any): Array<String> = when (x) {
            is Int -> context.resources.getStringArray(x)
            is ArrayList<*> -> x.toArray(arrayOfNulls(0))
            is Array<*> -> x.map(Any?::toString).toTypedArray()
            else -> throw IllegalArgumentException()
        }
        if (this !is MyDialogFragment.Callback) {
            throw UnsupportedOperationException("Using PTDialogFragment must implement Callback to Fragment")
        }

        // 既に detach されている場合は何もしない
        if (isDetached) return

        val args = Bundle()
        title?.let { args.putString("title", if (it is Int) getString(it) else it.toString()) }
        message?.let { args.putString("message", if (it is Int) getString(it) else it.toString()) }
        items?.let { args.putStringArray("items", getStringArray(it)) }
        multiChoiceItems?.let { args.putStringArray("multi_choice_items", getStringArray(it)) }
        multiChoiceItemValues?.let { args.putBooleanArray("multi_choice_item_values", it) }
        date?.let { args.putSerializable("date", it) }
        positive?.let { args.putString("positive", if (it is Int) getString(it) else it.toString()) }
        negative?.let { args.putString("negative", if (it is Int) getString(it) else it.toString()) }
        args.putBoolean("cancelable", cancelable)
        args.putBundle("params", params)

        // resume でない場合は pending する
        if (isResumed) {
            with(MyDialogFragment()) {
                arguments = args
                setTargetFragment(this@MyFragment, requestCode)
                show(this@MyFragment.fragmentManager, tag)
            }
        } else {
            // 復帰させるために requestCode, tag を格納する
            args.putInt("request_code", requestCode)
            args.putString("tag", tag)
            mDialogData += args
        }
    }

    override fun onResume() {
        super.onResume()
        mDialogData.forEach {
            with(MyDialogFragment()) {
                arguments = it
                setTargetFragment(this@MyFragment, it.getInt("request_code"))
                show(this@MyFragment.fragmentManager, it.getString("tag"))
            }
        }
        mDialogData.clear()
        mDismissingTags.forEach { dismissDialog(it) }
        mDismissingTags.clear()
    }
}

こんな MyFragment を用意する。 そうして以下のように MyFragment を継承した Fragment 上で MyDialogFragment を表示してみる:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    savedInstanceState ?: supportFragmentManager.beginTransaction()
            .add(android.R.id.content, AFragment())
            .commit()
}

class AFragment : MyFragment() {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        // 5 秒後表示. 今度はスリープしてもクラッシュせずに復帰時にちゃんと Dialog が表示される!!
        Handler().postDelayed({ showDialog(message = "Hello World") }, 5000)
    }
}

今度はクラッシュしないし、画面復帰時に正しく表示しようとしていた Dialog が表示される。 肝は Dialog を表示しようとする時に isResumed でなかったら tag で表示しようとしている Dialog の情報を保存し onResume() 後に改めて表示するというところだ。 これを共通ダイアログに対して行っておくとこのようなボイラープレートコードをあちこちに仕込まなくて済むようになるので有用である。

タイプウェルオリジナル総合 SH

今日もタイプウェルオリジナルの総合レベルを SH に上げることができた。 今のところは 3 日に 1 レベルペースを維持できているが、今後どんどん上げにくくなってくると思われるので、どこまでこのペースが保てるかが興味深いところである。

配点が高い大小文字のみを前回の更新から全く上げることが出来ていない。 圧倒的に一番練習を行っているにも関わらず上げることができないでいるのがなかなか辛いところだ。 一方、ほとんど練習をしていない数字はどんどんレベルが上がっていく。 数字は実用上役に立つ場面がほとんどないのであまりモチベーションが上がらないのが難点なのだが、大小文字のみが上がらなくて疲れたら気分転換にしつこくやっていこうかと思う。

尚、タイプウェルオリジナルばかりやっている副作用として QWERTY での英語タイピングの腕も上がっているように見受けられる。 タイプウェルオリジナルはランダム文字列であるが、どうも実用上全く役に立たないというわけではないらしい。

順位 ポイント 総レ 達成日
- 920095 SH SJ SG SJ SF 17/08/09
- 913348 SI SJ SH SJ SH 17/08/06
593 900067 SJ A SH B SI 17/08/03

自宅の浴槽が壊れたのでメーカーサポートに修理を頼んでいた。 浴槽の湯を抜く栓を開ける為のボタンがあるのだが、それが押せなくなってしまったという故障。 修理はものの 30 分弱で終わったのだが、修理代が 1 万 2 千円かかった……。 まぁ出張修理というのは高いもので、技術者の拘束時間 (移動時間も含む)、交通費、電話サポートの人件費、部品代、など加算していくとその位になってしまうのは致し方ない。 ともかくこれで浴槽が直ったので数日間シャワーを浴びていたところだが今日から風呂に入れるというものだ。 この暑い中風呂か、と思われるかもしれないが、日本人として昔から真夏でも浴槽に浸かっていたので、どうも風呂に入らないと落ち着かない。 習慣というものはそうそう逸脱できるものではないようだ。

修理の応対など行っていたのと台風 5 号の接近があったので今日は有給休暇としておいた。 久々に有給休暇を取得したのでなかなか楽しい。 世間、学生は夏休みモードであるが、普通の社会人はそう気軽に休みも取得できない……。

最近ゲリラ豪雨のような激しい雨が多い気がする。 今日も帰宅途中でそんなゲリラ豪雨に見舞われ濡れながら帰った。 途中にラーメン屋があったのでそこで雨宿りしながら食事。 自宅の近くにあるそのラーメン屋は地元に根ざしたチェーン店で家族連れなどが多く賑わっている……。 この店には久々に入ったのだが、普通のラーメンを頂き満足する。 別にすごく美味いというわけではないが、以前食べた時と同じ味、同じ感想である。 しかしそれがいい。

外で食べる場合というのは、常に変わらない同じ味、同じ値段というのが長所になり得る。 むしろコロコロ替わるようでは辛い。 消費税などの正当な理由を伴わない値上げなどもそうだ。 これがちゃんと説明がなされていればいいのだが、ある日突然メニューを見るとしれっと値段を上げてくる店もある。 こういう店には何度か行ったことがあるが誠実さの欠片も見えなくて本当に失望する。 外食に限った話ではないが、これも一つの人と人とのコミュニケーションの形なのではないかと思う。

タイプウェルオリジナル総合 SI

3 日ぶりにタイプウェルオリジナルの総合レベルを上げることができた。 一番出遅れたのは配点が多い大小文字のみであったが、何とか今日 1.2 秒ほど記録を縮めて SJ に乗せることが出来た。

自分の場合月配列を SJ から SS まで上げるのに 1 ヶ月と 20 日かかったのだが、実は途中で配列改造 (月配列 2-263 式から月配列 K へ) を入れてしまったので月配列 2-263 式のまま一直線に目指していたら 1 ヶ月と 10 日程度で達成できていたと思われる。 これと比較してどうか、というのが気になるところである。 何しろタイプウェルオリジナルの難易度に関する情報はネット上を探してもほぼ無いからだ。

大小文字のみは SJ が 50 秒で 1 秒間隔で進んでいき SS が 40 秒とレベルアップしていける。 J から SJ までと比べて一見難易度が下がったように見えるが、ここまで上げるのに結構苦労しているので全く油断は禁物である。

順位 ポイント 総レ 達成日
- 913348 SI SJ SH SJ SH 17/08/06
593 900067 SJ A SH B SI 17/08/03

山間部は天気が悪そう

今日は雨が降っているわけではなかったが、ネットで天気予報を見る限りだと山間部はどうも雲行きが怪しいように見える……。 先々週ゲリラ豪雨に遭って痛い目を見たばかりなので、今日はツーリングには出かけず軽く街乗りをした後でスーパーで買い物をして家でゆっくりすることにした。

ちなみに明日も天気があまり良くなさそうに見える……。 どうもいい天気に恵まれない。

スマホのドラクエセール中

ふと Google Play のスクウェア・エニックスのアプリ一覧を見るとドラクエが軒並み 30% オフになっていた。 そういえばドラクエ 3 が 1,200 円であったがちょっと高いなと思ってずっと保留にしておいたのだが、今日見ると 840 円になっている……。 ということで購入して遊ぶことにした。 世間ではドラクエ 11 が盛り上がっているところだが、私は PS4 も 3DS も所持していない。 ハードを買ってまで遊ぼうとは思わない。

ドラクエ 3 であるが、このソフトは私が少年の頃遊んだ思い出深いソフトである。 しばらくプレイしていなかったが、基本的な進め方やピラミッドの仕掛けなどちゃんと覚えているものだから驚いた。 それだけ熱中してプレイしていたということであるが……。

今時のドラクエなので戦闘中に敵がアニメーションするのかと思っていたのだが、そうではなかった。 まぁアニメーションなしのほうがテンポ良く進められるというのはあるが、SFC 版でもアニメーションがあったのに……と思うとちょっと寂しい。

それにしてもドラクエ 3 のゲームバランスは秀逸である。 スイスイ進めてダーマ神殿でレベル上げをする。

結論

あれから散々いじりまわしてみてああでもない、これも違う、などと試行錯誤してみた。 最終的にたどり着いたのが以下の配列である:

/* 単打 */
[
  |ふ|ら|ゅ|ゃ|  |  |も|ー|  |  |
そ|こ|し|て|ょ|つ|ん|い|の|り|「|」
は|か|  |と|た|く|う|  |゛|き|れ|
す|け|に|な|さ|っ|る|、|。|゜|  |
]

[d],[k][
ぅ|ぁ|ぃ|ぇ|ぉ|  |  |  |  |  |  |
  |ひ|ほ|  |め|ぬ|え|み|や|  |  |  
ち|を|  |あ|よ|ま|お|  |わ|ゆ|  |
  |へ|せ|  |  |む|ろ|ね|  |・|  |
]

-shift[
!|@|#|$|%|’|&|*|(|)|-|=|¥|
  |  |  |  |  |  |  |  |  |;|[|]
  |  |  |  |  |  |  |  |  |  |’|~
  |  |  |  |  |  |  |,|.|?|  |
]

なんと終わってみると総合 XH を取得した月配列 K の安定版と異なる点は「ち」をシフト側に移動したのみとなってしまった……。

まず「いうん」を QWERTY と同位置に合わせて混同を避けるという案だが思ったよりうまくいかなかった。 「う」の位置はやはり 2-263 式の J の位置が最善だと思ったのは、拗音の着地 (「しょう」「きょう」など) の打鍵感が U より J の方が良いのと文章の終わりが「ん」より「う」の方が圧倒的に多く (「~と思う。」「~に従う。」など) 、U. よりも J. の打鍵のほうが極めて自然だったのである。

今回特に悩んだのが長音「ー」の配置である。 何しろ月配列 K の安定版の位置だと「指を最上段に上げる」という挙動が QWERTY の - と同一の為混同に悩まされたからである。 この混同は右手小指を右に伸ばした位置に長音を配置した時は起きなかった。 そのため「ー」と「れ」の入れ替えを考えしばらく打ってみたわけだが、「れ」を薬指担当にしてしまうと「われ」というパターンが結構出てきて同指違鍵が発生してしまうことに気付いた。 「我々」「壊れる」「捕われる」「言われる」など結構出てくるのである。 また、小指側に移動した「ー」だが、これも小指担当の「き」と組み合わさった「キー」というパターンが結構出てきて結局小指連続パターンから逃れられないという結論になった。 では、思い切って「れ」をシフト側に移すか? しかし打鍵数が増えてしまう割にそんなにメリットがない……。 ということで前述の結論となったわけである。

但し今回の「ち」の移動には結構自信がある。 「ち」も月配列 K の歴史で何度となく動かしてきたかなであるが、ようやく定住場所を見つけたような感じだ。 以前の 7 のキーに対してシフト側に移動した KA という打鍵だが、この交互打鍵 KA は小指のほうがかなり短いため AK になってしまうというミスが起きにくくとても打ちやすい。 また A の位置から月配列 K の強みである各種拗音へと繋げやすい。

月配列 K は最上段も使用する四段配列であるが、最上段の中でも打ちにくい位置のキーである 1, 6, 7, 0 にはキーを配置していない。 実は 5 も打ちにくいのだが「ゃゅょっ」を全て単打に合わせるために頻度の低い「ゃ」を配置している。 固めておくことで「しゃ」「しゅ」「しょ」と同じような打鍵感でタイピングを楽しめるのが月配列 K のウリの 1 つである。

月配列 2-263 式はデファクトの名に恥じない出来

月配列 2-263 式は標準の状態でとても良く出来ている。 私も今回の変更で素の 2-263 式に戻そうか、と少し考えたほどである。

ただ、月配列 K の「シフトキー自体には文字を配置しない」、これが普段使いにおいてかなり効いてきたというのは今まで月配列 K を使ってきた感想であり、月配列 2-263 式に戻してしまうとまた連続シフトミスパターンに悩まされることになってしまう。 流石にそれだけは思いとどまったところだ。

タイプウェルオリジナル総合 SJ

タイプウェルオリジナルに関しては結構前から始めてはいたのだが、しばらくそのままにしていて 1 週間前ほどから本格的に再開しだした。 そして、今日遂に総合レベルにおいて SJ を達成することができた。 ここからが更に長い道のりの始まりと言えよう。

本気でオールラウンダーを目指していくとして Blog に SJ の段階からいつものように記録を付けていくことにする。 オリジナルがどの程度の難易度なのかというのは少し興味があるので、月配列の国語 K や Colemak の英単語と対比して見ていければと思う。

尚、今までの所感としては月配列を総合 SJ に上げる程度と同じか少し簡単な位ではないかと思う。 ただ、オリジナルに関しては種目ごとに大きく難易度差があり、使うキー数が少ない数字が一番簡単で、その次にレベル判定が甘い大小文字混在が来る。 難しいのはレベル判定が厳しい大 (小) 文字のみと使うキー数が多く右手小指連続が辛いすべてのキーだろう。

一番スコア配分が多い大 (小) 文字のみが難しいということで総合 SJ を目指す上での一番の壁は大小文字のみであることは間違いない。 タイプウェルのスコアの関係上ある程度満遍なく上げないといけないので、各種記号を日常的に打たない方は恐らくすべてのキーが一番難しいと思うことだろう。 但し、これはあくまで総合 SJ までの話であり、今後タイムが縮まっていくにつれてこの傾向は変わってくるものと思われる。

順位 ポイント 総レ 達成日
593 900067 SJ A SH B SI 17/08/03

今日はとても涼しかったのでバイクに乗ってみた。 帰りに雨に降られてしまったのだが、それでも涼しくて気持ちよく乗ることが出来た。

帰ってきたら先々週の最悪の雨だったツーリングの汚れを洗車で落とす。 バイクは車と違って洗う面積が狭いので、ただバケツに水汲んできて少しずつ水かけながら行うだけで大分綺麗になる。

週末の天気が回復傾向のようで嬉しい限りである。 可能であればまた安いビジネスホテルに宿泊するプランで 1 泊 2 日で行ってみたいが、悩ましいところだ。

モダン配列を使うのは楽がしたいから

Colemak を辞めてから 2 週間ほど月配列を使用せず QWERTY ローマ字のみで過ごしてみたのだが 1 年ほど使っていなかったにも関わらず最盛期に近い程度には速度が回復し月配列に近いくらいの速さになってしまった。 正直速さを求めるのであればローマ字でも JIS かな、月配列であってもそこまでは変わらない、というのが今までの結論だ。

月配列を使う理由は速さではない。 打鍵数を少なくして楽をしたいからのはずである。

「いうん」を QWERTY と同位置にすると「ん」が辛い

まず最初に書いておくが、かな出現頻度において「い」「う」「ん」は最上位に来るかなでありこれらの文字の配置は非常に重要である。

前回月配列 K の「いうん」をそれぞれ QWERTY の I, U, N と同位置に配置してみた。 「い」は元々の月配列 2-263 式でも QWERTY と同位置なのでいいのだが、問題は「う」と「ん」である。 QWERTY の U の位置はまずまず悪くない位置なのだが N の位置がいわゆる伸ばす位置であまり良くない。 ローマ字で散々打鍵しているのだが、それでも QWERTY でいうところの J の位置の方がいいに決まっている。

ということで、今回は「ん」だけどうしても QWERTY と異なる位置になってしまうのだが、月配列 2-263 式の「うとんが微妙に異なる」よりは「んだけ異なる」方が好ましいか、ということで素直に「う」と「ん」だけ入れ替えることにした。 あと「ん」を N の位置に持ってきてしまうと代わりに小書き「っ」というあまり嬉しくない文字を J に持ってきたりしなければならないという点もある。

「ち」の所在が困る

月配列 K で一番悩んでいるのが、右手小指連続パターンの脱却のために JIS キーボードでいうところの @ のキーからリストラした「ち」のかなである。 何しろ「ち」は以下の特性を持つので配置出来る場所が限られてしまう:

前者をクリアするには右手側に置くのが一番なのだが、後者もクリアしようとすると意外と置く位置に困ってしまう……。 以前の月配列 K では 7 のキーに置いていたのだが、この位置も最善とはいい難く、例えば「運賃」のようなワードで大幅に減速してしまうし、「ぢ」も実はそんなに打ちやすくはない。 今回はシフト側 A の位置に置いてみることにした。 試しに KAT, KA4, KA5, KAK4 の運指を試してみたが悪くはない。

「ら」「も」はオリジナルからそのまま上に持ってきた位置が最善

「ら」「も」に関しては月配列 K の歴史の中で何度も移動してきたのだが、結局オリジナルの月配列 2-263 式に対してそのまま上に持ってきた位置が一番打ちやすいという結論に達した。 例えば 8 のキーに「ら」を配置してしまうと直下にある「い」との相性が悪く、「依頼」などの割と多くのワードで同指違鍵パターンが発生してしまう。

長音や中黒、カギ括弧などはローマ字と同位置とする

他、長音や中黒、カギ括弧などは可能な限り QWERTY ローマ字と同位置とした。 そのまま配置できない場合はシフト側にした。

月配列 K

/* 単打 */
[
  |ふ|ら|ゅ|ゃ|  |  |も|を|  |ー|
そ|こ|し|て|ょ|つ|う|い|の|り|「|」
は|か|  |と|た|く|ん|  |゛|き|れ|
す|け|に|な|さ|っ|る|、|。|゜|  |
]

[d],[k][
ぅ|ぁ|ぃ|ぇ|ぉ|  |  |  |  |  |  |
  |ひ|ほ|  |め|ぬ|え|み|や|  |  |  
ち|  |  |あ|よ|ま|お|  |わ|ゆ|  |
  |へ|せ|  |  |む|ろ|ね|  |・|  |
]

-shift[
!|@|#|$|%|’|&|*|(|)|-|=|¥|
  |  |  |  |  |  |  |  |  |;|[|]
  |  |  |  |  |  |  |  |  |  |’|~
  |  |  |  |  |  |  |,|.|?|  |
]