是否可以使用幻像类型编写类似身份函数的内容,以便转换类型?
例如,给定以下类型定义
data Nucleotide a = A | C | G | T | U
data RNA = RNA
data DNA = DNA
我想写一个像
这样的转换函数r2d :: Nucleotide RNA -> Nucleotide DNA
r2d U = T
r2d x = x
但是,这并不是类型检查,因为单个变量x
在相反的一侧不能有不同的类型。
是否可以在不必枚举
的情况下编写此内容r2d :: Nucleotide RNA -> Nucleotide DNA
r2d U = T
r2d A = A
r2d C = C
r2d G = G
答案 0 :(得分:7)
TL; DR:
不要使数据类型可能存在无效数据:
T :: Nucleotide RNA
是可能的,这在生物学上是无意义的,因此您可能会得到r2d T
(在编译时可能会阻止运行时崩溃)。
请注意,Chris Drost's answer值得称赞是对所提出的技术问题的良好答案。
我注意到潜在的崩溃来源,因为您的函数r2d
不是全部 - r2d T
未定义,并且意识到这是因为您无意拥有T :: Nucleotide RNA
(也不是U :: Nucleotide DNA
)。这是一个问题,因为任何时候你不小心(产生用户错误)r2d T
你的整个程序都会崩溃。
这是您的类型中的设计缺陷。类型系统的一个主要观点是无法生成无效数据,但您的代码却允许T :: Nucleotide RNA
甚至T :: Nucleotide [Bool]
。
可悲的是,解决方案是制作更无聊/更少光滑的类型,其中DNA的C和RNA的C之间存在区别,但是您可以使用派生的Enum
实例来转换它们,而无需进行所有输入。
data DNA = A | C | G | T deriving (Eq, Show, Read, Enum)
data RNA = A' | C' | G' | U' deriving (Eq, Show, Read, Enum)
r2d :: RNA -> DNA
r2d = toEnum.fromEnum
d2r :: DNA -> RNA
d2r = toEnum.fromEnum
toEnum.fromEnum :: (Enum a, Enum b) => a -> b
的工作原理是将枚举类型转换为Int
,然后从Int
转换为其他枚举类型。
现在r2d T
只是一个类型错误,因此如果允许,程序将无法编译,而使用幻像类型,如果用户设法输入无效数据,它将在运行时编译并崩溃。
(没有....)
您可能会觉得区分C
和C'
是错误的,因为它们从生物/化学的角度来看是相同的,并且可能存在一些妥协的位置具有构造函数A | C | G | TU
的幻像类型,并根据上下文不同地读取用户数据:
{-# LANGUAGE FlexibleInstances #-}
data Nucleotide a = A | C | G | TU deriving (Eq,Enum)
data RNA = RNA
data DNA = DNA
instance Show (Nucleotide DNA) where
show A = "A"
show C = "C"
show G = "G"
show TU = "T"
instance Show (Nucleotide RNA) where
show A = "A"
show C = "C"
show G = "G"
show TU = "U"
r2d :: (Nucleotide RNA) -> (Nucleotide DNA)
r2d = toEnum.fromEnum
d2r :: (Nucleotide DNA) -> (Nucleotide RNA)
d2r = toEnum.fromEnum
有时候制作一个复杂的类型会增加你需要使用的扩展数量,如果你能够容忍一些'
,那么你就会遇到一些潜在问题更少的问题。
在我看来,您最好使用我的第一个解决方案并为Show RNA
和Read RNA
编写自定义实例,用户无需将'
置于其中信的结尾。
但请注意,read
永远不是一个完整的功能(即程序崩溃的原因),最好使用safe
package中的readMay
以便您可以恢复优雅地给你的用户一个礼貌的错误信息和修复它的机会,而不是崩溃,或者通过使用Parsec或类似的方式编写解析器来读取大量复杂的结构化数据,其中read或readMay不必要地慢。
答案 1 :(得分:3)
使用case语句可以稍快一点:
r2d :: Nucleotide RNA -> Nucleotide DNA
r2d x = case x of U -> T; A -> A; C -> C; G -> G
我们也知道它们具有相同的表示形式,因此您可以使用Unsafe.Coerce
中的unsafeCoerce。他们将此类事情的编译器推理带给了GHC; read more about coerce here