Created
December 9, 2019 15:22
-
-
Save mklbtz/2b282476f6b3e2fec83556f00c677baf to your computer and use it in GitHub Desktop.
Swift translation of the code samples from https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// From: http://www.figure.ink/blog/2019/11/9/parse-dont-validate | |
// Original: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/#2019-11-05-parse-don-t-validate-footnote-1-return | |
/* Haskell | |
head :: [a] -> a | |
head (x:_) = x | |
*/ | |
func head<A>(_ array: [A]) -> A { | |
return array[0] // Crashes at runtime when array is empty. | |
} | |
/* Haskell | |
head :: [a] -> Maybe a | |
head (x:_) = Just x | |
head [] = Nothing | |
*/ | |
func head<A>(_ array: [A]) -> A? { | |
if array.isEmpty { | |
return .none | |
} else { | |
return array[0] | |
} | |
} | |
/* Haskell | |
getConfigurationDirectories :: IO [FilePath] | |
getConfigurationDirectories = do | |
configDirsString <- getEnv "CONFIG_DIRS" | |
let configDirsList = split ',' configDirsString | |
when (null configDirsList) $ | |
throwIO $ userError "CONFIG_DIRS cannot be empty" | |
pure configDirsList | |
main :: IO () | |
main = do | |
configDirs <- getConfigurationDirectories | |
case head configDirs of | |
Just cacheDir -> initializeCache cacheDir | |
Nothing -> error "should never happen; already checked configDirs is non-empty" | |
*/ | |
// NOTE: The IO type comes from Haskell std lib and you can think of it like a lazy Result or a Future. | |
// In my examples, I'm converting functions that return IO into throwing functions. | |
// This is a close-enough approximation. | |
func getConfigurationDirectories() throws -> [FilePath] { | |
let configDirsString = getEnv("CONFIG_DIRS") | |
let configDirsList = configDirsString.split(separator: ",") | |
guard !configDirsList.isEmpty else { | |
return throw UserError("CONFIG_DIRS cannot be empty")) | |
} | |
return configDirsList | |
} | |
func main() throws { | |
let configDirs = try getConfigurationDirectories() | |
guard let cacheDir = head(configDirs) else { | |
fatalError("should never happen; already checked configDirs is non-empty") | |
} | |
return initializeCache(cacheDir) | |
} | |
/* Haskell | |
data NonEmpty a = a :| [a] | |
*/ | |
typealias NonEmpty<A> = (A, [A]) | |
/* Haskell | |
head :: NonEmpty a -> a | |
head (x:|_) = x | |
*/ | |
func head<A>(_ nea: NonEmpty<A>) -> A { | |
let (x, _) = nea | |
return x | |
} | |
/* Haskell | |
getConfigurationDirectories :: IO (NonEmpty FilePath) | |
getConfigurationDirectories = do | |
configDirsString <- getEnv "CONFIG_DIRS" | |
let configDirsList = split ',' configDirsString | |
case nonEmpty configDirsList of | |
Just nonEmptyConfigDirsList -> pure nonEmptyConfigDirsList | |
Nothing -> throwIO $ userError "CONFIG_DIRS cannot be empty" | |
main :: IO () | |
main = do | |
configDirs <- getConfigurationDirectories | |
initializeCache (head configDirs) | |
*/ | |
func getConfigurationDirectories() throws -> NonEmpty<FilePath> { | |
let configDirsString = getEnv("CONFIG_DIRS") | |
let configDirsList = configDirsString.split(separator: ",") | |
guard let nonEmptyConfigDirsList = nonEmpty(configDirsList) else { | |
throw UserError("CONFIG_DIRS cannot be empty")) | |
} | |
return nonEmptyConfigDirsList | |
} | |
func main() throws { | |
let configDirs = try getConfigurationDirectories() | |
initializeCache(head(configDirs)) | |
} | |
/* Haskell | |
nonEmpty :: [a] -> Maybe (NonEmpty a) | |
*/ | |
func nonEmpty<A>(_ array: [A]) -> Optional<NonEmpty<A>> | |
/* Haskell | |
head' :: [a] -> Maybe a | |
head' = fmap head . nonEmpty | |
*/ | |
func head2<A>(_ array: [A]) -> A? { | |
// `map` here is Optional.map | |
return nonEmpty(array).map { head($0) } | |
} | |
/* Haskell | |
validateNonEmpty :: [a] -> IO () | |
validateNonEmpty (_:_) = pure () | |
validateNonEmpty [] = throwIO $ userError "list cannot be empty" | |
parseNonEmpty :: [a] -> IO (NonEmpty a) | |
parseNonEmpty (x:xs) = pure (x:|xs) | |
parseNonEmpty [] = throwIO $ userError "list cannot be empty" | |
*/ | |
func validateNonEmpty<A>(_ array: [A]) throws -> Void { | |
if array.isEmpty { | |
throw UserError("list cannot be empty") | |
} | |
return () | |
} | |
func parseNonEmpty<A>(_ array: [A]) throws -> NonEmpty<A> { | |
if array.isEmpty { | |
throw UserError("list cannot be empty") | |
} | |
var tail = array | |
let first = tail.removeFirst() | |
return (first, tail) | |
} | |
/* Haskell | |
checkNoDuplicateKeys :: (MonadError AppError m, Eq k) => [(k, v)] -> m () | |
*/ | |
// NOTE: A direct translation would look something like this... | |
func checkNoDuplicateKeys<K: Hashable, V>(_ pairs: [(K, V)]) -> Result<Void, Error> | |
// ...but a more standard Swift implementation might just return a Bool. The effect is the same. | |
func checkNoDuplicateKeys<K: Hashable, V>(_ pairs: [(K, V)]) -> Bool | |
/* Haskell | |
checkNoDuplicateKeys :: (MonadError AppError m, Eq k) => [(k, v)] -> m (Map k v) | |
*/ | |
func checkNoDuplicateKeys<K: Hashable, V>(_ pairs: [(K, V)]) -> Result<Dictionary<K, V>, Error> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment