1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Idris2の文法についての覚書

1
Posted at

概要

Idris2の文法についての簡単な覚書になります。

ClaudeHaskellとの違いに焦点を絞ってロードマップを作成させたものから、いくつかピックアップしてまとめているので、Idris2の全ての文法について解説した記事ではありませんので、ご注意ください。

また、Idris2初心者がまとめた記事になりますので、何か間違いなどがあれば、ご指摘お願いいたします。

Idris2の文法について

文法的にはほとんどHaskellですが、以下の点で大きく異なります。

  • 依存型が存在する (型が値に依存する)
  • 型駆動開発 (型定義必須)

依存型について

依存型は、型が実際の値に依存するという性質を持つ型システムです。
型チェック時に値の情報を使用します。

依存型の例としてベクトル型があります。
Vect n aの様に記述し、nは要素数、aは要素の型を指定します。
実例として、要素数3のInt型のベクトルは以下の様に定義します。
この場合、実際の値 [1, 2, 3]の要素数が型に情報として含まれています。

import Data.Vect
let vec : Vect 3 Int = [1, 2, 3]

もう少し高度な例としては、ベクトルの計算結果の要素数を指定することもできます。
連結は以下の様に定義でき、結果の型であるVect (n + m) aは要素数がn+mであるa型のベクトルとなります。

append : Vect n a -> Vect m a -> Vect (n + m) a
append [] ys = ys
append (x :: xs) ys = x :: append xs ys

通常のHaskellと同様に以下の様に利用できます。

putStrLn $ show $ append [1, 2] [3, 4] -- [1, 2, 3, 4]

型駆動開発について

Idris2の開発は、型シグネチャを起点として実装を開始するので、型駆動開発と呼ばれます。

実際に実行してみるとわかりますが、Haskellの様に実装を書いただけではエラーになります。
必ず型(型シグネチャ)も記述する必要があります。

上記の例のappendを1行目の型シグネチャを記述していない状態で、実行(チェック)すると、以下の様なエラーが出力されます。

Error: No type declaration for Main.append.

hello:5:1--5:18
 1 | module Main
 2 | 
 3 | import Data.Vect
 4 | 
 5 | append [] ys = ys
     ^^^^^^^^^^^^^^^^^

厳密にいうと、一部自動推論が適用されることがあるので、型シグネチャを記載していなくても実行させることはできますが、明示的に定義しておいた方が無難です。

let x1 = 5 -- 自動推論される
let vec1 = [1, 2, 3] -- Data.VectをimportしていなければLisetに自動推論される

add x y = x + y
putStrLn $ show $ add 1 2 -- 自動推論される
putStrLn $ show $ add 1.0 2.0 -- エラー

let res : Double = add 1.0 2.0
putStrLn $ show res -- OK

全域関数と部分関数

Idris2では全域関数(total, covering)と部分関数(partial)を明示的にアノテーションできます。

未指定の場合はcoveringとなりますが、下表の通り停止性が保証されないので、基本的にはtotalを目指して実装することになります。

アノテーション 網羅性 停止性
total
covering
partial

Vectは長さ情報を持つので、以下の様に、網羅性と停止性を持つように定義できます。

total
headVect : Vect (S n) a -> a
headVect (x :: xs) = x

Listで同様に定義しようとすると、partialになります。
partial関数を呼び出す際には、呼び出し元もpartialにする必要があるので注意してください

partial
headList : List a -> a
headList (x :: xs) = x

上記の処理は、空リストのケースを考慮していないため、partialとなっています。
totalにする場合は、MaybeEitherなどを利用して、空リストのケースについて明示的に対応する必要があります。

total
headListMaybe : List a -> Maybe a
headListMaybe [] = Nothing
headListMaybe (x :: xs) = Just x

coveringでなければならないケースは殆どないはずなので、基本的にはtotalpartialのどちらかに倒してください。
totalではなく、純粋なcoveringの実例として、以下のような定義が考えられますが、停止性が保証されておらず、実行すると無限ループになります。

someFunction : Int -> Int
someFunction n = someFunction (n - 1)

Interface

以下はNatSortable Interfaceを追加した例になります。
Named Interfaceとしてdescendingを追加しているので、逆順にも対応しています。

interface Sortable a where
    compare : a -> a -> Ordering

Sortable Nat where
    compare x y =
        if x < y then LT
        else if x > y then GT
        else EQ

[descending] Sortable Nat where
    compare x y =
        if x > y then LT
        else if x < y then GT
        else EQ

sortBySortable : Sortable a => List a -> List a
sortBySortable [] = []
sortBySortable (x :: xs) =
    let smaller = sortBySortable (filter (\y => compare y x == LT) xs)
        larger = sortBySortable (filter (\y => compare y x /= LT) xs)
    in smaller ++ [x] ++ larger

main : IO ()
main = do
    let xs : List Nat = [1, 5, 2]
    putStrLn $ show $ sortBySortable xs
    putStrLn $ show $ sortBySortable @{descending} xs

モジュール管理&アクセス修飾子

モジュール名は以下の様に、フォルダ名とファイル名をドットで繋いだものにします。

project/
  Math/
    Basic.idr        -- module Math.Basic
    Advanced.idr     -- module Math.Advanced
  Main.idr           -- module Main(デフォルト)

各モジュールに定義された関数や型などは、デフォルトでprivateなので、外部公開する場合は、用途に応じて以下のアクセス修飾子に変更します。

  • private : 型も定義も公開しない
  • export : 型のみ公開 (型抽象)
  • public export : 型と定義を公開

以下は各修飾子を利用した実例です。

ライブラリ側のコード (`AccessorLibrary/Library.idr`)
private
data PrivateNumber = MkPrivateNumber Int

private
Show PrivateNumber where
    show (MkPrivateNumber n) = show n

private
makePrivateNumber : Int -> PrivateNumber
makePrivateNumber n = MkPrivateNumber n

export
data ExportedNumber = MkExportedNumber Int

export
Show ExportedNumber where
    show (MkExportedNumber n) = show n

export
makeExportedNumber : Int -> ExportedNumber
makeExportedNumber n = MkExportedNumber n

public export
data PublicExportedNumber = MkPublicExportedNumber Int

public export
Show PublicExportedNumber where
    show (MkPublicExportedNumber n) = show n

public export
makePublicExportedNumber : Int -> PublicExportedNumber
makePublicExportedNumber n = MkPublicExportedNumber n
呼び出し側のコード(`Main.idr`)
import AccessorLibrary.Library

main : IO ()
main = do
    -- let n1 = MkPrivateNumber 1 -- privateなので参照できない
    -- putStrLn $ show $ makePrivateNumber 1 -- privateなので参照できない
    -- let n2 = MkExportedNumber 1 -- exportなので実装は参照できない
    putStrLn $ show $ makeExportedNumber 2
    let n3 = MkPublicExportedNumber 3
    putStrLn $ show $ n3
    putStrLn "OK"

with

Haskellに存在しないwithが利用できます。
以下のような構造になっています。

function : (args) -> ReturnType
function args with (expression)
  function pattern1 | result1 = body1
  function pattern2 | result2 = body2

以下の例は、引数のリストの各要素が偶数か判定するものです。

allEven : List Int -> Bool
allEven [] = True
allEven (x :: xs) with (x `mod` 2 == 0, allEven xs) 
    allEven (x :: xs) | (True, True) = True
    allEven (x :: xs) | (True, False) = False
    allEven (x :: xs) | (False, _) = False

この場合、パターンの部分は、以下の様に省略して書けます。

allEven : List Int -> Bool
allEven [] = True
allEven (x :: xs) with (x `mod` 2 == 0, allEven xs) 
    _ | (True, True) = True
    _ | (True, False) = False
    _ | (False, _) = False

expression部分に渡すデータを整形して処理したい場合があります。
以下のケースでは、元データを逆順にソートとしてから処理を行っています。
Idris2では、このデータ整形のことをViewと言っているようです。

Viewを利用した例
data ReverseView : List a -> Type where
  Nil : ReverseView []
  Append : (xs : List a) -> (x : a) -> ReverseView (xs ++ [x])

reverseView : (xs : List a) -> ReverseView xs
reverseView [] = Nil
reverseView (x :: xs) = 
  case reverseView xs of
    Nil => Append [] x
    Append ys y => Append (x :: ys) y

searchFromLast : (xs : List a) -> (a -> Bool) -> Maybe a
searchFromLast xs p with (reverseView xs)
    searchFromLast [] p | Nil = Nothing
    searchFromLast _ p | Append ys x = 
        if p x then Just x else searchFromLast ys p

main : IO ()
main = do
    putStrLn $ show $ searchFromLast [1, 2, 3, 4, 5] (\x => x `mod` 2 == 0)

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?