如何在Haskell中转换自定义常量类型?

iqxoj9l9  于 2023-11-18  发布在  其他
关注(0)|答案(1)|浏览(96)

我正在做一个关于Exercism.org的问题(特别是meetup)。为了将其简化为一个最小的例子,我将解决一个稍微简单的问题,说明同样的问题。

  • 在丹尼尔的评论后添加上下文:Exercism有关于它定义的数据结构的预先编写的测试(称为Weekday)。我们可以按照我们喜欢的方式实现它,但测试使用构造函数MondayTuesday等。我希望实现为DayOfWeek,它有相同的命名构造函数,但更多的行为。我们可以说,练习应该使用这个构造函数,但是他们没有,感觉应该有一个比较简单的方式来拿合同,(这个东西被命名为Weekday,我可以按照自己的意愿实现它,前提是我不改变构造函数的名称),并将它指向一个已经实现的类型。

问题的(简短)版本

我有一个数据类型,它使常数,它的行为就像一个库类型。我怎么能别名我自己的数据类型?具体来说,我希望有

data Weekday = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday

字符串
并希望将其别名为Data.Time.Calendar.DayOfWeek以使用经过良好测试的代码。

有更多的上下文和尝试的解决方案

假设这是一个伪科学式的问题,

module Meetup (Weekday(..), findFirstDay) where

import Data.Time.Calendar (Day)

data Weekday = Monday
             | Tuesday
             | Wednesday
             | Thursday
             | Friday
             | Saturday
             | Sunday

findFirstDay :: Weekday -> Integer -> Int -> Day
findFirstDay weekday year month = error "You have to implement this function"


我们应该修改这一点(例如,我们可能希望在Weekday数据类型上使用deriving Eq)。
解决这个问题的尝试可能看起来像

import Data.Time.Calendar (Day, fromGregorian, dayOfWeek)

{- Get all [Mondays|Tuesdays|..|Sundays] in the month -}
allWeekdayOfMonth :: DayOfWeek -> Integer -> Int -> [Day]
allWeekdayOfMonth weekday year month = filter (\x -> weekday == dayOfWeek x) [fromGregorian year month day| day<-[1..31] ]

almostFindFirstDay :: DayOfWeek -> Integer -> Int -> Day
almostFindFirstDay weekday year month = head $ allWeekdayOfMonth weekday year month


这个函数不是我们想要的函数的原因是因为第一个参数是不同的类型(DayOfWeek vs Weekday)。具体来说,测试是用Weekday作为导入编写的,所以我不能只是切换数据库。
我的理解是,这是我可以使用type别名的地方,这将使我的签名更具可读性,但对编译器不可见(例如,如果我想从签名中明显看出Int[埃格]代表什么,我可以做type Month=Int等)。
这是我试图解决的问题:

module Meetup (Weekday(..), findFirstDay) where

import Data.Time.Calendar (Day, fromGregorian, dayOfWeek, DayOfWeek(..))

type Weekday = DayOfWeek

allWeekdayOfMonth :: DayOfWeek -> Integer -> Int -> [Day]
allWeekdayOfMonth weekday year month = filter (\x -> weekday == dayOfWeek x) [fromGregorian year month day| day<-[1..31] ]

almostFindFirstDay :: DayOfWeek -> Integer -> Int -> Day
almostFindFirstDay weekday year month = head $ allWeekdayOfMonth weekday year month

findFirstDay :: Weekday -> Integer -> Int -> Day
findFirstDay weekday year month = head $ allWeekdayOfMonth weekday year month


ghci中,如果我复制模块行下面的所有内容,则可以这样做:

ghci> findFirstDay Monday 2023 11
2023-11-06


但是当我在Exercism上使用模块系统时,

Pattern match(es) are non-exhaustive
    In an equation for ‘findFirstDay’:
        Patterns of type ‘DayOfWeek’, ‘Integer’,
                         ‘Int’ not matched:
            Monday (GHC.Num.Integer.IS _) _
            Monday (GHC.Num.Integer.IP _) _
            Monday (GHC.Num.Integer.IN _) _
            Tuesday (GHC.Num.Integer.IS _) _
            ...

注意我不想找的东西

我知道我可以使用Enum和位置、已知日期、日期差和模运算来很容易地解决这个问题。这不是一个“算法是什么?”的情况,但我很惊讶重用一个在其他地方有不同名称的类型是多么困难,并寻找解决此类问题的规范方法。

qxgroojn

qxgroojn1#

假设你的客户端有一个这样的导入列表:

import Meetup (Weekday(..))

字符串
在这种情况下,为了让(..)导入任何东西,我们必须创建一个新类型--一个简单的type别名是不行的。这确实有点令人讨厌,也许应该成为GHC语言扩展的主题。下面是一个如何使用newtype创建新类型的最小示例:

{-# Language PatternSynonyms #-}
module Meetup (Weekday(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)) where

import qualified Data.Time.Calendar as C

newtype Weekday = Weekday { getDayOfWeek :: C.DayOfWeek }
pattern Monday    = Weekday C.Monday
pattern Tuesday   = Weekday C.Tuesday
pattern Wednesday = Weekday C.Wednesday
pattern Thursday  = Weekday C.Thursday
pattern Friday    = Weekday C.Friday
pattern Saturday  = Weekday C.Saturday
pattern Sunday    = Weekday C.Sunday


现在,在Meetup模块中,可以在这个新类型和原始类型之间进行转换:

Weekday :: C.DayOfWeek -> Weekday
getDayOfWeek :: Weekday -> C.DayOfWeek


WeekdayC.DayOfWeek在内存中的表示形式保证是相同的--除非在特殊情况下,上述转换函数通常会在编译后的代码中消失。

相关问题