Haskell 导出记录仅供读取访问

标签 haskell record encapsulation accessor

我有一个使用记录语法的 Haskell 类型。

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_

但这似乎是解决问题的一种相当迂回的方法。有没有办法继续使用记录语法,同时仅导出记录名称以进行读取访问,而不导出写入访问?

最佳答案

隐藏构造函数,然后为每个字段定义新的访问器函数是一种解决方案,但对于具有大量字段的记录来说,这可能会变得乏味。

这是使用新的 HasField 的解决方案GHC 8.2.1 中的类型类这避免了为每个字段定义函数。

这个想法是定义一个辅助新类型,如下所示:

{-# 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 的字段访问器不应该隐藏;它们必须在 HasField 的范围内Moat 的实例开始。

每个面向公众的 API 中的函数都应返回并接收 Moat Foo s 而不是 Foo s。

为了使访问器语法稍微不那么冗长,我们可以转向 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来自客户端的构造函数。

关于Haskell 导出记录仅供读取访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45655819/

相关文章:

嵌套数组/记录的 Azure 流分析查询

c# - C#中有 "Records"吗?

C++ 私有(private)真的是私有(private)的吗?

其他语言中的 Python 描述符协议(protocol)模拟?

haskell - Haskell 的 SDL 库中的操纵杆事件处理

haskell - 在 Writer monad 中交换 `mappend`

haskell - 了解 Haskell 类型签名

sql - 重写函数以不返回记录类型

haskell - Haskell 的 <|> 运算符有什么作用?

对象外的JavaScript函数封装