为什么我要用mapM编写id

时间:2015-01-07 15:17:09

标签: haskell

我有以下方法:

nucleotideComplement :: Char -> Either String Char
nucleotideComplement 'G' = Right 'C'
nucleotideComplement 'C' = Right 'G'
nucleotideComplement 'T' = Right 'A'
nucleotideComplement 'A' = Right 'U'
nucleotideComplement x = Left "Not a valid RNA nucleotide."

想要定义另一个:

toRNA :: String -> String
toRNA = either error mapM nucleotideComplement

但是我在这里遇到了类型错误。但是这样做似乎解决了这个问题:

toRNA :: String -> String
toRNA = either error id . mapM nucleotideComplement

我不明白为什么会发生这种情况

首先,id的类型为a -> a。接下来,在获取:tmapM nucleotideComplement的类型(id . mapM nucleotideComplement)时,它们似乎是相同的。为什么我会有这样不同的效果?

希望有人能够进一步澄清这一点。

3 个答案:

答案 0 :(得分:12)

我认为你正在读这个错误......

either error id . mapM nucleotideComplement

你似乎认为这意味着

either error (id . mapM nucleotideComplement)

实际上它意味着

(either error id) . (mapM nucleotideComplement)

你在任何地方都没有id . mapM nucleotideComplement。您正在应用mapM,然后将结果传递给either,这将适用errorid,具体取决于它是Left还是{{} 1}}。

答案 1 :(得分:3)

两者的类型是(a -> c) -> (b -> c) -> Either a b -> c。因此,您将其应用于error,然后获得(b -> c) -> Either String b -> c,然后将其应用于mapM,然后获得Monad m => Either String (a -> m b) -> [a] -> m [b]。然后,您将其应用于nucleotideComplement并收到错误,因为nucleotideComplement是一个函数,而不是Either

换句话说,如果您打算使用两个参数调用它,则将either应用于三个参数,其中第二个参数是将mapM应用于nucleotideComplement的结果。要使用您想要的参数调用函数,您可以编写either error (mapM nucleotideComponent),但仍然无法工作,因为either的第二个参数应该是接受Char的函数(因为你有一个Either String Char),而不是一个接受monad的人。要实现您想要的目标,您可以撰写either error nucleotideComponent或使用.,因为您已经找到了。

.的版本有效,因为Haskell的优先规则说either error id . mapM nucleotideComplement等同于(either error id) . (mapM nucleotideComplement),而不是(either error id . mapM) nucleotideComplementeither error (id . mapM nucleotideComplement)either error id是一个将Either String b转换为Either a b的函数,其中左侧案例会导致错误,mapM nucleotideComplement是一个将m Char转换为另一个m Char的函数m,其中char为"翻转"对于任何monad m - 在这种情况下Either StringEither String Char。因此,通过编写这两个函数,您将获得一个函数,将Either a Char转换为either error flipNucleotide,右侧的情况为翻转的字符,左侧的情况会导致错误。

当然{{1}}是更简单的解决方案。

答案 2 :(得分:0)

这并不能完全解决您的类型错误,但我建议您重新考虑您的代表。具体而言,通常最好使用类型来强制执行不变量,避免可能抛出错误或异常的部分函数,​​并避免意外混淆可能属于代码的不同部分的相关事物。有很多方法可以解决这个问题,但这是一个方法。这种方法假装DNA和RNA具有完全不同种类的核苷酸。在化学上,这不是真的,但它可能是你正在做的事情的合理代表。实际上,对化学现实进行编码可能超出了Haskell类型系统的能力,并且在这种情况下捕获错误实际上可能没那么有用。

data DNANucleotide = GD | CD | TD | AD
data RNANucleotide = GR | CR | UR | AR

toStringDNA :: [DNANucleotide] -> String
toStringDNA = map (\nucleotide -> case nucleotide of
  {GD -> 'G'; CD -> 'C'; TD -> 'T'; AD -> 'A'})

toStringRNA = ...

fromCharDNA :: Char -> Maybe DNANucleotide
fromCharDNA 'G' = Just GD
fromCharDNA 'C' = Just CD
...
fromCharDNA _ = Nothing

fromCharRNA = ...

fromStringDNA :: String -> Maybe [DNANucleotide]
fromStringDNA = mapM fromCharDNA

fromStringRNA :: String -> Maybe [RNANucleotide]
fromStringRNA = mapM fromCharRNA

一旦你使用DNA和RNA进入工作的实际机制,而不是从字符串中读取它们,就不会再有错误了:

transcribeN :: DNANucleotide -> RNANucleotide
transcribeN GD = CR
transcribeN CD = GR
transcribeN TD = AR
transcribeN AD = UR

transcribe :: [DNANucleotide] -> [RNANucleotide]
transcribe = map transcribeN