Decoding Factorio blueprints in Haskell

Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
22
down vote
favorite
In the game Factorio there are string-encoded representations of blueprints that I wish to decode.
The implementation according to their wiki is the following:
This is a technical description of the blueprint string format, used to share blueprints with other users.
A blueprint string is a json representation of the blueprint, encoded using base 64 with the version byte in front (0 in vanilla 0.15) and then compressed with zlib deflate. So to get the json representation of a blueprint from a blueprint string, skip the first byte, base64 decode the string, and finally decompress using zlib deflate.
Please keep in mind that I am very new to Haskell, having only played around with it maybe once earlier in my life, though I do have some functional programming experience.
The list of dependencies is the following:
- base >= 4.7 && < 5
- bytestring
- base64-bytestring
- zlib
The Main.hs file:
module Main where
import Lib
import System.Environment
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn "Usage: factorio-exe <blueprint-string>"
else putStrLn (blueprintJson (head args))
The Lib.hs file:
module Lib
( blueprintJson
) where
import Codec.Compression.Zlib
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64
blueprintJson :: [Char] -> [Char]
blueprintJson str = bpDeflate (bpDecode str)
bpDecode :: [Char] -> C.ByteString
bpDecode str = let (Right decoded) = decode (C.pack (tail str)) in decoded
bpDeflate :: C.ByteString -> [Char]
bpDeflate bstr = L.unpack (decompress (L.fromStrict bstr))
An example blueprint string to test with is the following:
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
Do note that when decoding that there may appear characters with which the Windows Command Prompt may have issues displaying.
beginner haskell
add a comment |Â
up vote
22
down vote
favorite
In the game Factorio there are string-encoded representations of blueprints that I wish to decode.
The implementation according to their wiki is the following:
This is a technical description of the blueprint string format, used to share blueprints with other users.
A blueprint string is a json representation of the blueprint, encoded using base 64 with the version byte in front (0 in vanilla 0.15) and then compressed with zlib deflate. So to get the json representation of a blueprint from a blueprint string, skip the first byte, base64 decode the string, and finally decompress using zlib deflate.
Please keep in mind that I am very new to Haskell, having only played around with it maybe once earlier in my life, though I do have some functional programming experience.
The list of dependencies is the following:
- base >= 4.7 && < 5
- bytestring
- base64-bytestring
- zlib
The Main.hs file:
module Main where
import Lib
import System.Environment
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn "Usage: factorio-exe <blueprint-string>"
else putStrLn (blueprintJson (head args))
The Lib.hs file:
module Lib
( blueprintJson
) where
import Codec.Compression.Zlib
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64
blueprintJson :: [Char] -> [Char]
blueprintJson str = bpDeflate (bpDecode str)
bpDecode :: [Char] -> C.ByteString
bpDecode str = let (Right decoded) = decode (C.pack (tail str)) in decoded
bpDeflate :: C.ByteString -> [Char]
bpDeflate bstr = L.unpack (decompress (L.fromStrict bstr))
An example blueprint string to test with is the following:
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
Do note that when decoding that there may appear characters with which the Windows Command Prompt may have issues displaying.
beginner haskell
add a comment |Â
up vote
22
down vote
favorite
up vote
22
down vote
favorite
In the game Factorio there are string-encoded representations of blueprints that I wish to decode.
The implementation according to their wiki is the following:
This is a technical description of the blueprint string format, used to share blueprints with other users.
A blueprint string is a json representation of the blueprint, encoded using base 64 with the version byte in front (0 in vanilla 0.15) and then compressed with zlib deflate. So to get the json representation of a blueprint from a blueprint string, skip the first byte, base64 decode the string, and finally decompress using zlib deflate.
Please keep in mind that I am very new to Haskell, having only played around with it maybe once earlier in my life, though I do have some functional programming experience.
The list of dependencies is the following:
- base >= 4.7 && < 5
- bytestring
- base64-bytestring
- zlib
The Main.hs file:
module Main where
import Lib
import System.Environment
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn "Usage: factorio-exe <blueprint-string>"
else putStrLn (blueprintJson (head args))
The Lib.hs file:
module Lib
( blueprintJson
) where
import Codec.Compression.Zlib
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64
blueprintJson :: [Char] -> [Char]
blueprintJson str = bpDeflate (bpDecode str)
bpDecode :: [Char] -> C.ByteString
bpDecode str = let (Right decoded) = decode (C.pack (tail str)) in decoded
bpDeflate :: C.ByteString -> [Char]
bpDeflate bstr = L.unpack (decompress (L.fromStrict bstr))
An example blueprint string to test with is the following:
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
Do note that when decoding that there may appear characters with which the Windows Command Prompt may have issues displaying.
beginner haskell
In the game Factorio there are string-encoded representations of blueprints that I wish to decode.
The implementation according to their wiki is the following:
This is a technical description of the blueprint string format, used to share blueprints with other users.
A blueprint string is a json representation of the blueprint, encoded using base 64 with the version byte in front (0 in vanilla 0.15) and then compressed with zlib deflate. So to get the json representation of a blueprint from a blueprint string, skip the first byte, base64 decode the string, and finally decompress using zlib deflate.
Please keep in mind that I am very new to Haskell, having only played around with it maybe once earlier in my life, though I do have some functional programming experience.
The list of dependencies is the following:
- base >= 4.7 && < 5
- bytestring
- base64-bytestring
- zlib
The Main.hs file:
module Main where
import Lib
import System.Environment
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn "Usage: factorio-exe <blueprint-string>"
else putStrLn (blueprintJson (head args))
The Lib.hs file:
module Lib
( blueprintJson
) where
import Codec.Compression.Zlib
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64
blueprintJson :: [Char] -> [Char]
blueprintJson str = bpDeflate (bpDecode str)
bpDecode :: [Char] -> C.ByteString
bpDecode str = let (Right decoded) = decode (C.pack (tail str)) in decoded
bpDeflate :: C.ByteString -> [Char]
bpDeflate bstr = L.unpack (decompress (L.fromStrict bstr))
An example blueprint string to test with is the following:
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
Do note that when decoding that there may appear characters with which the Windows Command Prompt may have issues displaying.
beginner haskell
asked Mar 24 at 21:26
skiwi
6,60852997
6,60852997
add a comment |Â
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
17
down vote
accepted
There's not much to say, to be honest. The type signatures are all there, which is a plus.
Communicate possible errors and make functions total
However, bpDecode has two possible runtime error sources: tail (which is partial) and Right _ = decode, since the latter can return a Left. Therefore bpDecode is partial too. In Haskell, we try to keep the partial functions to a minimum. bpDecode can fail, but it's type does not communicate that to us.
Instead, bpDecode should return Either String C.ByteString as decode. We can use String to communicate another possible error, namely an empty String:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode "" = Left "bpDecode: Empty String"
bpDecode s = decode . C.pack . tail $ s
Alternatively, if you don't care about empty string case, we can use drop 1 instead of tail. The former always returns a list, even if our original list was empty:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode = decode . C.pack . drop 1
Some libraries provide a "I know it's correct" function and you can too:
bpDecode' :: [Char] -> C.ByteString
bpDecode' s = let (Right d) = bpDecode s in d
but that's an aside. However, now bpDecode is total: for any input it will produce an output.
Use hlint to find possible eta-reductions
While not necessary, this could be a good time to learn about hlint. It will report common improvements on your code. In this case, you can write bpDeflate as
bpDeflate :: C.ByteString -> [Char]
bpDeflate = L.unpack . decompress . L.fromStrict
and blueprintJson as
blueprintJson :: [Char] -> [Char]
blueprintJson = bpDeflate . bpDecode
Try to stay in a single character stream type
Haskell has 5 types that work with strings. ByteString (lazy and strict), Text (lazy and strict) and String (aka [Char]). If possible try to stick to one of them and don't switch between them all the time. For example, all your functions can be written with lazy ByteStrings:
import Codec.Compression.Zlib
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64.Lazy
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap bpDeflate . bpDecode
bpDecode :: L.ByteString -> Either String L.ByteString
bpDecode = decode . L.drop 1
bpDeflate :: L.ByteString -> L.ByteString
bpDeflate = decompress
Note that I changed blueprintJson's type, since we also changed bpDecode's type. In case you're not familiar with fmap: in this context you can think of fmap as
fmap f (Right r) = Right (f r)
fmap _ (Left l) = Left l
Regardless, the string type depends of course on your later use case. Since blueprintJson hints that you will decode JSON, you will likely use aeson later, which uses lazy ByteStrings as well.
Given those functions above, we could now write blueprintJson as
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap decompress . decode . L.drop 1
blueprintJson' :: L.ByteString -> L.ByteString
blueprintJson' = fromRight . blueprintJson
fromRight :: Either a b -> b
fromRight e = -- left as an exercise
Prefer pattern matches instead of boolean guards
In main, you check whether the list is null. That still enables us to accide
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn (blueprintJson (head args)) -- woops!
else putStrLn "Usage: factorio-exe <blueprint-string>"
If you pattern match you cannot accidentally use head on an empty list:
main :: IO ()
main = do
args <- getArgs
case args of
[b] -> putStrLn (blueprintJson b)
_ -> putStrLn "Usage: factorio-exe <blueprint-string>"
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
17
down vote
accepted
There's not much to say, to be honest. The type signatures are all there, which is a plus.
Communicate possible errors and make functions total
However, bpDecode has two possible runtime error sources: tail (which is partial) and Right _ = decode, since the latter can return a Left. Therefore bpDecode is partial too. In Haskell, we try to keep the partial functions to a minimum. bpDecode can fail, but it's type does not communicate that to us.
Instead, bpDecode should return Either String C.ByteString as decode. We can use String to communicate another possible error, namely an empty String:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode "" = Left "bpDecode: Empty String"
bpDecode s = decode . C.pack . tail $ s
Alternatively, if you don't care about empty string case, we can use drop 1 instead of tail. The former always returns a list, even if our original list was empty:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode = decode . C.pack . drop 1
Some libraries provide a "I know it's correct" function and you can too:
bpDecode' :: [Char] -> C.ByteString
bpDecode' s = let (Right d) = bpDecode s in d
but that's an aside. However, now bpDecode is total: for any input it will produce an output.
Use hlint to find possible eta-reductions
While not necessary, this could be a good time to learn about hlint. It will report common improvements on your code. In this case, you can write bpDeflate as
bpDeflate :: C.ByteString -> [Char]
bpDeflate = L.unpack . decompress . L.fromStrict
and blueprintJson as
blueprintJson :: [Char] -> [Char]
blueprintJson = bpDeflate . bpDecode
Try to stay in a single character stream type
Haskell has 5 types that work with strings. ByteString (lazy and strict), Text (lazy and strict) and String (aka [Char]). If possible try to stick to one of them and don't switch between them all the time. For example, all your functions can be written with lazy ByteStrings:
import Codec.Compression.Zlib
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64.Lazy
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap bpDeflate . bpDecode
bpDecode :: L.ByteString -> Either String L.ByteString
bpDecode = decode . L.drop 1
bpDeflate :: L.ByteString -> L.ByteString
bpDeflate = decompress
Note that I changed blueprintJson's type, since we also changed bpDecode's type. In case you're not familiar with fmap: in this context you can think of fmap as
fmap f (Right r) = Right (f r)
fmap _ (Left l) = Left l
Regardless, the string type depends of course on your later use case. Since blueprintJson hints that you will decode JSON, you will likely use aeson later, which uses lazy ByteStrings as well.
Given those functions above, we could now write blueprintJson as
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap decompress . decode . L.drop 1
blueprintJson' :: L.ByteString -> L.ByteString
blueprintJson' = fromRight . blueprintJson
fromRight :: Either a b -> b
fromRight e = -- left as an exercise
Prefer pattern matches instead of boolean guards
In main, you check whether the list is null. That still enables us to accide
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn (blueprintJson (head args)) -- woops!
else putStrLn "Usage: factorio-exe <blueprint-string>"
If you pattern match you cannot accidentally use head on an empty list:
main :: IO ()
main = do
args <- getArgs
case args of
[b] -> putStrLn (blueprintJson b)
_ -> putStrLn "Usage: factorio-exe <blueprint-string>"
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
add a comment |Â
up vote
17
down vote
accepted
There's not much to say, to be honest. The type signatures are all there, which is a plus.
Communicate possible errors and make functions total
However, bpDecode has two possible runtime error sources: tail (which is partial) and Right _ = decode, since the latter can return a Left. Therefore bpDecode is partial too. In Haskell, we try to keep the partial functions to a minimum. bpDecode can fail, but it's type does not communicate that to us.
Instead, bpDecode should return Either String C.ByteString as decode. We can use String to communicate another possible error, namely an empty String:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode "" = Left "bpDecode: Empty String"
bpDecode s = decode . C.pack . tail $ s
Alternatively, if you don't care about empty string case, we can use drop 1 instead of tail. The former always returns a list, even if our original list was empty:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode = decode . C.pack . drop 1
Some libraries provide a "I know it's correct" function and you can too:
bpDecode' :: [Char] -> C.ByteString
bpDecode' s = let (Right d) = bpDecode s in d
but that's an aside. However, now bpDecode is total: for any input it will produce an output.
Use hlint to find possible eta-reductions
While not necessary, this could be a good time to learn about hlint. It will report common improvements on your code. In this case, you can write bpDeflate as
bpDeflate :: C.ByteString -> [Char]
bpDeflate = L.unpack . decompress . L.fromStrict
and blueprintJson as
blueprintJson :: [Char] -> [Char]
blueprintJson = bpDeflate . bpDecode
Try to stay in a single character stream type
Haskell has 5 types that work with strings. ByteString (lazy and strict), Text (lazy and strict) and String (aka [Char]). If possible try to stick to one of them and don't switch between them all the time. For example, all your functions can be written with lazy ByteStrings:
import Codec.Compression.Zlib
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64.Lazy
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap bpDeflate . bpDecode
bpDecode :: L.ByteString -> Either String L.ByteString
bpDecode = decode . L.drop 1
bpDeflate :: L.ByteString -> L.ByteString
bpDeflate = decompress
Note that I changed blueprintJson's type, since we also changed bpDecode's type. In case you're not familiar with fmap: in this context you can think of fmap as
fmap f (Right r) = Right (f r)
fmap _ (Left l) = Left l
Regardless, the string type depends of course on your later use case. Since blueprintJson hints that you will decode JSON, you will likely use aeson later, which uses lazy ByteStrings as well.
Given those functions above, we could now write blueprintJson as
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap decompress . decode . L.drop 1
blueprintJson' :: L.ByteString -> L.ByteString
blueprintJson' = fromRight . blueprintJson
fromRight :: Either a b -> b
fromRight e = -- left as an exercise
Prefer pattern matches instead of boolean guards
In main, you check whether the list is null. That still enables us to accide
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn (blueprintJson (head args)) -- woops!
else putStrLn "Usage: factorio-exe <blueprint-string>"
If you pattern match you cannot accidentally use head on an empty list:
main :: IO ()
main = do
args <- getArgs
case args of
[b] -> putStrLn (blueprintJson b)
_ -> putStrLn "Usage: factorio-exe <blueprint-string>"
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
add a comment |Â
up vote
17
down vote
accepted
up vote
17
down vote
accepted
There's not much to say, to be honest. The type signatures are all there, which is a plus.
Communicate possible errors and make functions total
However, bpDecode has two possible runtime error sources: tail (which is partial) and Right _ = decode, since the latter can return a Left. Therefore bpDecode is partial too. In Haskell, we try to keep the partial functions to a minimum. bpDecode can fail, but it's type does not communicate that to us.
Instead, bpDecode should return Either String C.ByteString as decode. We can use String to communicate another possible error, namely an empty String:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode "" = Left "bpDecode: Empty String"
bpDecode s = decode . C.pack . tail $ s
Alternatively, if you don't care about empty string case, we can use drop 1 instead of tail. The former always returns a list, even if our original list was empty:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode = decode . C.pack . drop 1
Some libraries provide a "I know it's correct" function and you can too:
bpDecode' :: [Char] -> C.ByteString
bpDecode' s = let (Right d) = bpDecode s in d
but that's an aside. However, now bpDecode is total: for any input it will produce an output.
Use hlint to find possible eta-reductions
While not necessary, this could be a good time to learn about hlint. It will report common improvements on your code. In this case, you can write bpDeflate as
bpDeflate :: C.ByteString -> [Char]
bpDeflate = L.unpack . decompress . L.fromStrict
and blueprintJson as
blueprintJson :: [Char] -> [Char]
blueprintJson = bpDeflate . bpDecode
Try to stay in a single character stream type
Haskell has 5 types that work with strings. ByteString (lazy and strict), Text (lazy and strict) and String (aka [Char]). If possible try to stick to one of them and don't switch between them all the time. For example, all your functions can be written with lazy ByteStrings:
import Codec.Compression.Zlib
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64.Lazy
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap bpDeflate . bpDecode
bpDecode :: L.ByteString -> Either String L.ByteString
bpDecode = decode . L.drop 1
bpDeflate :: L.ByteString -> L.ByteString
bpDeflate = decompress
Note that I changed blueprintJson's type, since we also changed bpDecode's type. In case you're not familiar with fmap: in this context you can think of fmap as
fmap f (Right r) = Right (f r)
fmap _ (Left l) = Left l
Regardless, the string type depends of course on your later use case. Since blueprintJson hints that you will decode JSON, you will likely use aeson later, which uses lazy ByteStrings as well.
Given those functions above, we could now write blueprintJson as
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap decompress . decode . L.drop 1
blueprintJson' :: L.ByteString -> L.ByteString
blueprintJson' = fromRight . blueprintJson
fromRight :: Either a b -> b
fromRight e = -- left as an exercise
Prefer pattern matches instead of boolean guards
In main, you check whether the list is null. That still enables us to accide
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn (blueprintJson (head args)) -- woops!
else putStrLn "Usage: factorio-exe <blueprint-string>"
If you pattern match you cannot accidentally use head on an empty list:
main :: IO ()
main = do
args <- getArgs
case args of
[b] -> putStrLn (blueprintJson b)
_ -> putStrLn "Usage: factorio-exe <blueprint-string>"
There's not much to say, to be honest. The type signatures are all there, which is a plus.
Communicate possible errors and make functions total
However, bpDecode has two possible runtime error sources: tail (which is partial) and Right _ = decode, since the latter can return a Left. Therefore bpDecode is partial too. In Haskell, we try to keep the partial functions to a minimum. bpDecode can fail, but it's type does not communicate that to us.
Instead, bpDecode should return Either String C.ByteString as decode. We can use String to communicate another possible error, namely an empty String:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode "" = Left "bpDecode: Empty String"
bpDecode s = decode . C.pack . tail $ s
Alternatively, if you don't care about empty string case, we can use drop 1 instead of tail. The former always returns a list, even if our original list was empty:
bpDecode :: [Char] -> Either String C.ByteString
bpDecode = decode . C.pack . drop 1
Some libraries provide a "I know it's correct" function and you can too:
bpDecode' :: [Char] -> C.ByteString
bpDecode' s = let (Right d) = bpDecode s in d
but that's an aside. However, now bpDecode is total: for any input it will produce an output.
Use hlint to find possible eta-reductions
While not necessary, this could be a good time to learn about hlint. It will report common improvements on your code. In this case, you can write bpDeflate as
bpDeflate :: C.ByteString -> [Char]
bpDeflate = L.unpack . decompress . L.fromStrict
and blueprintJson as
blueprintJson :: [Char] -> [Char]
blueprintJson = bpDeflate . bpDecode
Try to stay in a single character stream type
Haskell has 5 types that work with strings. ByteString (lazy and strict), Text (lazy and strict) and String (aka [Char]). If possible try to stick to one of them and don't switch between them all the time. For example, all your functions can be written with lazy ByteStrings:
import Codec.Compression.Zlib
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Base64.Lazy
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap bpDeflate . bpDecode
bpDecode :: L.ByteString -> Either String L.ByteString
bpDecode = decode . L.drop 1
bpDeflate :: L.ByteString -> L.ByteString
bpDeflate = decompress
Note that I changed blueprintJson's type, since we also changed bpDecode's type. In case you're not familiar with fmap: in this context you can think of fmap as
fmap f (Right r) = Right (f r)
fmap _ (Left l) = Left l
Regardless, the string type depends of course on your later use case. Since blueprintJson hints that you will decode JSON, you will likely use aeson later, which uses lazy ByteStrings as well.
Given those functions above, we could now write blueprintJson as
blueprintJson :: L.ByteString -> Either String L.ByteString
blueprintJson = fmap decompress . decode . L.drop 1
blueprintJson' :: L.ByteString -> L.ByteString
blueprintJson' = fromRight . blueprintJson
fromRight :: Either a b -> b
fromRight e = -- left as an exercise
Prefer pattern matches instead of boolean guards
In main, you check whether the list is null. That still enables us to accide
main :: IO ()
main = do
args <- getArgs
if null args
then putStrLn (blueprintJson (head args)) -- woops!
else putStrLn "Usage: factorio-exe <blueprint-string>"
If you pattern match you cannot accidentally use head on an empty list:
main :: IO ()
main = do
args <- getArgs
case args of
[b] -> putStrLn (blueprintJson b)
_ -> putStrLn "Usage: factorio-exe <blueprint-string>"
edited Mar 25 at 16:58
answered Mar 24 at 21:59
Zeta
14.3k23267
14.3k23267
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
add a comment |Â
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
How would using a lazy ByteString work for the decode function? On hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/⦠it states that it uses the strict variant, so I don't see how it could work.
â skiwi
Mar 25 at 15:48
1
1
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
@skiwi there's a lazy variant, too: hackage.haskell.org/package/base64-bytestring-1.0.0.1/docs/â¦. I accidentally forgot to change the import.
â Zeta
Mar 25 at 16:58
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190406%2fdecoding-factorio-blueprints-in-haskell%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password