Skip to content

Instantly share code, notes, and snippets.

@mklbtz
Created December 9, 2019 15:22
Show Gist options
  • Save mklbtz/2b282476f6b3e2fec83556f00c677baf to your computer and use it in GitHub Desktop.
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/
// 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