Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

@nwtgck

HaskellでUnion typeを実現するopen-unionを使ってみました

はじめに

HaskellでもTypeScriptやDottyやCrystalのようにUnion typeが欲しくなるときがありますよね

-- いちいち、こんな風に書いたりしたくないですし、
data IntOrChar         = I  Int | C  Char
-- 型が増えたバージョンを用意する必要が出たときに、いちいち増やすために名前が被らないようにするのは、嫌ですよね
data IntOrCharOrString = I' Int | C' Char | S' String

open-unionというライブラリを食わず嫌いでやっていなかったので、今回触れてみました(前回の記事でUnboxedSumsは用途が違うとご指摘も受けましたし)。

環境

Stack: lts-10.5 (GHCは8.2.2になります)
open-union-0.3.0.0

GitHubの練習用リポジトリ

nwtgck/open-union-prac-haskell

リポジトリのapp/に動くコードが入ってます。

普通の型 => Union Type

まずは、DataKinds言語拡張を有効して、Data.OpenUnionをインポートします

{-# LANGUAGE DataKinds #-}
import Data.OpenUnion

普通の型をUnion typeにするときはliftUnionを使います。以下は例です。

let u1 :: Union '[Char, Int]
    u1 = liftUnion (35 :: Int)
print u1
-- => Union (35 :: Int)
let u2 :: Union '[Char, Int]
    u2 = liftUnion 'j'
print u2
-- => Union ('j' :: Char)

これを使えば、以下のように、型安全なヘテロリストも作れます

let hList1 :: [ Union '[Maybe Bool, String, ()] ]
    hList1 = [ liftUnion "apple"
                , liftUnion (Just True)
                , liftUnion ()
                , liftUnion "orange"
                , liftUnion (Nothing :: Maybe Bool)
                ]

型のパターンマッチング

こんな感じで、各型のときの処理を書いていきます
(パターンマッチングと言っていいか分からないですが...)

言語拡張ScopedTypeVariablesDataKindsを有効にします。

-- (from: https://github.com/bfops/open-union)

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DataKinds #-}


import Data.OpenUnion

showMyUnion :: Union '[Char, Int, [()]] -> String
showMyUnion
    =  (\(c :: Char)   -> "char: " ++ show c)
    @> (\(i :: Int)    -> "int: " ++ show i)
    @> (\(l :: [()])   -> "list length: " ++ show (length l))
    @> (\(s :: String) -> "string: " ++ s)
    @> typesExhausted

main :: IO ()
main = do
    putStrLn $ showMyUnion $ liftUnion (4 :: Int)
    putStrLn $ showMyUnion $ liftUnion 'a'
    putStrLn $ showMyUnion $ liftUnion [(), ()]

(これは公式のコードを使わさせていただきました)

Union type => より広いUnion type

Union '[Char, Int] => Union '[Char, Int, Bool]にするようなことをしたいときは、reUnionを使います。

let u1     :: Union '[Char, Int]
    u1     = liftUnion (35 :: Int)
let wideU1 :: Union '[Char, Int, Bool]
    wideU1 = reUnion u1

型の順番が違うUnion

Union '[Char, Int] => Union '[Int, Char]のようなときです。
これもreUnionを使います。

let u1     :: Union '[Char, Int]
    u1     = liftUnion (35 :: Int)

let u1'    :: Union '[Int, Char]
    u1'    = reUnion u1

Union type => 普通の型

Union [Char, Int, Bool] => Intにするときのように普通の型にするときに使い方

以下は使用例です。

これはUnion '[Char, Int, Bool] => Intにしたい例です。

let u3 :: Union '[Char, Int, Bool]
    u3      = liftUnion (3 :: Int)
let narrowU3 :: Either (Union '[Char, Bool]) Int
    narrowU3 = restrict u3
print narrowU3
-- => Right 3 

(変換で得たい型はEither l rrに書く感じです。)
(この関数の捉え方を変えれば、Intを消して、Union '[Char, Bool]なものはLeftに来るように考えることもできそうです)

restrictがあれば、以下の例のように、「要素がIntときに掛け算をする」といったこともできます。

import Data.Either.Combinators (rightToMaybe)

myMul :: Union '[Int, Bool, Char] -> Union '[(), Int] -> Maybe Int
myMul x y = do
    i  <- rightToMaybe (restrict x)
    j  <- rightToMaybe (restrict y)
    return (i * j)
let u4 = liftUnion (4 :: Int) :: Union '[Int, Bool, Char]
let u5 = liftUnion (3 :: Int) :: Union '[(), Int]
print (myMul u4 u5)
-- => Just 12
4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What is going on with this article?