mmag

ハマったことメモなど

:sys.get_stateをよく忘れる

http://erlang.org/doc/man/sys.html#get_state-1

GenServer使ったモジュールの単体テストでstate見ようとして、いつも思い出せなくてググってるので3回書く。

:sys.get_state(pid)
:sys.get_state(pid)
:sys.get_state(pid)

:sysがパッと出てこねーんだよな。ちなみにGenStateMachine(:gen_statem)とかにも使える。

Protocolの@deriveについて調べた

Elixir 1.9がそろそろ出そうなところに1.8の話をするんですけど、1.8からInspectプロトコルで文字列化される構造体のメンバをキーで指定できるようになりました。

defmodule User do
  @derive {Inspect, only: [:id, :name, :age]}
  defstruct [:id, :name, :age, :email, :encrypted_password]
end

こうしておくと、意図せずログにメールアドレスとか個人情報やらが出力されることを防げたり、人間が見てもあんま意味ないものを出さないようにできるというわけですね。

で本題は、こういうものを自分で実装するにはどうしたらいいんでしょうか、ということを思ったので書いてみました、というものです。

defstructのドキュメントProtocol.derive/3のドキュメントに全て書いてあるんですけど、Jason.Encoderのコードも読んで参考にしました。 Elixirのドキュメント、こっちが知りたいことは大抵書いてあるんですよね。どういうことなんですかね。

練習課題

構造体メンバの値を"*****"に置き換えるMaskプロトコルを実装します。:onlyオプションを付けた場合は、指定したものだけ"*****"に置き換えます。使われ方の例はこちら。

defmodule User do
  @derive {Mask, only: [:password]}
  defstruct [:name, :age, :password]
end

Mask.mask %User{name: "John", age: 29, password: "secret123"}
#=> %User{name: "John", age: 29, password: "*****"}

実装

defprotocol Mask do
  def mask(struct)
end

defimpl Mask, for: Any do
  defmacro __deriving__(module, struct, opts) do
    all_fields = struct |> Map.drop([:__struct__]) |> Map.keys()
    mask_target = Keyword.get(opts, :only, all_fields)

    quote do
      defimpl Mask, for: unquote(module) do
        def mask(struct) do
          Enum.reduce(unquote(mask_target), struct, fn (field, acc) ->
            Map.put(acc, field, "*****")
          end)
        end
      end
    end
  end

  def mask(_any) do
    raise Protocol.UndefinedError
  end
end

説明

@deriveはモジュール属性を設定するだけなので、特にこれが何をやってるとかではないです。何かやってるのはdefstructの方で、@derive属性が設定されている場合に、この辺りから諸々呼ばれてMask.__deriving__/3マクロが展開されます。このマクロの中でdefimpl Mask, for: Userしておいてあげると、Mask.mask(%User{})できるようになるという寸法です。__deriving__/3の第3引数には@deriveに書いたキーワードリスト(上の例だと[only: [:password]]が渡ってくるので、よしなに利用してあげればマスクするべきキーが判定できます。今回はdefimplしてないもの以外はraiseするようにしたので、例えばnilなんか投げるとこうなります。

Mask.mask(nil)
# ** (Protocol.UndefinedError) protocol Mask not implemented for nil. This protocol is implemented for: User
#     lib/deriver/protocol.ex:1: Mask.impl_for!/1
#     lib/deriver/protocol.ex:2: Mask.mask/1

AtomでSvelte 3のSyntax Highlight

https://svelte.dev/docs#Component_format にもあるように*.svelteはだいたいHTMLなので、HTMLのハイライトを使うようにエディタを設定してやればよさそう。ブログに書くときもHTMLってことにすれば

<script>
  export let name
</script>

<style>
  .p {
    color: purple;
  }
</style>

<p>Hello {name}</p>

こんな感じ。

で、Atomの設定を眺めたけどそういう項目がなかったのでググり、config.csoncustomFileTypesを書くとよいという知見を得た。

"*":
  ...
  core:
    ...
    customFileTypes:
      "text.html.basic": [
        "svelte"
      ]

一応svelte-atomっていうパッケージはあったけど、v3には対応してなさそうだった。多分そのうちlanguage-svelteみたいなのが現れると思う。