Open9

Gleamでおもしろかった話をまとめる

yukiyuki

3年前くらいにGleamを触ったきりだったが、最近version 1がリリースされてずっと触ろうと思って結構時間が経ってしまった。最近ドキュメントを眺めていてなんとなくおもしろそうだと再度思い、実際にいくつかコードを書いてみておもしろかったので、おもしろかった機能をまとめておく。

現状だが、Language Serverは結構動く。エラーメッセージもかなり改善されてきており、わかりやすくなっている。普通に開発をしていて妙なバグに直面することはない。一方で新しい言語であるため、詰まったときの情報がかなり少ない。このスクラップも、詰まったときに役立つように残しておく。

yukiyuki

ちょっと昔に軽く触っているので完全にゼロからのスタートかというと難しいけど、RustをやっていてかつScalaをやっていたからか、1日でスラスラ書けるようになってしまった。見てくれはFunctional Programming flavourなRustなので、FPがわかっていてRustが書ければすぐにアプリケーション作れて楽しいと思う。

yukiyuki

ジェネリクスは丸括弧

Option型の実装を見て一瞬戸惑うのだが、(a)aという型引数らしい。Gleamでは、ジェネリクスは(type)で記述される。たとえば他の言語でいうList<A>List(a)と記述される。

一瞬、typeに持ちうる型に共通するフィールドかと思ったが、型注釈が付いていないので判別可能ではある。

pub type Option(a) {
  Some(a)
  None
}

https://tour.gleam.run/everything/#data-types-generic-custom-types

yukiyuki

Nil

Gleamによるサンプルを見ると登場する謎のNilだが、これは他の言語でいうところのユニット型を指す。

Gleamではたとえば次のようにmain関数の戻り値として登場することがある。

pub fn main() -> Nil {
    todo
}

Gleamの関数は必ずなにかの値を返す必要がある。なのでこの関数は、Nilという値を返していることになる。他の言語ではnullに近しい概念として用いられるが、これを表現したい場合GleamにはOptionがある。

https://tour.gleam.run/everything/#data-types-nil

yukiyuki

パイプ演算子

Gleamでは|>でパイプ演算子が利用できる。一部のプログラミング言語で見かけることのある便利な機能だと思う。下記は、パイプ演算子でファイルの読み込みを実装してみた例である。

/// Reads a file and returns its contents as a list of lines
/// Returns an error message if the file cannot be read
fn read_file(path: String) -> Result(List(String), String) {
  simplifile.read(path)
  |> result.map(fn(content) { string.split(content, "\n") })
  |> result.map_error(fn(e) {
    string.inspect(e)
    |> string.append(": File Not Found")
  })
}

パイプ演算子は、第一引数の型があっていれば利用できる。たとえば、simplifile.readを呼び出すとResult型の値が返ってくる。これをresult.mapに繋げて型の変換を行っている。result.mapは次のような定義になっており、第一引数の値がパイプによって与えられていることがわかる。

pub fn map(over result: Result(a, e), with fun: fn(a) -> b) -> Result(b, e) {
  case result {
    Ok(x) -> Ok(fun(x))
    Error(e) -> Error(e)
  }
}

ちなみに、関数の引数にさらについているoverwithはLabelled Argumentsという機能でこれもGleamに独特な機能な気がする。

https://tour.gleam.run/everything/#functions-labelled-arguments

あんまりよく知らないのだが、Elixir由来なのだろうか。
https://gleam.run/cheatsheets/gleam-for-elixir-users/#labelled-arguments

yukiyuki

標準ライブラリは結構薄め?

stdlibがかなり薄い気がする。何をやるにもサードパーティ製のライブラリを利用する必要がある。下記に、何が標準ライブラリに入っているかのリストがある。

https://hexdocs.pm/gleam_stdlib/

最近grepを実装したみたのだけど、ファイルの読み書きすら simplifile というサードパーティ製のライブラリに頼る必要があった。

yukiyuki

Gleamにifはない

ない。caseを代わりに使う。

♥ ❯ gleam build
error: Syntax error
   ┌─
   │
52 │   if
   │   ^^ Gleam doesn't have if expressions

If you want to write a conditional expression you can use a `case`:

    case condition {
      True -> todo
      False -> todo
    }

See: https://tour.gleam.run/flow-control/case-expressions/
yukiyuki

@external

simplifileというファイルIOのライブラリを見ていると、次のような記述に出会うことがある。

@external(erlang, "simplifile_erl", "create_directory")
@external(javascript, "./simplifile_js.mjs", "createDirectory")
pub fn create_directory(filepath: String) -> Result(Nil, FileError)

これはおそらくだがシステムコールを利用する機能を実装する際に利用されるものと思われる。たとえば、simplifile_js.mjscreateDirectory関数を見ると次のようにNode.jsの機能を呼び出していることがわかる。

export function createDirectory(filepath) {
  return gleamResult(() => fs.mkdirSync(path.normalize(filepath)));
}

https://github.com/bcpeinhardt/simplifile/blob/99bfaa51b1f7c840d3f6376b9fb3933ba3a0945a/src/simplifile_js.mjs

Erlang側は次のように実装されている。

%% Create a directory at the given path. Missing parent directories are not created.
create_directory(Dir) ->
    posix_result(file:make_dir(Dir)).

https://github.com/bcpeinhardt/simplifile/blob/99bfaa51b1f7c840d3f6376b9fb3933ba3a0945a/src/simplifile_erl.erl

Gleamは結局のところ、ErlangかJavaScriptのランタイムの上のラッパー言語という感触に近い。したがってサードパーティライブラリでOSの機能を呼び出す機構を実装する場合、たとえばErlangの知識が必要になる。本格的にいろいろ直したいとか、ライブラリを作りたいとなった場合、ErlangもNode.jsもわかっていないとダメ、ということか…。