Decoding Factorio blueprints in Haskell

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
22
down vote

favorite
3












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.







share|improve this question

























    up vote
    22
    down vote

    favorite
    3












    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.







    share|improve this question





















      up vote
      22
      down vote

      favorite
      3









      up vote
      22
      down vote

      favorite
      3






      3





      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.







      share|improve this question











      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.









      share|improve this question










      share|improve this question




      share|improve this question









      asked Mar 24 at 21:26









      skiwi

      6,60852997




      6,60852997




















          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>"





          share|improve this answer























          • 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











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          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






























          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>"





          share|improve this answer























          • 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















          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>"





          share|improve this answer























          • 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













          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>"





          share|improve this answer















          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>"






          share|improve this answer















          share|improve this answer



          share|improve this answer








          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

















          • 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













           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          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













































































          Popular posts from this blog

          Python Lists

          Aion

          JavaScript Array Iteration Methods