Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 从数据构造函数的不相交并集获取值_Haskell - Fatal编程技术网

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来复习。