MySQLで文字列結合しようとしたら何も表示されなかった話

こんにちは~。たにすぎです。
入社してそろそろ3か月経ちますが、入ったときには既にみんなマスク着用だったので、何かの拍子に外した姿を見ると「誰だ……?」と2秒くらい固まります。
いやあ、早く素顔で笑える日が来てほしいですね。

さて、タイトルで既に原因お分かりの方もいるかと思いますが、
NULLな文字列をCONCATでつなげたら悲しい思いをしたのでその話をします。

今回の状況

「苗字(半角スペース)名前」の形で氏名を表示する時に何も考えずにCONCATでつなげた結果を表示しようとしたら痛い目にあいました。
こういうデータがあって、 サンプルのユーザーテーブル 以下のようなクエリを書きました。
SELECT CONCAT(name_last, ' ', name_first) AS name FROM scores s
 

結果

素直にconcatだけ使った結果
あれ……? 1番下が何も表示されないな。

なんで消えちゃうの

思ってたのと違う結果になったのでドキュメント読みます。 https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_concat
引数のいずれかかが NULL である場合、CONCAT() は NULL を返します。
はい。
つなげる文字列の中で1つでもNULLがあると、結果がNULLになるんですね。知らなかった……

書き換え案

氏名で片方がない場合はあまりなさそうですが、住所で建物名がないとかは割とありそうなのでなんとかしたいです。 

IFNULL

IFNULLをつかってNULLの場合は空に置き換えてから繋ぐように書き換えてみます。
SELECT
CONCAT(name_last, ' ', name_first) AS 'IFNULL使ってないname',
CONCAT( IFNULL(name_last,''), ' ', IFNULL(name_first, '') ) AS 'IFNULL使ったname'
FROM users u
できたできた。

COALESCE

COALESCEでも結果は同じだけど、なんだかもったいないような……
SELECT
CONCAT(name_last, ' ', name_first) AS 'そのままCONCAT',
CONCAT( COALESCE(name_last,''), ' ', COALESCE(name_first, '') ) AS 'COALESCE使ったname'
FROM users u
coalesceで繋いだ例

GROUP_CONCAT

GROUP_CONCATは複数行の結果をカンマ区切りで返してくれる関数です。
こちらはどうなるんでしょう?
SELECT GROUP_CONCAT(name_last), GROUP_CONCAT(name_first)
FROM users
group_concatにnull
あ、GROUP_CONCATの方はNULLは気にしないでくれるみたいですね。
https://dev.mysql.com/doc/refman/5.6/ja/group-by-functions.html#function_group-concat

おわりに

めんどくさがるのよくないですね!
何らかの理由でIF_NULLが使えない場合はCASEとIS_NULLで対応したらいいのかな……

おまけ: PostgreSQLの場合は?

これは完全に言い訳なんですが、ここ数年はPostgreSQLをメインに使っていて、そっちはNULL含む項目をCONCATで繋げても無視した結果を返してくれていたんです……
でもそういえばCONCAT使わずに「||」(パイプ)で繋げた場合はNULLになってましたね……

SELECT CONCAT(name_last, ' ', name_first) AS "CONCATの結果", name_last || ' ' || name_first AS "パイプの結果" FROM users

vim+uniteからneovim+deniteへの移行

社内の傾向としてはエディタはIntelliJやvscodeが主流で、私は元々vi系エディタのユーザなので最近はvscodeをvimのキーバインドで使ってます。

IDEは多機能なのはいいんですが、正直使いこなせてない感がすごいし、ぶっちゃけほぼeclipseしか使ったことない。
vscodeはvimキーバインドでもかなり使えるんですが、ちょいちょい気になる瞬間があり、なによりエディタは慣れたやつのほうがいい。
というか正直vimも使い切れてない。なので、まあそれならと比較的指が慣れてるvimの実装の一つでありずっと気になっていたneovimをこのブログ書く機会に一念発起してつかってみることにしました。

ubuntu18.04でapt使って入れようとすると、何もしないとneovimのバージョンが0.2系なのですが、リポジトリを追加することでneovim0.3系がinstallできます。その前にpython関係のやつもわすれずにいれときます。
sudo apt-get install python-dev python-pip python4-dev python3-pip 
sudo apt-get install software-properties-common 
sudo add-apt-repository ppa:neovim-ppa/stable 
sudo apt-get update 
sudo apt-get install neovim
今まで利用していた.vimrcをそのまま.config/nvim/init.vimにコピーしてまずは起動してみると、起動自体はできたのですが、プラグインを動かそうとするとTERMの設定がおかしというエラーが。
:checkhealthしてみろ、と親切なメッセージがでていたのでやってみると、TERMの設定がtmuxとzshの間で設定が異なっているのが問題だったらしく以下の設定を追加。

.tmux.conf
set-option -g default-terminal "screen-256color"
 
vimのpluginとしておなじみ便利なuniteをつかってたんですが、この機会に後継?っぽいdeniteを使ってみることにします。(これがneovim0.2系だと動かない
 
.vimrc
"unite.vim関連


" バッファ一覧
nnoremap <silent> ,ub :<C-u>Unite buffer<CR>
" ファイル一覧
nnoremap <silent> ,uf :<C-u>UniteWithBufferDir -buffer-name=files file<CR>
" レジスタ一覧
nnoremap <silent> ,ur :<C-u>Unite -buffer-name=register register<CR>
" 最近使用したファイル一覧
nnoremap <silent> ,um :<C-u>Unite file_mru<CR>
" 常用セット
nnoremap <silent> ,uu :<C-u>Unite buffer file_mru<CR>
" 全部乗せ
nnoremap <silent> ,ua :<C-u>UniteWithBufferDir -buffer-name=files buffer file_mru bookmark file<CR>


" ウィンドウを分割して開く
au FileType unite nnoremap <silent> <buffer> <expr> <C-j> unite#do_action('split')
au FileType unite inoremap <silent> <buffer> <expr> <C-j> unite#do_action('split')
" ウィンドウを縦に分割して開く
au FileType unite nnoremap <silent> <buffer> <expr> <C-l> unite#do_action('vsplit')
au FileType unite inoremap <silent> <buffer> <expr> <C-l> unite#do_action('vsplit')
" ESCキーを2回押すと終了する
au FileType unite nnoremap <silent> <buffer> <ESC><ESC> :q<CR>
au FileType unite inoremap <silent> <buffer> <ESC><ESC> <ESC>:q<CR>
uniteの時につかってた設定。これを。。。
 
init.vim
" denite.vim関連


" バッファ一覧
nnoremap <silent> ,ub :<C-u>Denite buffer<CR>
" ファイル一覧
nnoremap <silent> ,uf :<C-u>DeniteWithBufferDir -buffer-name=files file<CR>
" レジスタ一覧
nnoremap <silent> ,ur :<C-u>Denite -buffer-name=register register<CR>
" 最近使用したファイル一覧
nnoremap <silent> ,um :<C-u>Denite file_mru<CR>
" 常用セット
nnoremap <silent> ,uu :<C-u>Denite buffer file_mru<CR>
" 全部乗せ
nnoremap <silent> ,ua :<C-u>DeniteBufferDir -buffer-name=files buffer file_mru bookmark file<CR>


" ウィンドウを分割して開く
au FileType denite nnoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
au FileType denite inoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
" ウィンドウを縦に分割して開く
au FileType denite nnoremap <silent> <buffer> <expr> <C-l> denite#do_action('vsplit')
au FileType denite inoremap <silent> <buffer> <expr> <C-l> denite#do_action('vsplit')
" ESCキーを2回押すと終了する
au FileType denite nnoremap <silent> <buffer> <ESC><ESC> :q<CR>
au FileType denite inoremap <silent> <buffer> <ESC><ESC> <ESC>:q<CR>
 
こうだなきっと(uniteをdeniteに変えただけ

なんとこんな雑な変換のやり方でも一覧系はだいたい全部うごいたんですが、ファイル一覧だけ動かない。DeniteWithBufferDirがないっぽいので、:help DeniteしてみてみるとDeniteBufferDirという名前でほしいのが取れそう。
 
なので該当箇所をこれに変更します。あとなぜかbookmarkがなかったので全部乗せからbookmarkも外しときます(uniteだと定義がなくてもエラーになってなかった
 
init.vim
" ファイル一覧
nnoremap <silent> ,uf :<C-u>DeniteBufferDir -buffer-name=files file<CR>
 :
" 全部乗せ
nnoremap <silent> ,ua :<C-u>DeniteBufferDir -buffer-name=files buffer file_mru file<CR>
 
これだけだと動かなくて、checkhealthした際に出ていたアドバイスに従って、:UpdateRemotePluginを実行。これでDenite自体は起動できました。

ただ、なんかinsertモードから戻るときのescapeの反応が異様に遅い。。
しらべてるとtmuxのescape-timeのせいだという記事をみかけたのでとりあえずそれのせいにしとこう。

.tmux.conf
set -s escape-time 10
 
よしこれで終わりかと思ったら、deniteのbufferから該当箇所にとべない?
denite.txtにあるexampleからまるっと以下の設定をコピー

init.vim
  autocmd FileType denite call s:denite_my_settings()
  function! s:denite_my_settings() abort
    nnoremap <silent><buffer><expr> <CR>
    \ denite#do_map('do_action')
    nnoremap <silent><buffer><expr> d
    \ denite#do_map('do_action', 'delete')
    nnoremap <silent><buffer><expr> p
    \ denite#do_map('do_action', 'preview')
    nnoremap <silent><buffer><expr> q
    \ denite#do_map('quit')
    nnoremap <silent><buffer><expr> i
    \ denite#do_map('open_filter_buffer')
    nnoremap <silent><buffer><expr> <Space>
    \ denite#do_map('toggle_select').'j'
  endfunction
 
。。。ってここで気づいたんですが分割して開くやつも動いてなかった。
 
 
au FileType denite nnoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
これをこんなかんじでdenite_my_settingsの中に張り付けなおしていきます。
 
init.vim
autocmd FileType denite call s:denite_my_settings()
function! s:denite_my_settings() abort
             :
  nnoremap <silent><buffer><expr><C-j> denite#do_map('do_action','split')
             :
endfunction
これで検索結果のバッファからEnterキーでとんだりsplitして画面を開いたりできるようになりました。


ついでに最近でも開発が続いてるripgrepをdeniteから使うようにしてみたいと思います。
つかってるubuntuのバージョンだとaptにはなかったし、snapは利用できない環境だし、しょうがないのでdebian向けのパッケージでいれます。
curl -LO https://github.com/BurntSushi/ripgrep/releases/download/11.0.2/ripgrep_11.0.2_amd64.deb
sudo dpkg -i ripgrep_11.0.2_amd64.deb

以下の設定をinit.vimに追加。
 
init.vim
nnoremap <silent> ,ug :<C-u>Denite grep -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,uw :<C-u>DeniteCursorWord grep -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,ue :<C-u>Denite -resume -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,un :<C-u>Denite -resume -buffer-name=grep-buffer-denite -select=+1 -immediately<CR>
nnoremap <silent> ,up :<C-u>Denite -resume -buffer-name=grep-buffer-denite -select=-1 -immediately<CR>


call denite#custom#var('grep', 'command', ['rg'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'pattern_opt', [])
call denite#custom#var('grep', 'default_opts', ['-S', '--vimgrep','--no-heading'])
 

これでripgrep結果のdenite bufferからファイルにとんだり、キャッシュした検索結果からfilterして探したりとできるようになりました。

これだけだとなんなので、次に補完機能などを強力にしてくれるvim-lspを導入します。
vim-lsp-settingsで簡略化できるようですが、まずはそのありがたみと、素の設定のめんどくささを知るためにそのまま設定してみます。とりあえずtypescriptとphpでチャレンジ。
vundleユーザなので以下をpluginとして追加。
asynccompleteでtypescriptを補完するためにtscompletejob、asynccomplete-tscompletejob(長い)を入れます
 
init.vim
Plugin 'prabirshrestha/async.vim'
Plugin 'prabirshrestha/vim-lsp'
Plugin 'prabirshrestha/asynccomplete.vim'
Plugin 'prabirshrestha/asynccomplete-lsp.vim'
Plugin 'runoshun/tscompletejob'
Plugin 'prabirshrestha/asynccomplete-tscompletejob.vim'
このあたり(https://github.com/prabirshrestha/vim-lsp/wiki/Servers-TypeScript)を参考に入れていきます。まずはlanguageサーバをインストール。*1
npm install -g typescript-language-server intelephense
plugin設定に追加。intelephenseはプラグイン設定いらない模様。
 
init.vim
Plugin 'ryanolsonx/vim-lsp-typescript'
各language server設定。このあたりは記載されてるまま。*2
init.vim
" php 
if executable('intelephense')
  augroup LspPHPIntelephense
    au!
    au User lsp_setup call lsp#register_server({
        \ 'name': 'intelephense',
        \ 'cmd': {server_info->[&shell, &shellcmdflag, 'intelephense --stdio']},
        \ 'whitelist': ['php'],
        \ 'initialization_options': {'storagePath': '/tmp/intelephense'},
        \ 'workspace_config': {
        \   'intelephense': {
        \     'files': {
        \       'maxSize': 1000000,
        \       'associations': ['*.php', '*.phtml'],
        \       'exclude': [],
        \     },
        \     'completion': {
        \       'insertUseDeclaration': v:true,
        \       'fullyQualifyGlobalConstantsAndFunctions': v:false,
        \       'triggerParameterHints': v:true,
        \       'maxItems': 100,
        \     },
        \     'format': {
        \       'enable': v:true
        \     },
        \   },
        \ }
        \})
  augroup END
endif



" typescript
if executable('typescript-language-server')
    au User lsp_setup call lsp#register_server({
        \ 'name': 'typescript-language-server',
        \ 'cmd': {server_info->[&shell, &shellcmdflag, 'typescript-language-server --stdio']},
        \ 'root_uri':{server_info->lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'tsconfig.json'))},
        \ 'whitelist': ['typescript', 'typescript.tsx'],
        \ })
endif
 
vim-lspの設定。これもとりあえずはそのまま。g dでカーソル箇所のワードの定義箇所を探してくれます便利。F2でシンボル一括リネーム。
 
init.vim
function! s:on_lsp_buffer_enabled() abort
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    nmap <buffer> gd <plug>(lsp-definition)
    nmap <buffer> <f2> <plug>(lsp-rename)
    " refer to doc to add more commands
endfunction


augroup lsp_install
    au!
    " call s:on_lsp_buffer_enabled only for languages that has the server registered.
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
 
エラー表示と補完候補のリフレッシュタイミングの設定はいろいろためしてみましたが、結局デフォルトの挙動が好みだったのと、popup更新もう少しゆっくりでもいいかなってことでg:asyncomplete_popup_delayの値を少し増やすだけにしました。
 
で、結局lsp-serverと補完の設定とか、言語を追加するたびにペタペタやらんといかんわけですね。手順は決まってるといえ、確かに長いし、なにより面倒くさい。そこでvim-lsp-settingsの出番。
 
init.vim
Plugin 'mattn/vim-lsp-settings'
を:VundleInstallして、
 
init.vim
if empty(globpath(&rtp, 'autoload/lsp.vim'))
  finish
endif
 
でおわり。*1,*2の設定はいらなくなります。 あとは使いたい言語編集時に:LspInstallServerするようにすれば裏で設定してくれます。ありがたや。
 
 
思ったより長くなってしまったので、ここまでにしたいと思います。最後までご覧いただきありがとうございました。

AsciiDoc使ってみた

こんにちわ。コロナウイルスの影響で電車がガラガラの日々が続いていましたが、GW明けから徐々に出勤している人が増えているみたいですね。
電車混まないでえええ!!ということで、テックブログ今週の担当はuraです。

ドキュメント作るのつらい

皆さんは仕様書やマニュアルなどのドキュメントを作りますか?
私は現在受注プロジェクトのPMをしており、頻繁にドキュメントを作る機会があります。
一般的に、ドキュメントはWordやExcel等で作成するかと思います。WordやExcelは見やすいですよね。好きですか?私は嫌いです。

  • Gitで履歴管理しても簡単に文章を検索できない。差分も見えない。
  • チームで同じファイルを同時に編集しづらい。(GoogleDocsを使えばなんとかなるけど)
  • 書式を整えるのが面倒。気づいたら一部だけフォントが異なるとかよくある。
  • 顧客用のドキュメントであれば、修正の度にファイルを送信する必要がある。
ではどうするか?Markdownで作成してGitで管理しよう。
いちいちファイルを送るのは面倒なので、GitにプッシュしたらHTMLに変換してWebページに公開しよう。

いや、でもMarkdownだとテーブルのセル結合すらできないから、ちょっと複雑なドキュメントになると表現力が足りない・・・。

そんなことを考えているときに見つけたのが、今回のテーマのAsciiDocです。

AsciiDocとは?

Asciidocは軽量マークアップ言語の一つです。Markdownの仲間ですね。
以下のような採用事例があります。

Markdownと比較すると以下のような点が優れています。

  • テーブルのセル結合ができる
  • 相互参照が使える
  • 外部ファイルを読み込める(ソースコードをそのまま読み込んで表示したり、分けて作成したドキュメントをマージしたり・・・)
  • PlantUMLを直接書ける

使ってみる

それではさっそく使ってみます。

セクション

「=」の数で表現します。
== セクション1
=== セクション1.1
==== セクション1.1.1
== セクション2
== セクション3

リスト

「*」の数で表現します。
* リスト1
* リスト2
** インデント
*** さらにインデント

テーブル

[cols="1a,1a,1a",options="autowidth"]
|===
|1A|1B|1C
2+|「数値+」で横結合|2C
.2+|「.数値+」で縦結合|3B|3C
|4B
|* 4C
* リストを
* 入れることも可能
|===

画像

image::https://techblog.imagemagic.jp/wp-content/uploads/2018/11/%E6%8E%A1%E7%94%A8%E3%83%90%E3%83%8A%E3%83%BC.png[alt=代替テキスト, width="200" align="center"]

アンカー

アンカーは二重の「[」で表現します。
[[hogehoge]]

リンク

link:https://imagemagic.jp/recruit/[イメージ・マジックではエンジニアを募集中です。]

<<hogehoge, アンカーへのリンクは二重の「<」で表現します。>>

外部ファイルの読み込み

include::hogehoge.adoc[]

まとめ

AsciiDocの特徴や簡単な使い方について紹介しました。
現在マニュアル作成で使っていますが、やはりWordよりはラクをさせてもらっています。

インストール方法や詳しいリファレンスについては公式サイトをご覧ください。

デザインに使える比率の話

皆さんこんにちは。イメージマジック三浦です。 新型コロナウイルスに対して、弊社でも在宅勤務等の感染予防対策が進んできて今までと違う状況が日常になりつつあります。一日も早い収束を願いつつも、日々自分にできることをやっていこうと思います。 今回は「デザインに使える比率の話」と題して、黄金比と白銀比を紹介します。

黄金比

1:(1+√5)/2≒1:1.6
(1+√5)/2は黄金数とも呼ばれ、フィボナッチ数列の一般項表記に出てくる数です。整数値で比率を表すと5:8となります。
Web画面を作る時、通常表示向けと強調表示向けのフォントサイズを5:8にすることがありますが、通常表示側が奇数になる場合は以下2つの内、1.6に近い方を使うようにしています。
  • 強調表示向け÷(通常表示向け+1)
  • 強調表示向け÷(通常表示向け-1)

白銀比

貴金属比による定義…1:(1+√2)≒1:2.4
直角二等辺三角形の斜辺と底辺の比…1:√2≒1:1.4
「1:√2」は紙の寸法に出てくる他、日本の建築物に多く取り入れられています。また、東京スカイツリーの「東京スカイツリー展望回廊」とタワー全体の高さが約1:1.41で、これも白銀比になっています。

少し数学的な話

第n貴金属比…1:{n+√(n^2)+4}/2
黄金比や白銀比を更に一般化した概念として、貴金属比というものがあります。第1貴金属比(n=1)が黄金比、第2貴金属比(n=2)が白銀比です。ちなみに第3貴金属比(n=3)…1:{3+√13}/2 には青銅比という別名がついています。
(この並び、昔のアニメを思い出します)

おまけ

貴金属比に属さないながら、貴金属の名前を持つ比として白金比というものがあります。
白金比…1:√3
これは、正三角形の底辺の中点と頂点を結んでできる直角三角形の、斜辺を除く辺の比として出てきます。斜辺は短い方の辺のちょうど2倍になっていて、これは三平方の定理からも分かります。

最後に

少し前の話になりますが、京都大学数理解析研究所の望月新一教授による「ABC予想」の証明が認められたと話題になりました。「ABC予想」を真とすると、「フェルマーの最終定理(ワイルズの定理)」をあっという間に証明できるという話があるので紹介します。詳しい説明をしているサイトが他にありますので、ここでは定理と証明の概略を紹介するに留めます。
【フェルマーの最終定理(ワイルズの定理)】
3以上の自然数nについて、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
【証明の概略】
・ABC予想を真とすると、n>=6で(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しないことが証明できる。
・n=3,4,5の場合は、先人達によって個別に証明されている。
・よって3以上の自然数について、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
証明に360年かかった命題を、ここまで簡単に証明してしまえるのは驚くばかりです。しかしながら全てではなく、先人達が残した実績との合体技で証明です(これも昔の某アニメを思い出す)。どんなに優れた知見が出ても、そこに至るまでの先人達の歩みを無視することはできないと感じます。
今回は、ここで終わりとします。

JavaScriptって括弧のお化け

はじめに

コロナウイルスに戦々恐々の廣田です。
皆様も体にはお気をつけて…

憂鬱な今日この頃ですが、最近の業務中の癒しはslackが未読を始末した時に励ましてくれることです。
癒されすぎてキャプチャを集めちゃいました。

さて、今回はJavaScriptが括弧の有無で挙動が全然違うので調べてみました。

コールバック関数の書き方

Vue.jsで画面上の検索ボタンを押したらモーダル画面が開いて一覧から検索を行えるような機能を作っていた時のこと。 つぎのようなコードで上手く動かなかったのですが、どこが問題でしょうか🤔
<!--HTML側--> 
<div id="js-mainArea">
<button type="button" @click="search">検索</button> 
</div>
//本体のVueインスタンス
new Vue({
   el: '#js-mainArea',
   data: {},
   methods: {
       search() {
           EventBus.$emit('open-search-modal');
       },
   },
});
//モーダルのVueインスタンス
new Vue({
   el: '#js-search-modal',
   data: {
       isOpen = false
   },
   created: function () {
       EventBus.$on('open-search-modal', this.open())
   },
   methods: {
       open() {
           console.log('open');
           this.isOpen = true;
       },
  },
});
すごく初歩的なところだと思うのですが、検索ボタンを押したときemitしたイベントを捕捉している以下の箇所が原因でした。
EventBus.$on('open-search-modal', this.open()) 
イベントハンドラとしてコールバック関数を指定したいので正しくはこうですよね。
EventBus.$on('open-search-modal', this.open)
はい。括弧を書いていたのが間違いでした。

うっかり括弧付きで書いてしまうことがあるのですが、どうして括弧付きで書くとダメなんでしょう。

「括弧をつけるとメソッドが即座に実行されてしまう」
「コールバック関数を書く時は括弧無しで書く」

コールバック関数として渡したいときは括弧書いちゃいけないんですね
わかりました~

…でもなんで?
納得できなかったので調べてみました🧐

具体的な例で考えてみる

わかりやすいようにSquare関数を用意します。
function Square(number) {
  return number*number;
}
これは以下と同等です。
const Square = function(number) {
  return number*number;
}
2つめのように表現すると関数も他の定数や数値のように受け渡しが可能なものだということがわかりやすくなりました。(こういうのを第一級オブジェクトというらしいです。)

括弧ありでの挙動

let number = 3;
let result = Square(number);
このときSquare関数が引数にnumberをとって実行され、変数resultには「9」がセットされます。

括弧なしでの挙動

let number = 3;
let result = Square;
このとき変数resultにはSquare関数そのものがセットされます。
つまりresult()とすることでSquare関数を実行できるということです。
let output = result(3); 
console.log(output); // equal to 9
これは以下と同等です。
let output = Square(3);
resultを呼びだすとき、実際にはSquare関数を参照しているんですね。


 

今回の事例に戻って

さて最初のVueの例に戻ります。
created: function () {
       EventBus.$on('open-search-modal', this.open())
   },
   methods: {
       open() {
           console.log('open');
           this.isOpen = true;
       },
  },
},
上記のように書いた時、createdの段階でopen関数が実行されてコンソールに”open”が表示されます。
open関数は変数isOpenをtrueにするのみの関数のなのでその実行結果がイベントハンドラとして渡されても、イベント捕捉時に何も実行されません。

open() { 
    console.log('open'); 
    this.isOpen = true; 
},
//これは以下と同じ
open = function() {
    console.log('open'); 
    this.isOpen = true;
}
 
なのでイベントハンドラにopen関数自体をコールバック関数として渡すためには括弧無しのthis.openと書かなければいけません。
納得できましたε-(‘∀`;)ホッ


とってとっても参考になりました。
https://stackoverflow.com/questions/3246928/in-javascript-does-it-make-a-difference-if-i-call-a-function-with-parentheses

アロー関数とかほんと括弧のお化け。arimasさんに同意します。
https://qiita.com/arimas/items/448f411e78d12f7d5208

Java14とJavaFXを使い普通のブロック崩しを作る

はじめに

安藤です。
世の中の情勢が厳しい昨今ですがいかがお過ごしでしょうか。私は元気です。
今回は3/17にリリースされたJava14を早速使ってみたという話です。

Java14について

全容はきしださんがまとめてくれています。いつもお世話になっています。
Java 14新機能まとめ
個人的に注目したいのが、ついに正式版になったPackaging Toolです。
およそ1年前にこちらの記事でも触れましたが、ようやく公式のパッケージャが登場しました。

今回作ったもの

本題です。
この記事のためだけにJavaFXを使ったシンプルなブロック崩しを作りました。
javafx-breakout
breakout.zip
実は前職で新卒研修時に暇だったので似たようなものを作ったことがあり、それを思い出しながら作っていたのですが、想像の3倍くらい大変でした。
新しい構文を使うことと、できるだけ内部状態の変更を起こさないことをコンセプトに作りました。

全体の構成

ProcessingのようにCanvasで画面を描画していきます。fxmlの記述はこれだけです。

<?xml version="1.0" encoding="UTF-8"?>


<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.Group?>
<Group xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1"
       fx:controller="jp.imagemagic.breakout.FieldController"
       onMouseMoved="#mouseMove" onMouseClicked="#mouseClick">
    <children>
        <Canvas fx:id="canvas" focusTraversable="true" width="400" height="600"/>
    </children>
</Group>

画面にはブロック、ボール、バーがありますが、統一的な計算のために以下を用意しました。
Recordはイミュータブルな推移をさせるためにうってつけです。

package jp.imagemagic.breakout;

import javafx.scene.paint.Color;

interface Drawable {
    Vector pos();

    Vector size();

    default DrawType drawType() {
        return DrawType.Rect;
    }

    default Color fill() {
        return Color.WHITE;
    }

    default Color stroke() {
        return Color.BLACK;
    }
}


package jp.imagemagic.breakout;

public record Vector(double x, double y) {
    public Vector move(double dx, double dy) {
        return new Vector(x + dx, y + dy);
    }

    public Vector move(Vector v) {
        return move(v.x, v.y);
    }

    public Vector scalar(double n) {
        return scalar(n, n);
    }

    public Vector scalar(double nx, double ny) {
        return new Vector(x * nx, y * ny);
    }
}

クラス構成の大枠は以下の通りです。これはtetrixの影響を受けた構成になっています。
  • 画面からの入力の処理と一定時間ごとに画面の推移を行うFieldController
  • 現在の状態を保持するState
  • Stateの推移ロジックをもつField
  • FieldControllerとStateを仲介するViewUnit

package jp.imagemagic.breakout;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

final class ViewUnit {
    private final Field field;
    private State state;

    ViewUnit(double w, double h) {
        field = new Field(new Vector(w, h));
        state = field.initState();
    }

    void trans(UnaryOperator<State> trans) {
        state = trans.apply(state);
    }

    UnaryOperator<State> moveBar(double x) {
        return field.moveBar(x);
    }

    UnaryOperator<State> moveBall() {
        return field.moveBall();
    }

    UnaryOperator<State> transStatus() {
        return field.transStatus();
    }

    void drawView(Consumer<Drawable> draw, Consumer<Status> statusDraw) {
        state.view().objects().forEach(draw);
        statusDraw.accept(state.status());
    }
}


// 一部抜粋
public class FieldController implements Initializable {
    @FXML
    private Canvas canvas;
    private GraphicsContext gc;
    private ViewUnit unit;

    public void mouseMove(MouseEvent e) {
        transAndDraw(unit.moveBar(e.getSceneX()));
    }

    public void mouseClick() {
        transAndDraw(unit.transStatus());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        gc = canvas.getGraphicsContext2D();
        unit = new ViewUnit(canvas.getWidth(), canvas.getHeight());
        final var timeline = new Timeline(new KeyFrame(new Duration(10), e -> transAndDraw(unit.moveBall())));
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
        drawView();
    }
}

補記: jpackageによるパッケージング

予めjlinkでランタイムの大きさを削っておくのがお勧めです。
インストーラを伴わないものを作ろうと色々と試した結果、このようなコマンドになりました。
jpackage -t app-image -n breakout -d dist -i app --main-class jp.imagemagic.breakout.Main --main-jar jp.imagemagic.breakout.jar --runtime-image jregram.min\ --java-options "--add-exports javafx.base/com.sun.javafx.reflect=ALL-UNNAMED --add-reads javafx.base=ALL-UNNAMED --add-reads javafx.graphics=ALL-UNNAMED --enable-preview"
appにjarファイルがあり、distにbreakoutというディレクトリができます。
jarファイルにしていますが、オプションを変えればクラスファイルでも行けると思います。

UWPアプリからOpenCVしてみる(その1)

矢野です。

プロトタイプ的にWindows用のアプリケーションを新たに作成するにあたり、ちょっと考えました。Windows Formsなら自分もそれなり多少は経験もあるけど、新規でつくるのに今さらFormsというのもどうなのかしら? でもWPFやUWPも結局のところあまり普及してきている感じがしないし、枯れたFormsに比べれば、情報はかなり少ない。XAML Islands? 何それ?
このプロトタイプは、PCに接続したWebカメラで撮影した画像をOpenCVでちょちょいと弄れるだけでいいのですが、でもFormsからカメラなどメディア関連の制御をするのは結構大変です。DirectShowとかMedia Foundationなどを使ってC++でゴリゴリ書く気になれば色々細かいところまで自由になるけれど、プロトタイプなのにそのへんをめちゃくちゃ頑張る気にはなれないし、とはいっても入手が容易なC#向けのラッパーだとちょっと古かったり、ちょっと不便だったり……。
で、けっきょくUWPアプリをC#で書くことにしました。それでも周回遅れなのかもしれませんが、せっかくの機会ですから少しでも新しい仕組みに追いついておきたいということで。
  • UWPアプリ上でMediaCaptureを使ってWebカメラにアクセス
  • Webカメラから取り込んだ画像をOpenCVで処理
  • 処理した画像を表示
これらを実現する簡単なアプリをつくってみたいと思います。 Visual Studio 2019を起動し、C#/Windows/UWPの「空白のアプリ(ユニバーサルWindows)」を新しいプロジェクトとして作成。
ソリューションエクスプローラーからPackage.appxmanifestをダブルクリック→機能タブの[Webカメラ]と「マイク]にチェックを入れて保存。
MainPage上にツールボックスからCaptureElementを配置し、適当な名前(captureElement)を付ける。StretchプロパティをUniformToFillにしておく。 MainPageのOnNavigatedTo(NavigationEventArgs)メソッドをオーバーライドする(awaitな呼び出しを行うので、メソッドにasync修飾子を追加します)。
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    var capture = new MediaCapture();

    await capture.InitializeAsync();

    var props = capture.VideoDeviceController
        .GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;

    await capture.VideoDeviceController
        .SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, props);

    captureElement.Source = capture;
    await capture.StartPreviewAsync();
}
プロジェクトをビルドして起動します。

アクセス許可を確認するダイアログが表示された場合は、許可します。
  • マイクへのアクセスを許可しますか?→[はい]
  • カメラにアクセスすることを許可しますか?→[はい]
たったこれだけで、リアルタイムにカメラの画像が表示されました! すごい! 泣きそうなりながらC++でMedia Foundationで書いた時とは大違い!(大げさ)
その2に続きます。

Illustratorスクリプト開発の便利ツール

はじめに

こんにちは、イメージ・マジックのもあいです。

前回ブログを書いたのが約一年前、時間が経つのがどんどん早く感じられてきている今日この頃です。
富士ゼロックススーパーカップも終わり、Jリーグもそろそろ開幕する時期になってきました。 今回は、2018年7月頃に公開したIllustratorスクリプトのあれこれを書いた後にIllustratorスクリプトを開発するに当たって便利なツールを見つけましたので、簡単な使い方を紹介します。

ExtendScript Debugger

見つけたツールは、Microsoft社が公開しているVisual Studio Code のExtensionである ExtendScript Debugger というものです。これはAdobeが公開している公式なツールになります。

インストール

手順は下記の通りです
  • Visual Studio Codeをインストール
  • Visual Studio Codeの拡張機能(Ctrl + Shift + X)で ExtendScriptで検索
  • ExtendScript Debugger をクリック
  • インストール

使い方

  • デバッグしたいIllustratorスクリプトをVisual Studio Codeで開きます
  • Visual Stuio Codeのステータスバーにあるターゲットアプリケーションを選択をクリックし、画面上部に現れるターゲットアプリケーションを選択というボックスに使用するアプリケーション(今回はAdobe Illustrator 2020)を選択します。この時点でIllustratorが起動していない場合はIllustratorを起動するかどうかを聞いてきますのではいを選択します。
  • F5キーを押下し、環境の選択でExtendScript Debugを選択すれば、デバッグを開始できます。
  • ブレークポイントを設定すれば、そこで止めることも可能です。
  • 変数、ウォッチ式、コールスタックと行った情報を見ることができます。
  • グローバルなオブジェクトの情報も見られます。
  • ローカル変数の情報も当然見られます。

最後に

以前は Extendscript Toolkit CC をインストールしていましたが、このツール自体がEOLなので、インストール手順が煩雑なのと、新規ドキュメントを作成する処理を実行できないという問題がありましたが、今回紹介しているツールはそんなことも無くデバッグできるので、開発がだいぶ楽になったと思っています。

年末にいろいろ振り返る

こんにちは株式会社イメージ・マジックのsoenoです 。
今週テックブログということでクリスマスっぽい何かを考えていましたが、
終わりましたね。クリスマス。
それでも何か季節感のある投稿をと思った結果、今回のテーマは振り返りです。
会社のブログなので入社以来で いろいろ振り返ってみようかと思います。
 

入社以来何が変わったか

変わったこと(いろいろありますが今回は3つ。)
  1. 黒い画面を怖がらなくなった。
  2. IntelliJ IDEA と少し仲良くなった。
  3. データが降ってくると思わなくなった。
詳細は以下の通りです。



変化の詳細

黒い画面を怖がらなくなった。

 コマンドプロンプトの入力機会が増え、
コマンドプロンプトで何かを操作することは特別で危険なものという意識から
環境を整えたり、エラーの確認に必要なものと理解しました。


IntelliJ IDEAと少し仲良くなった。

直近ではgitでやらかしたときの後始末でお世話になりました。
個人的には
  「CapsLock + Alt + 縦に選択」 での複数エリア編集からの
  「ctrl + w」 での単語選択、の後の入力がとても素敵だと思います。
   →構造の近い要素で単語の長さを気にせずにクラスを足すなどできます。



データが降ってくると思わなくなった。

 フロントのみ開発していると、 型やら数は最初に取り決め
サーバーから取り決めた形で降ってきたデータで色々する。(その後は知らない。)
 という認識が、サーバー側の処理も見える環境によって、 phpが渡すデータを作り、その大本にはデータベースがいて …… などという 一連のやり取りとしてみえるようになりました。(何ならDBが書き換わるところまで見えます。)

 よって、データは降ってくるものではなく、それ用に加工され渡されている。という理解になりました。


まとめ

 全体的に古めで人力な開発から、フレームワーク等を使った新しく、パワフルな開発環境へ変わった印象です。


おわりに

朝のエレベータで死ぬほどネストしたscssを思い出し、
さらにそれすら修正後(css→scssへ)のデータだったことを思い出し 。
いろいろ変わったものだとしみじみ思いました。

今後もいろいろな変化があると思いますが、
その変化が良いものであるといいと思います 。

それでは少し早いですが良き年末、良き新年を!

Symfony Form Type Options Guessing

こんにちは、岡野です。
Symfonyで 開発中のプロジェクトでvalidateのコードを減らしたく、Form Type Options Guessingを色々と試した結果を共有します( Symfony4.3 )。
参考:https://symfony.com/doc/4.3/forms.html#form-type-options-guessing
1.既存のコード
FormBuilder記述
  add('sort', TextType::class)
Entity記述
  @ORM\Column(type="integer")
生成HTML
  <input type="text" id="..." name="..." required="required">
  (以降、id/name/required属性は省略)
type=”text”の場合、EntityでsetSort(?int)などと型指定していると、数値でない”a”などをsubmitした際にPropertyAccessorでInvalidArgumentExceptionが発生してしまう。
2.クラスを省略
FormBuilder
  add('sort')
Entity
  @ORM\Column(type="integer")
HTML
  <input type="number">
type=”number”になった。
3.Rangeアノテーションを追加
FormBuilder
  add('sort')
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern=".{1,}">
maxlength/pattern属性は増えたが、min, max属性は付けてくれない。 type=”number”の場合maxlength属性が効かない様で、123456の様な値でsubmitできてしまう(以降Chrome78で確認)。
4.add()の引数でmin/maxを指定
FormBuilder
  add('sort', null, ['attr' => ['min' => 0, 'max' => 99999]])
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern=".{1,}" min="0" max="99999">
123456は入力できるがsubmitはできない。
5.add()の引数でpatternも指定
FormBuilder
  add('sort', null, ['attr' => ['min' => 0, 'max' => 99999, 'pattern' => '\d{1,5}']])
Entity
  @ORM\Column(type="integer")
  @Assert\Range(min="0", max="99999")
HTML
  <input type="number" maxlength="5" pattern="\d{1,5}" min="0" max="99999">
type=”number”の場合pattern属性が効かない様で、123456が入力できる。submitはできない。
結局、開発中のプロジェクトでは3.で進めることにしました。