- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我对数据类型感到困惑。假设我们有
{-# LANGUAGE DataKinds #-}
...
data Format
= Photo
{ bytes :: Int
}
| Video
{ bytes :: Int
, durationSec :: Int
}
我想让 then 与提升类型一起运行:
createVideo :: Int -> Int -> 'Video
createVideo _ _ = error "Not implemented"
编译器会要求我们提供参数,并用它们给出 msg 'Video Int Int has kind `Format'。我希望这种类似于 kotlin 的编译时行为:
sealed class Format {
abstract val bytes: Int
data class Photo(override val bytes: Int) : Format()
data class Video(override val bytes: Int, val durationSec: Int) : Format()
}
private fun createVideo(bytes: Int, durationSec: Int) : Format.Video {
return Format.Video(bytes, durationSec)
}
fun main() {
val video: Format = createVideo(bytes = 0, durationSec = 0) // correct
val video2: Format.Video = createVideo(bytes = 0, durationSec = 0) // correct
val video3: Format.Photo = createVideo(bytes = 0, durationSec = 0) // compiler error
}
最佳答案
数据类型不提供在编译时检查数据结构值的直接机制。换句话说,如果你有一个数据类型:
data Format
= Photo
{ bytes :: Int
}
| Video
{ bytes :: Int
, durationSec :: Int
}
适合表示照片和视频,使用 DataKinds
扩展将其提升到类型级别 not 允许您编写仅接受 10 秒的函数视频:
processTenSecondVideo :: Video bytes 10 -> IO () -- does not work
甚至生成保证是视频的 Format
值:
createVideo1 :: Int -> Int -> Format -- works
createVideo2 :: Int -> Int -> Video bytes duration -- does not work
从技术的角度来看,DataKinds
扩展推广的新类型 Photo
和 Video
是一种类型(具体来说,不支持值的新类型 Format
)。因此,虽然存在 Int
类型的值(因为 Int
属于 *
类型,即具有值的类型),但没有类型的值照片 0
或 视频 128000 10
存在。因此,您不能将这些类型用作函数的返回类型(或参数类型)。
那么,数据种类有什么用呢?好吧,如果你有一个数据结构,你想在编译时以某种方式对其进行约束,你就不会提升那个数据结构。相反,您提升其他数据结构以用作工具来编写类型级程序来约束目标数据结构。
在您的示例中,您想要对数据结构施加的约束确实非常适度:您想要检查数据结构是否是 Video
。您不需要数据类型来执行此操作。如果您只是稍微重新安排一下数据结构,那么常规的 Haskell 类型就足够了。将格式分为两种类型:
data Photo = Photo {bytes :: Int}
data Video = Video {bytes :: Int, durationSec :: Int}
足以在编译时区分视频和非视频。如果您的程序的某些部分要处理可以是视频或照片的值,则可以引入求和类型:
data Format = PhotoF Photo | VideoF Video
当约束变得更加复杂时,数据类型就会变得有用。例如,假设您想审查照片视频以保证全家人的安全:
censorPhoto :: Photo -> Photo
censorVideo :: Video -> Video
并允许用户生成屏幕截图:
screenShot :: Video -> Photo
您可能希望在编译时确保您不会意外地对视频进行两次审查,或者向年轻观众展示未经审查的视频,或者让其他人通过截取未经审查的视频并将其作为被审查的照片。
您可以通过引入更多类型来实现:
data UncensoredPhoto = UncensoredPhoto {bytes :: Int}
data UncensoredVideo = UncensoredVideo {bytes :: Int, durationSec :: Int}
data UncensoredFormat = UncensoredPhotoF UncensoredPhoto | UncensoredVideoF UncensoredVideo
data CensoredPhoto = CensoredPhoto {bytes :: Int}
data CensoredVideo = CensoredVideo {bytes :: Int, durationSec :: Int}
data CensoredFormat = CensoredPhotoF CensoredPhoto | CensoredVideoF CensoredVideo
data AnyPhoto = UncensoredPhotoA UncensoredPhoto | CensoredPhotoA CensoredPhoto
data AnyVideo = UncensoredVideoA UncensoredVideo | CensoredVideoA CensoredVideo
data AnyFormat = AnyPhotoF AnyPhoto | AnyVideoF AnyVideo
所以你可以这样写:
censorFormat :: UncensoredFormat -> CensoredFormat
censoredScreenshot :: CensoredVideo -> CensoredPhoto
uncensoredScreenshot :: UncensoredVideo -> UncensoredPhoto
showAdult :: AnyFormat -> IO ()
showChild :: CensoredFormat -> IO ()
虽然这很乱。假设您想对视频长度添加一些限制,以防止垃圾邮件发送者提交大量短视频,或者避免占用您的服务器审查非常长的视频。 ShortUncensoredVideo
您要定义多少种类型?
在这种情况下,您可以使用数据种类来开发一种类型级别的“语言”来描述您的数据结构的属性:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
-- these types/constructors will be promoted...
data Censoring = Censored | Uncensored
data Length = Short | Medium | Long
-- ...and used to tag our actual data structures
data Photo (c :: Censoring) = Photo { bytes :: Int }
data Video (c :: Censoring) (l :: Length) = Video {bytes :: Int, durationSec :: Int}
data Format (c :: Censoring) where
PhotoF :: Photo c -> Format c
VideoF :: Video c l -> Format c
现在我们可以这样写:
-- preserve censoring at compile time
screenShot :: Video c l -> Photo c
screenShot (Video b _) = Photo (b `div` 10)
-- only censor uncensored videos that aren't long
type family NotLong l where
NotLong Long = False
NotLong l = True
censorVideo :: (NotLong l ~ True) => Video Uncensored l -> Video Censored l
censorVideo (Video b l) = Video (b `div` 2) (l `div` 2)
-- show any format to adults
showAdult :: Format c -> IO ()
showAdult fmt = print fmt
-- only censored content for kids
showChild :: Format Censored -> IO ()
showChild fmt = print fmt
并在编译时捕获问题:
main = do
-- we can show a screenshot from a censored version of an uncensored short video to a child
showChild $ PhotoF . screenShot . censorVideo $ (Video 128000 1 :: Video 'Uncensored 'Short)
-- but the following are compilation errors
-- can't censor an already censored video
showAdult $ VideoF . censorVideo $ (Video 128000 1 :: Video 'Censored 'Short)
-- can't censor a long video
showAdult $ VideoF . censorVideo $ (Video 12800000 100 :: Video 'Uncensored 'Long)
-- can't show a child an uncensored screenshot
showChild $ PhotoF . screenShot $ (Video 128000 1 :: Video 'Uncensored 'Short)
请注意,提升的类型(Censored
、Uncensored
、Short
、Medium
、Long
) 和类型(Censoring
和 Length
)与未升级的类型Photo
、Video
没有直接关系, 和他们描述的 Format
。正如我所说,这就是通常使用数据类型的方式。
完整代码示例:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
-- these types/constructors will be promoted...
data Censoring = Censored | Uncensored
data Length = Short | Medium | Long
-- ...and used to tag our actual data structures
data Photo (c :: Censoring) = Photo { bytes :: Int }
data Video (c :: Censoring) (l :: Length) = Video {bytes :: Int, durationSec :: Int}
data Format (c :: Censoring) where
PhotoF :: Photo c -> Format c
VideoF :: Video c l -> Format c
instance Show (Format c) where show _ = "<Format>"
-- preserve censoring at compile time
screenShot :: Video c l -> Photo c
screenShot (Video b _) = Photo (b `div` 10)
-- only censor uncensored videos that aren't long
type family NotLong l where
NotLong Long = False
NotLong l = True
censorVideo :: (NotLong l ~ True) => Video Uncensored l -> Video Censored l
censorVideo (Video b l) = Video (b `div` 2) (l `div` 2)
-- show any format to adults
showAdult :: Format c -> IO ()
showAdult fmt = print fmt
-- only censored content for kids
showChild :: Format Censored -> IO ()
showChild fmt = print fmt
main = do
-- we can show a screenshot from a censored version of an uncensored short video to a child
showChild $ PhotoF . screenShot . censorVideo $ (Video 128000 1 :: Video 'Uncensored 'Short)
-- but the following are compilation errors
-- can't censor an already censored video
showAdult $ VideoF . censorVideo $ (Video 128000 1 :: Video 'Censored 'Short)
-- can't censor a long video
showAdult $ VideoF . censorVideo $ (Video 12800000 100 :: Video 'Uncensored 'Long)
-- can't show a child an uncensored screenshot
showChild $ PhotoF . screenShot $ (Video 128000 1 :: Video 'Uncensored 'Short)
关于haskell - 使用数据种类对函数类型的约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73763911/
C语言sscanf()函数:从字符串中读取指定格式的数据 头文件: ?
最近,我有一个关于工作预评估的问题,即使查询了每个功能的工作原理,我也不知道如何解决。这是一个伪代码。 下面是一个名为foo()的函数,该函数将被传递一个值并返回一个值。如果将以下值传递给foo函数,
CStr 函数 返回表达式,该表达式已被转换为 String 子类型的 Variant。 CStr(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CSng 函数 返回表达式,该表达式已被转换为 Single 子类型的 Variant。 CSng(expression) expression 参数是任意有效的表达式。 说明 通常,可
CreateObject 函数 创建并返回对 Automation 对象的引用。 CreateObject(servername.typename [, location]) 参数 serv
Cos 函数 返回某个角的余弦值。 Cos(number) number 参数可以是任何将某个角表示为弧度的有效数值表达式。 说明 Cos 函数取某个角并返回直角三角形两边的比值。此比值是
CLng 函数 返回表达式,此表达式已被转换为 Long 子类型的 Variant。 CLng(expression) expression 参数是任意有效的表达式。 说明 通常,您可以使
CInt 函数 返回表达式,此表达式已被转换为 Integer 子类型的 Variant。 CInt(expression) expression 参数是任意有效的表达式。 说明 通常,可
Chr 函数 返回与指定的 ANSI 字符代码相对应的字符。 Chr(charcode) charcode 参数是可以标识字符的数字。 说明 从 0 到 31 的数字表示标准的不可打印的
CDbl 函数 返回表达式,此表达式已被转换为 Double 子类型的 Variant。 CDbl(expression) expression 参数是任意有效的表达式。 说明 通常,您可
CDate 函数 返回表达式,此表达式已被转换为 Date 子类型的 Variant。 CDate(date) date 参数是任意有效的日期表达式。 说明 IsDate 函数用于判断 d
CCur 函数 返回表达式,此表达式已被转换为 Currency 子类型的 Variant。 CCur(expression) expression 参数是任意有效的表达式。 说明 通常,
CByte 函数 返回表达式,此表达式已被转换为 Byte 子类型的 Variant。 CByte(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CBool 函数 返回表达式,此表达式已转换为 Boolean 子类型的 Variant。 CBool(expression) expression 是任意有效的表达式。 说明 如果 ex
Atn 函数 返回数值的反正切值。 Atn(number) number 参数可以是任意有效的数值表达式。 说明 Atn 函数计算直角三角形两个边的比值 (number) 并返回对应角的弧
Asc 函数 返回与字符串的第一个字母对应的 ANSI 字符代码。 Asc(string) string 参数是任意有效的字符串表达式。如果 string 参数未包含字符,则将发生运行时错误。
Array 函数 返回包含数组的 Variant。 Array(arglist) arglist 参数是赋给包含在 Variant 中的数组元素的值的列表(用逗号分隔)。如果没有指定此参数,则
Abs 函数 返回数字的绝对值。 Abs(number) number 参数可以是任意有效的数值表达式。如果 number 包含 Null,则返回 Null;如果是未初始化变量,则返回 0。
FormatPercent 函数 返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 FormatPercent(expression[,NumDigitsAfterD
FormatNumber 函数 返回表达式,此表达式已被格式化为数值。 FormatNumber( expression [,NumDigitsAfterDecimal [,Inc
我是一名优秀的程序员,十分优秀!