Open13

Teal言語 学習ログ

zenwerkzenwerk

配列

-- `{型名}` で配列の型定義
local names: {string} = {"John", "Paul", "George", "Ringo"}

local lengths = {}
for _, n in ipairs(names) do
   table.insert(lengths, #n) -- this makes the lengths table a {number}
end

local sizes: {number} = {34, 36, 38}
sizes[#sizes + 1] = true as number -- this does not perform a conversion! it will just stop tl from complaining!
local sum = 0
for i = 1, #sizes do
   sum = sum + sizes[i] -- will crash at runtime!
end

print(sum)
zenwerkzenwerk

タプル

-- `{string, integer}` 2要素のタプル型を表す.
local p1 = { "Anna", 15 }
local p2 = { "Bob", 37 }
local p3 = { "Chris", 65 }

local age_of_p1: number = p1[2] -- no type errors here
-- error! index 3 out of range for tuple {1: string, 2: integer}
--local nonsense = p1[3]

local my_number = math.random(1, 2)
print(my_number)
local x = p1[my_number] -- => x は `string | number` の union型
if x is string then
   print("Name is " .. x .. "!")
else
   print("Age is " .. x)
end

-- error! expected maximum length of 2, got 3
--local p4: {string, integer} = { "Delilah", 32, false }

-- 配列とタプルの型注釈
-- 全ての要素が同じ型のとき、配列として推論される
-- 複数の型が混在するユニオン型の配列が必要なとき、明示的な型注釈が必要
local array_of_union: {string | number} = {1, 2, "hello", "hi"}

-- 同じ型のタプルが必要なとき、明示的な型注釈が必要
local tuple_of_nums: {number, number} = {1, 2}
zenwerkzenwerk

マップ型

-- `{型名:型名}` でマップ型
local populations: {string:number}
--local complicated: {Object:{string:{Point}}} = {}
local modes = { -- {boolean:number} に推論される
   [false] = 127,
   [true] = 230,
}

-- 文字列をキーとするマップを使用するときは型注釈を明示したほうが良い.
-- レコード型を混同される恐れがあるため.
local is_vowel: {string:boolean} = {
   a = true,
   e = true,
   i = true,
   o = true,
   u = true,
}
zenwerkzenwerk

レコード型

-- レコードはオブジェクトや​構造体を​表現するのに便利.
-- レコード型の変数を​宣言するには、​まずレコード型の型定義が必要.

-- global type と宣言するとグローバルで有効
local type Point = record
   x: number
   y: number
end

-- 型定義には糖衣構文がある
local record Vector
   x: number
   y: number
end

-- 同じテーブル構造でも型が違うとエラー
local v1: Vector = { x = 100, y = 100 }
local p2: Point = v1 -- Error!

-- `as` キーワードでキャスト可能
local p2 = v1 as Point -- Teal goes "ok, I'll trust you..."

-- レコード関数の定義が可能
function Point.new(x: number, y: number): Point
   local self: Point = setmetatable({}, { __index = Point })
   self.x = x or 0
   self.y = y or 0
   return self
end

-- Lua の `:` でのテーブル関数定義も受け継いでいる
function Point:move(dx: number, dy: number)
   self.x = self.x + dx
   self.y = self.y + dy
end

-- レコード型に関数メンバを定義すれば、動的なコールバック関数などを保持可能
local record Obj
   location: Point
   draw: function(Obj) -- ←これ
end

-- 配列I/F を宣言すると、配列データも保持できる
-- ​フィールド名で​アクセスする​方​法と、​配列​インデックスの療法で要素に​アクセスできる
local record Node is {Node}
   weight: number
   name: string
end

-- レコードは入れ子定義可能
-- モジュールを​レコードと​して​エクスポートする​際に便利
local record http
   record Response
      status_code: number
   end

   get: function(string): Response
end

return http

-- こう使う
local http = require("http")

local x: http.Response = http.get("http://example.com")
print(x.status_code)
zenwerkzenwerk

インターフェイス

-- インターフェイスとは、​本質的には​抽象レコード.
-- レコードは Luaテーブルとしても型としても利用可能.
local interface MyAbstractInterface
   a: string
   x: integer
   y: integer
   my_func: function(self, integer) -- `self` が型定義に含まれている点に注目
   another_func: function(self, integer, self) -- `self` が...に注目
end

-- インターフェイスは値を保持できない
MyAbstractInterface.a = "this doesn't work" -- error!

-- インスタンスだったら値を保持できる
local obj: MyAbstractInterface = { x = 10, y = 20 } -- this works

-- インターフェイスは値を保持できない
function MyAbstractInterface:my_func(n: integer) -- error!
end

-- インスタンスだったら値を保持できる
obj.my_func = function(self: MyAbstractInterface, n: integer) -- this works
end


-- レコード型は `is` 演算子でインターフェイスを継承できる
local record MyRecord is MyAbstractInterface
   b: string
end

local r: MyRecord = {}
r.b = "this works"
r.a = "this works too because 'a' comes from MyAbstractInterface" -- 継承したメンバ

-- the following function complies to the type declared for `another_func`
-- in MyAbstractInterface, because MyRecord is the `self` type in this context
-- 以下の関数は、`MyAbstractInterface` で宣言されている `another_func` の型に準拠する.
-- この文脈において `MyRecord` がこのコンテキストにおける `self` 型であるため.
function MyRecord:another_func(n: integer, another: MyRecord)
   print(n + self.x, another.b)
end
zenwerkzenwerk

ジェネリクス

-- `<型変数>` でジェネリクス
local function keys<K,V>(xs: {K:V}):{K}
   local ks = {}
   for k, v in pairs(xs) do
      table.insert(ks, k)
   end
   return ks
end

local s = keys({ a = 1, b = 2 }) -- s is {string}


-- ジェネリクスレコード型
local type Tree = record<X>
   {Tree<X>}
   item: X
end

local t: Tree<number> = {
   item = 1,
   { item = 2 },
   { item = 3, { item = 4 } },
}


-- 型変数はis​演算子を​使用して​インターフェースに​よって​制限可能​
local function largest_shape<S is Shape>(shapes: {S}): S
   local max = 0
   local largest: S
   for _, s in ipairs(shapes) do
      if s.area >= max then
         max = s.area
         largest = s
      end
   end
   return largest
end
zenwerkzenwerk

Enum型

列挙型

-- enum型の変数や引数は、宣言されたリストに含まれる値のみを受け付る.
-- enum型は文字列へ変換は可能だが、その逆は不可.
-- 任意の文字列をキャストによってenum型に変換することは可能.
local type Direction = enum
   "north"
   "south"
   "east"
   "west"
end

-- 糖衣構文
local enum Direction
   "north"
   "south"
   "east"
   "west"
end
zenwerkzenwerk

関数定義

-- 関数の型定義可能
local type Comparator = function<T>(T, T): boolean

-- `?` はオプション引数を表す
-- sort のためのカスタム比較関数 cmp? を受け取れる.
local function mysort<A>(arr: {A}, cmp?: Comparator<A>)
   -- 関数定義を書く...
end

-- 多値を返すときの型定義
-- f は「オプション引数文字列を受取り、{number:number}のタプルとnumberを返す関数」を受け取る関数
f: function(function(? string): (number, number), number)


-- イテレータ関数を返す関数の例(from 'Programming in Lua')
local function allwords(): (function(): string)
   local line = io.read()
   local pos = 1
   return function(): string
      while line do
         local s, e = line:find("%w+", pos)
         if s then
            pos = e + 1
            return line:sub(s, e)
         else
            line = io.read()
            pos = 1
         end
      end
      return nil
   end
end

for word in allwords() do
   print(word)
end

-- 可変長引数を受け取る関数の型定義
-- 返り値も可変長
local function test(...: number): number...
   print(...)
   return ...
end

local a, b, c = test(1, 2, 3)


-- Lua の動的言語の関数を使う場合、返り値の型は `any...` となる.
-- このとき呼び出し側で `as` で具体的な型を指定する
local s = { 1234, "ola" }
local a, b = table.unpack(s) as (number, string)

print(a + 1)      -- `a` has type number
print(b:upper())  -- `b` has type string
zenwerkzenwerk

Union型

-- Union型の変数が定義可能
-- 型制限のあるジェネリクス変数のようなもの
local a: string | number | MyRecord
local b: {boolean} | MyEnum
local c: number | {string:number}


-- Union型の変数は使用時に `is` での型判別が必要
local a: string | number | MyRecord

if a is string then
   print("Hello, " .. a)
elseif a is number then
   print(a + 10)
else
   print(a.my_record_field)
end

-- `is` は式なので、こう使える
local a: string | number
local x: number = a is number and a + 1 or 0
zenwerkzenwerk

any型

anyは、動的型付けのLua変数と同様に、あらゆる値を受け入れる。ただし、Tealではこの値に関する一切の情報を把握していないため、等値比較やnilとの比較以外にできることはほとんどなく、as演算子を使用して他の型に明示的にキャストする程度の操作しか行えない。

一部のLuaライブラリでは、Tealでは容易に表現できない複雑な動的型をする。このような場合、any型を使用し、明示的な型変換を行うのが最後の手段となる。