Haskell 勉強メモ5
let式
let
式は変数を束縛する。 let
式自身も式。式であるとは、「値を持つ」ということ。
円柱の表面積を求める。
cylinder :: Double -> Double -> Double cylinder r h = let sideArea = 2 * pi * r * h topArea = pi * r ^ 2 in sideArea + 2 * topArea *Main> cylinder 4 10 351.85837720205683
let は式であるので、コードの中のどんなところでも使える。
*Main> 4 * (let a = 9 in a + 1) + 2 42 -- ローカルスコープに関数を作る *Main> [let square x = x * x in (square 5, square 3, square 2)] [(25,9,4)] -- タプル要素を分解してそれぞれの名前に束縛する *Main> (let (a, b, c) = (1, 2, 3) in a + b + c) * 100 600
case 式
パターンマッチで使う。
head' :: [a] -> a head' xs = case xs of [] -> error "No head for empty lists" (x : _) -> x *Main> head' [] *** Exception: No head for empty lists CallStack (from HasCallStack): error, called at baby.hs:3:9 in main:Main *Main> head' [1, 2] 1 *Main>
case
式の構文は下記
case expression of pattern -> result pattern -> result pattern -> result
式に合致した最初のパターンが使われる。
case 式はどこでも使える。
describeList :: [a] -> String describeList ls = "The list is " ++ case ls of [] -> "empty" [x] -> "a singleton list" xs -> "a longer list" *Main> describeList [] "The list is empty" *Main> describeList [1, 2] "The list is a longer list" *Main> describeList [1] "The list is a singleton list"
Haskell 勉強メモ4
関数を書くための構文
パターンマッチ
Scala でもたくさん使うパターンマッチ。Haskell版も見てく。
渡された数が7かどうかを判別する関数。
lucky :: Int -> String lucky 7 = "LUCKY NUMBER SEVEN!" lucky x = "Sorry, you're out of luck, pal!" *Main> lucky 8 "Sorry, you're out of luck, pal!" *Main> lucky 7 "LUCKY NUMBER SEVEN!"
lucky
を呼ぶと、パターンが上から下の順で試される。
def lucky(n: Int): String = n match { case 7 => "LUCKY NUMBER SEVEN!" case _ => "Sorry, you're out of luck, pal!" } lucky(8) // val res11: String = Sorry, you're out of luck, pal! lucky(7) // val res12: String = LUCKY NUMBER SEVEN!
階乗計算するプログラムをパターンマッチを使って、再帰的に書く。
factorial :: Int -> Int factorial 0 = 1 factorial n = n * factorial (n - 1)
Scalaの場合
def factorial(n: Int): Int = n match { case 0 => 1 case k => k * factorial(k-1) }
タプルのパターンマッチもできる。
2次元ベクトルの足し算。
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors a b = (fst a + fst b, snd a + snd b) -- これだと読みづらい。 addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) -- こう書くと直感的
リスト内包表記でもパターンマッチが使える。
*Main> let xs = [(1,3),(4,3),(2,4),(5,3),(5,6),(3,1)] *Main> [a + b | (a, b) <- xs] [4,7,6,8,11,4] -- マッチしない要素は結果に反映されない。 *Main> [x*100 + 3 | (x, 3) <- xs] [103,403,503]
val list = Seq((1,3),(4,3),(2,4),(5,3),(5,6),(3,1)) for ((a, b) <- list) yield a + b // val res13: Seq[Int] = List(4, 7, 6, 8, 11, 4) for ((x, 3) <- list) yield x*100 + 3 // val res14: Seq[Int] = List(103, 403, 503)
リストもパターンマッチで使える。空のリスト []
または、:
を含むパターンと合致させることができる。x:xs
というパターンは、リストの先頭要素を x
に束縛し、リストの残りを xs
に束縛する。
head' :: [a] -> a head' [] = error "Can't call head on an empty list, dummy!" head' (x: _) = x *Main> head' [] *** Exception: Can't call head on an empty list, dummy! CallStack (from HasCallStack): error, called at baby.hs:40:12 in main:Main *Main> head' [2,3,4] 2
Scalaのパターンマッチの場合は下記。
def head[T](list: List[T]): T = list match { case x::_ => x case Nil => throw new IllegalArgumentException } head(List(2,3,4)) // val res0: Int = 2 head(List()) // java.lang.IllegalArgumentException
asパターン
Haskellには as パターン という特殊なパターンがある。値をパターンに分解しつつ、パターンマッチの対象になった値自体も参照したいときに使う。@
を使うことで、元のリスト全体にアクセスすることができる。
firstLetter :: String -> String firstLetter "" = "Empty string whoops!" firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x] *Main> firstLetter "Dracula" "The first letter of Dracula is D"
知らなかったが Scala でも同じことができるらしい。参考
上と似たことをScalaでやると。
def firstLetter(letters: List[Char]): String = { letters match { case Nil => "Empty string whoops!" case all @ (x::xs) => s"The first letter of ${all} is $x" } } firstLetter("Dracula".toList) // val res3: String = The first letter of List(D, r, a, c, u, l, a) is D
@
を使うことで、元の要素にアクセスできていることが分かる。Scalaの場合は、元の要素をそのまま使えばいいんじゃとか思うけど、何か違うのだろうか。。?
ガード
引数の値が満たす性質で場合分けするとき。
bmiTell :: Double -> String bmiTell bmi | bmi <= 18.5 = "underweight." | bmi <= 25.0 = "normal" | bmi <= 30.0 = "fat" | otherwise = "Are you Whale?"
参考本のサンプルはもっと毒々しかったけど、書くの面倒だった。bmi
の値で場合分けしている。
Scalaで書く場合は
def bmiTell(bmi: Double): String = bmi match { case v if v <= 18.5 => "underweight" case v if v <= 25.0 => "normal" case v if v <= 30.0 => "fat" case v => "Are you whale?" }
複数引数でも可能。
bmiTell :: Double -> Double -> String bmiTell weight height | weight / height ^ 2 <= 18.5 = "underweight." | weight / height ^ 2 <= 25.0 = "normal" | weight / height ^ 2 <= 30.0 = "fat" | otherwise = "Are you Whale?"
where
命令型のプログラミング言語なら、同じ値を何度を計算するのを避けるため、変数に格納するやることが一般的。Haskellでは、where
キーワードをつかて計算の中間結果に名前をつける。さっきの bmiTell
だと where /height ^ 2
を何度も書く&再評価するのは無駄が多い。下記のように修正する。
bmiTell :: Double -> Double -> String bmiTell weight height | bmi <= 18.5 = "underweight." | bmi <= 25.0 = "normal" | bmi <= 30.0 = "fat" | otherwise = "Are you Whale?" where bmi = weight / height ^ 2
where
のなかでパターンマッチを使うこともできる。
initials :: String -> String -> String initials firstname lastname = [f] ++ " " ++ [l] ++ "." where (f : _) = firstname (l : _) = lastname
where
ブロック内では、定数だけでなく関数も定義できる。
calcBmis :: [(Double, Double)] -> [Double] calcBmis xs = [bmi w h | (w, h) <- xs] where bmi weight height = weight / height ^ 2
Haskell 勉強メモ3
型変数
Haskellの head
関数の型を見てみる。
*Main> :t head head :: [a] -> a
この a
が型変数と呼ばれる。型変数によって関数を複数の型に対して動作するようにできる。型変数を用いた関数は多相的関数と呼ばれる。
どのような型でも良いという性質のことを多相性(polymorphism)と呼ぶ。型変数を含むような型のことを多相型 と呼ぶ。
型クラス
型クラス (type class) とは、ある型が何らかの性質を持つことを示すインターフェースのこと。型クラスは関数の集まりを定める。型クラスに属する関数のことをその型クラスのメソッドと呼ぶことがある。
等価性を定義する型クラスの例
*Main> :t (==) (==) :: Eq a => a -> a -> Bool
これは、等価性関数が「同じ型の任意の2つの引数を取り、Boolを返す」関数であることを意味する。
シンボル =>
よりも前にあるものは型クラス制約と呼ばれ、「引数の2つの値のかたは Eq
クラスのインスタンス出なければならない」ことを示す。
型クラスの例
Eq
型クラス
等価性をテストできる型に使われる。関数の型変数に Eq
クラスの制約がついていたら、その関数の定義のどこかで、==
か /=
が使われていることを示す。
Ord
型クラス
順序を付けられる型のための型クラス。
*Main> :t (>) (>) :: Ord a => a -> a -> Bool
引数を2つとり、それらが関係を満たすかどうかを教えてくれる Bool
を返す。
compare
関数は Ord
のインスタンスの型の引数を2つとり、Ordering
を返す。Ordering
は GT, LT, EQ
のいずれかをとる。
*Main> :t compare compare :: Ord a => a -> a -> Ordering *Main> "Abrakadabra" < "Zebra" True *Main> "Abrakadabra" `compare` "Zebra" LT *Main> 5 >= 2 True *Main> 5 `compare` 3 GT *Main> 'b' > 'a' True *Main>
Show
型クラス
ある値は、その型が Show
型クラスのインスタンスになっていれば、文字列として表現できる。この型クラスの操作でよく使われるのが show
で、これは指定した値を文字列として表示する関数。※ print
文みたいなものかな?
*Main> show 3 "3" *Main> show 5.334 "5.334" *Main> show True "True"
Read
型クラス
Show
型と対をなす型クラス。read
関数は文字列を受け取り、Read
のインスタンスの方を返す。
*Main> :t read read :: Read a => String -> a *Main> read "True" || False True *Main> read "8.2" + 3.8 12.0 *Main> read "5" - 2 3 *Main> read "[1,2,3,4]" ++ [3] [1,2,3,4,3] *Main> read "5" -- Floatなのか、Intなのか、それ以外なのか判断できないためエラー *** Exception: Prelude.read: no parse *Main> read "5" :: Float -- 型を明示する必要がある。 5.0 *Main> read "5" :: Int 5
Enum
型クラス
列挙型。値をレンジの中で使える。
*Main> ['a' .. 'e'] "abcde" *Main> [LT .. GT] [LT,EQ,GT] *Main> [3 .. 5] [3,4,5] *Main> succ 'B' 'C'
Bounded
型クラス
上限と下限を持つ。それぞれ、 minBound
と maxBound
関数で調べることができる。
*Main> minBound :: Int -9223372036854775808 *Main> maxBound :: Int 9223372036854775807 *Main> maxBound :: Char '\1114111' *Main> maxBound :: Bool True *Main> minBound :: Bool False
Num型クラス
数の型クラス
*Main> :t 20 20 :: Num p => p
Floating 型クラス
Float
と Double
Integral 型クラス
整数(全体)のみが含まれる数の型クラス。
Haskell 勉強メモ2
明示的な型宣言
Haskellの型宣言は2行で書く模様。こういう書き方は初めて。
removeNonUppercase :: [Char] -> [Char] removeNonUppercase st = [c | c <- st, c `elem` ['A' .. 'Z']] *Main> removeNonUppercase("HelloWorld") "HW" addThree :: Int -> Int -> Int -> Int addThree x y z = x + y + z *Main> addThree 1 2 3 6
Scalaだとこうかな。
def removeNonUppercase(st: Seq[Char]): Seq[Char] = { for (c <- st if ('A' to 'Z').contains(c)) yield c } removeNonUppercase("HelloWorld") // val res6: Seq[Char] = Vector(H, W) def addThree(x: Int)(y: Int)(z: Int): Int = x + y + z addThree(1)(2)(3) // val res7: Int = 6
一般的なHaskellの型
他の言語同様、Int
型がありマシンのワードサイズによって異なる模様。64ビットCPUの場合Intの範囲は -2^63〜2^63-1 なのでScalaだとLong型相当。他にInteger
型があって、これは有界ではなく大きな数値が扱える。BigInt
的なものか。
factorial :: Integer -> Integer factorial n = product [1 .. n] *Main> factorial 50 30414093201713378043612608166064768844377641568960512000000000000
def factorial(n: BigInt) = (BigInt(1) to n).foldLeft(BigInt(1)){ (a, b) => a * b} factorial(BigInt(50)) // val res8: BigInt = 30414093201713378043612608166064768844377641568960512000000000000
FloatとDouble。Floatは単精度浮動小数点数、Doubleは倍精度浮動小数点型。
円の面積を求める関数で試してみる。
circumference :: Float -> Float circumference r = 2 * pi * r *Main> circumference 4.0 25.132742 circumference' :: Double -> Double circumference' r = 2 * pi * r *Main> circumference' 4.0 25.132741228718345
def circumferenceF(r: Float): Float = 2.0f * math.Pi.toFloat * r def circumferenceD(r: Double): Double = 2.0 * math.Pi * r circumferenceF(4.0f) // val res9: Float = 25.132742 circumferenceD(4.0) // val res10: Double = 25.132741228718345
他にも
- 真偽値
Bool
(True, False
) Char
型(Unicode文字を表し、シングルクォート'
で括って表記する)- タブルも型。空のタプル
()
も型の一つで、ただ一つのあたい()
のみをもつ※これはユニットと呼ばれる。
Haskell 勉強メモ 1
関数型プログラミングをちゃんとやろうと思って「すごいHaskellたのしく学ぼう!」を買って読み始めた。その勉強メモ。自分のメイン言語はScalaなので、気になったところを比較しながら見ていく。
内包表記
Haskellのリスト内包表記。集合の内包的記法の概念。$latex \{2\cdot x | x \in N, x \le 10 \}$ を書いてみる。
Prelude> [x*2 | x <- [1..10]] [2,4,6,8,10,12,14,16,18,20]
Scalaで似たようなものを書く場合は、for 内包表記を使う。
scala> for (i <- 1 to 10) yield i * 2 val res1: IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
もう少し複雑な条件で。
2倍したもののうち、12以上のものからなるリストが欲しい場合
Prelude> [x*2 | x <- [1..10], x*2 >= 12] [12,14,16,18,20]
scala> for (i <- 1 to 10 if i*2 >= 12) yield i*2 val res3: IndexedSeq[Int] = Vector(12, 14, 16, 18, 20)
10以上の全ての奇数を"BANG!"に置き換え、10より小さい全ての奇数を"BOOM!"に置き換える内包表記。
boomBangs xs = [ if x < 10 then "BOOM!" else "BANG!" | x <- xs, odd x] // boomBangs [7..13] // ["BOOM!","BOOM!","BANG!","BANG!"]
def boomBangs(xs: Seq[Int]): Seq[String] = { for (x <- xs if x % 2 == 1) yield { if (x < 10) "BOOM" else "BANG!" } } // boomBangs(7 to 13) // val res1: Seq[String] = Vector(BOOM, BOOM, BANG!, BANG!)
複数の述語を含める場合。Haskellは間まで区切る。and条件
*Main> [ x | x <- [10..20], x /= 13, x /= 15, x /= 19] [10,11,12,14,16,17,18,20]
Scalaは if
の真偽値が想定になるようにしてあげればいい
for (x <- 10 to 20 if x != 13 && x != 15 && x !=19) yield x val res2: IndexedSeq[Int] = Vector(10, 11, 12, 14, 16, 17, 18, 20)
複数のリストから値を取り出す
*Main> [ x+y | x <- [1,2,3], y <- [10,100,1000] ] [11,101,1001,12,102,1002,13,103,1003]
for ( x <- 1 to 3; y <- Seq(10,100,1000) ) yield x + y val res3: IndexedSeq[Int] = Vector(11, 101, 1001, 12, 102, 1002, 13, 103, 1003)
述語論理を追加する
*Main> [ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50] [55,80,100,110]
for ( x <- Seq(2,5,10); y <- Seq(8,10,11) if (x*y > 50) ) yield x*y
直角三角形を見つける
- 3辺の長さ全て整数
- 各辺の長さは10以下
- 周囲の長さは24に等しい
Haskellで書く
// aは斜辺cを超えないかつ、bはaを超えない triples = [(a, b, c) | c <- [1 .. 10], a <- [1 .. c], b <- [1 .. a], a ^ 2 + b ^ 2 == c ^ 2, a + b + c == 24] // [(8,6,10)]
Scalaで書く。Scalaだと ^
みたなメソッドはないから、implicit class
でちょろっと作って使ってみる
implicit class MyInt(value: Int) { def **(n: Int):Int = { n match { case 0 => 1 case k => value * **(k-1) } } } val x:Int = 2 ** 5 // 32 for ( c <- 1 to 10; a <- 1 to c; b <- 1 to a if a ** 2 + b ** 2 == c ** 2 if a + b + c == 24 ) yield (a,b,c) val res5: IndexedSeq[(Int, Int, Int)] = Vector((8,6,10))
全体的にHaskellの方が数学チックな表現ができるなって印象。Scalaと対比でみることで、Scalaの理解が進みそう。
階層構造のデータから特定のプロパティを抽出して、フラットにする
json のような階層構造を持つデータから特定のプロパティを再帰的に探索して、平坦なリストにしたものを取得したい場面に出会ったのでメモする。
データ構造
case class Tree( value: Int, children: List[Tree] )
木構造?というか、自身のリストを持つようなデータ構造。
実装
再帰でなんとかする
case class Tree(value: Int, children: List[Tree]) { val values: List[Int] = { @tailrec def loop(children: List[Tree], vs: List[Int]): List[Int] = { children match { case Nil => vs case ::(head, next) => loop(next ++ head.children, vs appended head.value) } } loop(children, List(value)) } }
実行結果
val tree = { Tree( 1, List( Tree(2, List.empty), Tree(3, List.empty), Tree(4, List( Tree(5, List.empty), Tree(6, List.empty), Tree(7, List( Tree(8, List( Tree(9, List( Tree(12, List( Tree(13, List.empty) )) )) )), Tree(10, List( Tree(11, List.empty) )) )) )) ) ) } println(tree.values) // List(1, 2, 3, 4, 5, 6, 7, 8, 10, 9, 11, 12, 13)</pre>
うん。できてそう。
ちゃんとテストしたわけではない。
問題点
自身のプロパティにアクセスするような実装のため、ほぼ同じ処理なのに共通化しづらいのが難点。再帰的にとってくるような処理をあっちこっちでやると同じ処理が散らばる。
Slick の java.time 対応
普段 PlayFramework + Scala で開発していて、ORMには Slick を使うことが多い。Slickは version 3.3 から java.time
をサポートするようになったのだが、MySQL の型との対応がひどく(ほとんどTEXTにマッピングされて使い物にならない)、DBにMySQLを使う場合には気をつけなければならない。この対応表が私の知る限り、SlickのUpgrade Guidesにしか見当たらなくていつも見失うので、メモしておく。
http://scala-slick.org/doc/3.3.3/upgrade.html#support-for-java.time-columns
MySQL
Java Type SQL Type Example SQL Literal java.time.Instant
TEXT
'2019-02-03T18:20:28.660Z'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
TEXT
'18:20:28.661'
java.time.LocalDateTime
TEXT
'2019-02-03T18:20:28.661'
java.time.OffsetTime
TEXT
'18:20:28.661Z'
java.time.OffsetDateTime
TEXT
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
TEXT
'2019-02-03T18:20:28.661Z[Europe/London]'
H2
Java Type SQL Type Example SQL Literal java.time.Instant
TIMESTAMP(9) WITH TIME ZONE
'2019-02-03T18:20:28.660Z'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
VARCHAR
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
'2019-02-03 18:20:28.661'
java.time.OffsetTime
VARCHAR
'18:20:28.661Z'
java.time.OffsetDateTime
VARCHAR
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
VARCHAR
'2019-02-03T18:20:28.661Z[Europe/London]'
Postgres
Java Type SQL Type Example SQL Literal java.time.Instant
TIMESTAMP
'2019-02-03 18:20:28.66'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
TIME
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
'2019-02-03 18:20:28.661'
java.time.OffsetTime
TIMETZ
'18:20:28.661Z'
java.time.OffsetDateTime
VARCHAR
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
VARCHAR
'2019-02-03T18:20:28.661Z[Europe/London]'
DB2
Java Type SQL Type Example SQL Literal java.time.Instant
VARCHAR(254)
'2019-02-03T18:20:28.660Z'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
VARCHAR(254)
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
'2019-02-03 18:20:28.661'
java.time.OffsetTime
VARCHAR(254)
'18:20:28.661Z'
java.time.OffsetDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z[Europe/London]'
DerbyProfile
java TYPE SQL type example sql literal java.time.Instant
VARCHAR(254)
'2019-02-03T18:20:28.660Z'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
VARCHAR(254)
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
'2019-02-03 18:20:28.661'
java.time.OffsetTime
VARCHAR(254)
'18:20:28.661Z'
java.time.OffsetDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z[Europe/London]'
Oracle
Java Type SQL Type Example SQL Literal java.time.Instant
TIMESTAMP(9) WITH TIME ZONE
TO_TIMESTAMP_TZ('2019-02-03 18:20:28.660 +00', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH')
java.time.LocalDate
DATE
TO_DATE('2019-02-03', 'SYYYY-MM-DD')
java.time.LocalTime
VARCHAR2(254)
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
TO_TIMESTAMP('2019-02-03 18:20:28.661', 'YYYY-MM-DD HH24:MI:SS.FF3')
java.time.OffsetTime
TIMESTAMP(6) WITH TIME ZONE
TO_TIMESTAMP_TZ('1970-01-01 18:20:28.661 +0000', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM')
java.time.OffsetDateTime
TIMESTAMP(6) WITH TIME ZONE
TO_TIMESTAMP_TZ('2019-02-03 18:20:28.661 +0000', 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM')
java.time.ZonedDateTime
TIMESTAMP(6) WITH TIME ZONE
TO_TIMESTAMP_TZ('2019-02-03 18:20:28.661 Europe/London', 'YYYY-MM-DD HH24:MI:SS.FF3 TZR')
SQLite
Java Type SQL Type Example SQL Literal java.time.Instant
TIMESTAMP
1549218028660
java.time.LocalDate
DATE
1549152000000
java.time.LocalTime
VARCHAR(254)
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
1549218028661
java.time.OffsetTime
VARCHAR(254)
'18:20:28.661Z'
java.time.OffsetDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z'
java.time.ZonedDateTime
VARCHAR(254)
'2019-02-03T18:20:28.661Z[Europe/London]'
SQLServer
Java Type SQL Type Example SQL Literal java.time.Instant
DATETIMEOFFSET(6)
(convert(datetimeoffset(6), '2019-02-03 18:20:28.66 '))
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
TIME(6)
(convert(time(6), '18:20:28.661'))
java.time.LocalDateTime
DATETIME2(6)
'2019-02-03 18:20:28.661'
java.time.OffsetTime
VARCHAR(MAX)
'18:20:28.661Z'
java.time.OffsetDateTime
DATETIMEOFFSET(6)
(convert(datetimeoffset(6), '2019-02-03 18:20:28.661 '))
java.time.ZonedDateTime
VARCHAR(MAX)
'2019-02-03T18:20:28.661Z[Europe/London]'
Hsqldb
Java Type SQL Type Example SQL Literal java.time.Instant
TIMESTAMP(9) WITH TIME ZONE
'2019-02-03 18:20:28.66'
java.time.LocalDate
DATE
'2019-02-03'
java.time.LocalTime
TIME(3)
'18:20:28.661'
java.time.LocalDateTime
TIMESTAMP
'2019-02-03 18:20:28.661'
java.time.OffsetTime
TIME(9) WITH TIME ZONE
'18:20:28.661+0:00'
java.time.OffsetDateTime
TIMESTAMP(9) WITH TIME ZONE
'2019-02-03 18:20:28.661+0:00'
java.time.ZonedDateTime
LONGVARCHAR
'2019-02-03T18:20:28.661Z[Europe/London]'