ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

Haskellの型を触ってみた

この記事は Holmes Advent Calendar 2020 - Qiita 16 日目の記事です。


こんにちは。Holmesでインフラエンジニアをしている渡辺です。

Holmesでは社内LT大会が何度か開催されていて、趣味の話や技術話など業務に関係あることないことを持ち寄り、みんなそれぞれ好きなことを話しています。自分も発表に参加したので、ここではその時に自分が発表した内容を紹介できればと思います。

エンジニアであれば、自分が好きなプログラミング言語があるという人は多いと思います。自分が好きな言語はいろいろありますが、Haskellが好きなので今回はそれについてのお話です。

型について

大抵のプログラミング言語で扱える型としては以下のような物があると思います。

-- 文字列型
String s = "Hello, World"

-- 数値型(整数)
Integer x = 256

-- 数値型(浮動小数点数)
Double d = 3.14159265

-- 論理型(ブーリアン型)
Bool b = true

-- 配列型
List xs = [2, 3, 5, 7, 11]

通常取り扱うデータとしてはこのような型があれば普通のプログラムを書く分には困らないでしょう。ですが、例えば成功・失敗を表したい時にはどうするでしょうか。関数の結果として返り値で取得したい場合には構造体などのデータ構造を用いて取得する場合もあると思います。もしくは例外処理で対応することも可能でしょう。Haskellでは次のような書き方を使うこともできます。

Maybe型

失敗を返すかもしれない関数の並びから計算を構築できます。失敗はNothingで表現し、成功はJust a (aは任意の型)で返すことができます。

data Maybe a = Nothing | Just a

-- x が 2 で割り切れない場合には失敗する
div2 :: Int -> Maybe Int
div2 x = if even x
            then Just (x `div` 2)
            else Nothing

使用例は下記の通りです。ghciでの実行例になります。

ghci> :t Just 123
Just 123 :: Num a => Maybe a

ghci> :t Nothing
Nothing :: Maybe a

ghci> div2 6
Just 3

ghci> div2 3
Nothing

Either型

失敗あるいはエラー処理を構造化する例外処理をつかう関数のならびから計算を構築できます。失敗時にもただ終了するのではなく、エラーの内容から次のアクションを設定することができます。

data Either a b = Left a | Right b

head' :: [a] -> Either String a
head' [] = Left "empty list"
head' (x:xs) = Right x

使用例は下記の通りです。

ghci> :t Right 123
Right 123 :: Num b => Either a b

ghci> :t Left "failure"
Left "failure" :: Either [char] b

ghci> head' [1,2,3]
Right 1

ghci> head' []
Left "empty list"

さて、通常の型の使用方法としてはこれぐらいにして、もっと型を駆使して面白いことはできないでしょうか。

型レベル計算

Haskellは型の表現力が高く、型レベル自然数という物が扱えます。

まずは下準備として、今回の記事の書き方を使うにあたって必要なGHCの機能を有効化します。

{-# LANGUAGE FunctionalDependencies, MultiParamTypeClasses, UndecidableInstances, FlexibleInstances #-}

型でペアノ数を表していきます。Z は 0 を表します (zero)。S は (+1) を表します (successor,後者)。S Z は 1 、 S (S Z) は 2 という意味になります。

-- ペアノ数の定義
data Z
data S a

型クラスとそのインスタンスで関数を定義しています。

-- 型レベルの演算として加算を定義
class Add a b c | a b -> c

-- 0 + a は a になる
instance Add Z a a

-- a + b の結果を c に入れる
instance Add a b c => Add (S a) b (S c)

足し算(add), zero, oneについては型のみで表現し、two, threeについてもそれらの組み合わせでできています。

add :: Add a b c => a -> b -> c
add = undefined

zero = undefined :: Z
one = undefined :: S Z

two = add one one
three = add one two

ここまで書くと実際に型での計算が確認できるようになります。

ghci> :t one
one :: S Z

ghci> :t two
two :: S (S Z)

ghci> :t three
three :: S (S (S Z))

というわけで、型をうまく扱うことで型の上で数字の取り扱いができるようになりました。 今回の内容ではあまり高度な例とも実用的な例とも言えないかもしれませんが、型の表現力の一端は感じていただけたのでは無いでしょうか。

感想

業務では使用していないプログラミング言語なので難しかったという声はありましたが、内容を理解したうえで面白かったと言っていただけたり、Haskell関数型言語に興味を持ってくれた人がいたのでとても有意義で楽しい社内LT大会になりました。

最後に

Holmesはエンジニア・デザイナーを募集しています。 興味がある方はぜひこちらからご連絡ください!

lab.holmescloud.com

lab.holmescloud.com