2017-08-12 11 views
5

レコードの構文を使用するHaskell型があります。Haskell Export Record Only for Read Access

data Foo a = Foo { getDims :: (Int, Int), getData :: [a] } 

ユーザーが無効なオブジェクトを構築することができないように私は、Foo値コンストラクタをエクスポートする必要はありません。しかし、getDimsをエクスポートしたいので、ユーザーはデータ構造の寸法を取得できます。私はこの

module Data.ModuleName(Foo(getDims)) where 

を行う場合、ユーザーは、寸法を取得するためにgetDimsを使用することができますが、問題は、彼らはまた、フィールドを更新するレコード更新の構文を使用することができるということです。

getDims foo -- This is allowed (as intended) 
foo { getDims = (999, 999) } -- But this is also allowed (not intended) 

データを無効な状態にするため、後者を避けたいと思います。私は単にレコードを使うことはできないと思います。

data Foo a = Foo { getDims_ :: (Int, Int), getData :: [a] } 

getDims :: Foo a -> (Int, Int) 
getDims = getDims_ 

しかし、これは問題を回避するためにかなりラウンドアバウトな方法のようです。書き込みアクセスではなく、読み取りアクセスのためにレコード名をエクスポートするだけで、レコード構文を引き続き使用する方法はありますか?

+3

いいえ、ありません。後者の例はこれを達成するための標準的な方法であり、依然としてレコードを使用していることに注意してください。あなたが「ラウンドアバウト」と呼んでいるのは、実際には余分な2行のコードだけです。 – user2407038

答えて

5

コンストラクタを非表示にしてから、各フィールドに新しいアクセサー関数を定義することは解決策ですが、多数のフィールドを持つレコードが面倒なことがあります。

新しいフィールドHasFieldのタイプメッシュのGHC 8.2.1には、フィールドごとに関数を定義する必要がなくなりました。

アイデアは、このような補助のnewtypeを定義することです:

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE UndecidableInstances #-} 
{-# LANGUAGE TypeApplications #-} 
{-# LANGUAGE ScopedTypeVariables #-}  
{-# LANGUAGE PolyKinds #-} -- Important, obscure errors happen without this. 

import GHC.Records (HasField(..)) 

-- Do NOT export the actual constructor! 
newtype Moat r = Moat r 

-- Export this instead. 
moat :: r -> Moat r 
moat = Moat 

-- If r has a field, Moat r also has that field 
instance HasField s r v => HasField s (Moat r) v where 
    getField (Moat r) = getField @s r 

記録rのすべてのフィールドは、次の構文で、Moat rからづけし次のようになります。

λ :set -XDataKinds 
λ :set -XTypeApplications 
λ getField @"getDims" $ moat (Foo (5,5) ['s']) 
(5,5) 

Fooコンストラクタ必要がありますクライアントから隠される。ただし、Fooのフィールドアクセサーはでなく、である必要があります。彼らがでキックするMoatHasFieldインスタンスのスコープ内になければなりません。

戻り、Moat Fooの代わりにFoo Sを受けるべきで、あなたの一般向けAPIのすべての機能

わずかに少ない冗長アクセサ構文を作るために、我々はOverloadedLabelsに回すことができます。

import GHC.OverloadedLabels 

newtype Label r v = Label { field :: r -> v } 

instance HasField l r v => IsLabel l (Label r v) where 
    fromLabel = Label (getField @l) 

GHCiのでは:

λ :set -XOverloadedLabels 
λ field #getDims $ moat (Foo (5,5) ['s']) 
(5,5) 

代わりのFooコンストラクタを隠し、別のオプションは次のようになりますFooを完全に公開し、図書館内にMoatを定義すると、いずれも非表示になりますMoatクライアントからのコンストラクタ。

関連する問題