Haskell 从数据构造函数的不相交并集获取值
给定此类型构造函数:Haskell 从数据构造函数的不相交并集获取值,haskell,Haskell,给定此类型构造函数: data DatabaseItem = DbString String | DbNumber Integer | DbDate UTCTime 我可以编写一个函数,将数据库项解包到例如UTCTime: getDate :: DatabaseItem -> Maybe UTCTime getDate (DbDate a) = Just a getDate _ = Nothing 我不想为3个
data DatabaseItem = DbString String
| DbNumber Integer
| DbDate UTCTime
我可以编写一个函数,将数据库项
解包到例如UTCTime
:
getDate :: DatabaseItem -> Maybe UTCTime
getDate (DbDate a) = Just a
getDate _ = Nothing
我不想为3个数据构造函数中的每一个都编写这样的函数,而是想要一个通用函数(这也意味着我不再需要或者),但我不太清楚如何编写它。我试过:
unwrap :: DatabaseItem -> a
unwrap (i a) = a
-- error: Parse error in pattern: i
以及:
两者都不编译。有人能指出这些问题的症结所在,并提出更好的实施方案吗?谢谢 用户定义的数据类型的一种常见模式是为它们定义一个亚同态;e、 g.在标准库中,有foldr
用于[]
,maybe
用于maybe
,bool
用于bool
,或者或者或者等等。从本质上说,亚同态是将模式匹配具体化为函数,再加上对递归类型的一点幻想,这与本文无关
对于您的类型,它可能如下所示:
databaseItem ::
(String -> a) ->
(Integer -> a) ->
(UTCTime -> a) ->
(DatabaseItem -> a)
databaseItem string number date item = case item of
DbString s -> string s
DbNumber n -> number n
DbDate d -> date d
> unwrap (DbNumber 1) :: Integer
1
> 1 + unwrap (DbNumber 1)
2
> 1 + unwrap (DbString "foo")
*** Exception: unwrap: type mismatch
CallStack (from HasCallStack):
error, called at Unwrap.hs:16:25 in main:Main
>
例如,如果要获取表示项目的字符串,可以使用:
databaseItem id show (formatTime defaultTimeLocale "%c")
:: DatabaseItem -> String
您还可以根据它实现特定于构造函数的提取器
getDate = databaseItem (const Nothing) (const Nothing) Just
如果你对此感兴趣的话,还有更多关于变形的资料,以及为什么它们是消费散布在网络上的ADT的正确选择。我同意丹尼尔的建议,但值得指出的是,lens
的概念也让你这么做(还有更多!)。特别是考虑到你在评论中链接的要点,这可能会很有趣
{-# Language TemplateHaskell #-}
import Data.Time.Clock
import Control.Lens.TH
data DatabaseItem = DbString String
| DbNumber Integer
| DbDate UTCTime
makePrisms ''DatabaseItem
这会自动生成\u DbString
、\u DbNumber
和\u DbDate
函数,这些函数可以轻松地进行内联调整,以完成getString
、getNumber
和getDate
将要完成的任务。即:
main> import Control.Lens
main> :t (^? _DbString)
(^? _DbString) :: DatabaseItem -> Maybe String
main> :t (^? _DbNumber)
(^? _DbNumber) :: DatabaseItem -> Maybe Integer
main> :t (^? _DbDate)
(^? _DbDate) :: DatabaseItem -> Maybe UTCTime
然而,lens
的功能更强大。它可以通过过滤数据库来收集一行中的一个变体。例如,我可以使用数据库中的所有日期::[DatabaseItem]
。。每个_DbDate
编辑:添加了对评论的回复
如果您不明白为什么没有人直接回答您的问题,请注意,您可以使用数据编写展开,如下所示。可键入:
import Data.Maybe
import Data.Time
import Data.Typeable
data DatabaseItem = DbString String
| DbNumber Integer
| DbDate UTCTime
unwrap :: (Typeable a) => DatabaseItem -> a
unwrap x = case x of
DbString x -> go x
DbNumber x -> go x
DbDate x -> go x
where go :: (Typeable a, Typeable b) => a -> b
go = fromMaybe (error "unwrap: type mismatch") . cast
它可以这样使用:
databaseItem ::
(String -> a) ->
(Integer -> a) ->
(UTCTime -> a) ->
(DatabaseItem -> a)
databaseItem string number date item = case item of
DbString s -> string s
DbNumber n -> number n
DbDate d -> date d
> unwrap (DbNumber 1) :: Integer
1
> 1 + unwrap (DbNumber 1)
2
> 1 + unwrap (DbString "foo")
*** Exception: unwrap: type mismatch
CallStack (from HasCallStack):
error, called at Unwrap.hs:16:25 in main:Main
>
现在,尝试在一些实际代码中使用它。您会发现这是一次非常令人沮丧的经历,并意识到两者都有各自的功能,如:
getString (DbString x) = Just x
getString _ = Nothing
或者使用一个变形或棱镜将是一个更好的方法
在后续的评论中,您询问为什么Haskell版本比不需要强制转换的TypeScript版本复杂得多:
type DatabaseItem<T> = { value: T }
let DbString = (value: string) => ({ value })
let DbNumber = (value: number) => ({ value })
let DbDate = (value: Date) => ({ value })
function unwrap<T>({ value }: DatabaseItem<T>) {
return value
}
unwrap(DbString("Hello")) // "Hello"
unwrap(DbNumber(42)) // 42
unwrap(DbDate(new Date)) // Date
听起来像是镜头棱镜的用例。如果DatabaseItem
是一个类型索引的GADT,那么最后一个unwrap
将编译,类型为DatabaseItem a->a
。但这需要对原始数据类型进行一些更改。如果不了解如何使用getDate
以及如何使用unwrap
,回答这个问题会非常困难。即使unwrap
的第二个定义是有效的Haskell,它产生的值也不会比DatabaseItem
更有用,因为它仍然不能用作字符串
、整数
或UTCTime
(因为它可能是其中的任何一个)。您打算如何使用这些功能?你想解决什么问题?我很难理解这一点。@AlexisKing这里有更完整的用法@bcherny问题更多的是,一旦你实现了unwrap
,你会如何想象?很清楚如何使用您已经实现的其他函数。我想您刚刚让我了解了变形以及它们的重要性。谢谢您的示例。为什么哈斯克尔的情况如此复杂?在TypeScript中,不需要强制转换,因为unwrap
的参数是结构化类型的-如果参数具有预期的结构,那么代码将编译。不同之处在于,在TypeScript示例中,您在type参数中保留了类型信息,而Haskell代码将其丢弃。您可以轻松地编写数据数据库Item a=Item a
,然后按照预期展开(Item 5)==5
。但如果您这样做,那么DatabaseItem类型的值只有在您确切知道它们的类型时才有用:您无法在构建值后恢复该信息。在我看来,你的TypeScript代码也是如此,但我对TypeScript的了解还不足以确定。正如@amalloy所指出的,你的Haskell和TypeScript示例并不具有可比性。我在回答中添加了一条注释,以提供我认为与您的TypeScript示例相当的Haskell。感谢您的详细回复@K.a.Buhr!谢谢你简洁的解决方案!我用其他语言玩过镜头,但需要用Haskell来复习。