Skip to content

Commit 2ac38bd

Browse files
committed
Add introduction
1 parent 2400b8a commit 2ac38bd

File tree

1 file changed

+204
-0
lines changed

1 file changed

+204
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Introduction
2+
3+
This problem requires both
4+
5+
- validating that all input characters validly denote DNA nucleobases, and
6+
- producing these DNA nucleobases' corresponding RNA nucleobases.
7+
8+
The first below listed approach has these tasks performed separately.
9+
The other ones combine them in a single pass, in progressively more succinct ways.
10+
11+
12+
## Approach: validate first
13+
14+
```haskell
15+
toRNA :: String -> Either Char String
16+
toRNA dna =
17+
case find (`notElem` "GCTA") dna of
18+
Nothing -> Right (map transcribe dna)
19+
Just c -> Left c
20+
where
21+
transcribe = \case
22+
'G' -> 'C'
23+
'C' -> 'G'
24+
'T' -> 'A'
25+
'A' -> 'U'
26+
```
27+
28+
First search for the first invalid nucleobase.
29+
If you find one, return it.
30+
If all are valid, transcribe the entire strand in one go using `map`.
31+
32+
This approach has the input walked twice.
33+
Other approaches solve this problem in one pass.
34+
35+
This solution deals with nucleobases twice: first when validating, and again when transcribing.
36+
Ideally, nucleobases are dealt with in only one place in the code.
37+
38+
[Read more about this approach][validate-first].
39+
40+
41+
## Approach: a single pass using only elementary operations
42+
43+
```haskell
44+
toRNA :: String -> Either Char String
45+
toRNA [] = Right []
46+
toRNA (n : dna) = case transcribe n of
47+
Nothing -> Left n
48+
Just n' -> case toRNA dna of
49+
Left c -> Left c
50+
Right rna -> Right (n' : rna)
51+
52+
transcribe :: Char -> Maybe Char
53+
transcribe = \case
54+
'G' -> Just 'C'
55+
'C' -> Just 'G'
56+
'T' -> Just 'A'
57+
'A' -> Just 'U'
58+
_ -> Nothing
59+
```
60+
61+
This solution combines validation and transcription in a single list traversal.
62+
It is _elementary_ in the sense that it employs no abstractions: it uses only constructors (`[]`, `(:)`, `Nothing`, `Just`, `Left`, `Right`) and pattern matching, and no predefined functions at all.
63+
64+
Some of the code patterns used in this solution are very common, and were therefore abstracted into standard library functions.
65+
The approaches listed below show how much these functions can help to concisely express this approach's logic.
66+
67+
[Read more about this approach][elementary].
68+
69+
70+
## Approach: use `do`-notation
71+
72+
```haskell
73+
toRNA :: String -> Either Char String
74+
toRNA [] = pure []
75+
toRNA (n : dna) = do
76+
n' <- transcribe n
77+
rna <- toRNA dna
78+
pure (n' : rna)
79+
80+
transcribe :: Char -> Either Char Char
81+
transcribe = \case
82+
'G' -> Right 'C'
83+
'C' -> Right 'G'
84+
'T' -> Right 'A'
85+
'A' -> Right 'U'
86+
c -> Left c
87+
```
88+
89+
The [elementary solution][elementary] displays a common pattern that can equivalently be expressed using the common monadic `>>=` combinator and its `do`-notation [syntactic sugar][wikipedia-syntactic-sugar].
90+
91+
[Read more about this approach][do-notation].
92+
93+
94+
## Approach: use `Functor`/`Applicative` combinators
95+
96+
```haskell
97+
toRNA :: String -> Either Char String
98+
toRNA [] = pure []
99+
toRNA (n : dna) = (:) <$> transcribe n <*> toRNA dna
100+
101+
transcribe :: Char -> Either Char Char
102+
transcribe = \case
103+
'G' -> Right 'C'
104+
'C' -> Right 'G'
105+
'T' -> Right 'A'
106+
'A' -> Right 'U'
107+
c -> Left c
108+
```
109+
110+
The [elementary solution][elementary] displays a number of common patterns.
111+
As demonstrated by the [`do` notation solution][do-notation], these can be expressed with the `>>=` operator.
112+
However, the full power of `Monad` is not required.
113+
The same logic can also be expressed using common functorial combinators such as `fmap`/`<$>` and `<*>`.
114+
115+
[Read more about this approach][functorial-combinators].
116+
117+
118+
## Approach: use `traverse`
119+
120+
```haskell
121+
toRNA :: String -> Either Char String
122+
toRNA = traverse $ \case
123+
'G' -> Right 'C'
124+
'C' -> Right 'G'
125+
'T' -> Right 'A'
126+
'A' -> Right 'U'
127+
n -> Left n
128+
```
129+
130+
As it turns out, the [solution that uses functorial combinators][functorial-combinators] closely resembles the definition of `traverse` for lists.
131+
In fact, through a series of rewritings it can be shown to be equivalent.
132+
133+
[Read more about this approach][traverse].
134+
135+
136+
## General guidance
137+
138+
### Language extensions
139+
140+
For various reasons, some of GHC's features are locked behind switches known as _language extensions_.
141+
You can enable these by putting so-called _language pragmas_ at the top of your file:
142+
143+
```haskell
144+
-- This 👇 is a language pragma
145+
{-# LANGUAGE LambdaCase #-}
146+
147+
module DNA (toRNA) where
148+
149+
{-
150+
The rest of your code here
151+
-}
152+
```
153+
154+
155+
#### `LambdaCase`
156+
157+
Consider the following possible definition of `map`.
158+
159+
```haskell
160+
map f xs = case xs of
161+
[] -> []
162+
x : xs' -> f x : map xs'
163+
```
164+
165+
Here, a parameter `xs` is introduced only to be immediately pattern matched against, after which it is never used again.
166+
167+
Coming up with good names for such throwaway variables can be tedious and hard.
168+
The `LambdaCase` extension allows us to avoid having to by providing an extra bit of [syntactic sugar][wikipedia-syntactic-sugar]:
169+
170+
```haskell
171+
f = \case { }
172+
-- is syntactic sugar for / an abbreviation of
173+
f = \x -> case x of { }
174+
```
175+
176+
The above definition of `map` can equivalently be written as
177+
178+
```haskell
179+
map f = \case
180+
[] -> []
181+
x : xs -> f x : map f xs
182+
```
183+
184+
185+
[do-notation]:
186+
https://exercism.org/tracks/haskell/exercises/rna-transcription/approaches/do-notation
187+
"Approach: use do-notation"
188+
[elementary]:
189+
https://exercism.org/tracks/haskell/exercises/rna-transcription/approaches/elementary
190+
"Approach: a single pass using only elementary operations"
191+
[functorial-combinators]:
192+
https://exercism.org/tracks/haskell/exercises/rna-transcription/approaches/functorial-combinators
193+
"Approach: use Functor/Applicative combinators"
194+
[traverse]:
195+
https://exercism.org/tracks/haskell/exercises/rna-transcription/approaches/traverse
196+
"Approach: use traverse"
197+
[validate-first]:
198+
https://exercism.org/tracks/haskell/exercises/rna-transcription/approaches/validate-first
199+
"Approach: validate first"
200+
201+
202+
[wikipedia-syntactic-sugar]:
203+
https://en.wikipedia.org/wiki/Syntactic_sugar
204+
"Wikipedia: Syntactic sugar"

0 commit comments

Comments
 (0)