polyglot: Haskell to Purescript & Scala 3 transpiler

[ bsd3, compiler, library, program ] [ Propose Tags ]

Please see the README on GitHub at https://github.com/albertprz/polyglot#readme

[Skip to Readme]


Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


  • No Candidates
Versions [RSS]
Dependencies base (>=4.7 && <5), bookhound (>=0.1.24 && <0.2), bytestring, casing (>= && <0.2), containers, directory (>= && <1.4), directory-tree (>=0.12.1 && <0.13), extra (>=1.7.10 && <1.8), filepath (>= && <1.5), fsnotify (>= && <0.4), optparse-applicative (>=0.17.0 && <0.18), parallel (>= && <3.3), polyglot, text (>=2 && <2.1), utility-ht (>=0.0.16 && <0.1) [details]
License BSD-3-Clause
Copyright 2022 Alberto Perez Lopez
Author Alberto Perez Lopez
Maintainer albertoperez1994@gmail.com
Category Compiler
Home page https://github.com/albertprz/polyglot#readme
Bug tracker https://github.com/albertprz/polyglot/issues
Source repo head: git clone https://github.com/albertprz/polyglot
Uploaded by albertprz at 2023-05-25T11:41:27Z
Executables polyglot
Downloads 29 total (3 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2023-05-25 [all 1 reports]

Readme for polyglot-

[back to package description]



CLI tool to transpile Haskell modules to several target languages.

The CLI can convert individual Haskell files as well as recursively convert directory trees (or entire projects).

There are a few options available to, for example, watch a file / directory and reactively convert it whenever modified, as well as to format the output target language files.

At the moment, only parsing of Haskell 98 / 2010 standards along with a subset of GHC Syntax Extensions is supported (for example, there is currently no support for either Template Haskell or some of the GHC Syntax Extensions, such as GADTs and Type Families).


Usage: polyglot (-l|--language ARG) (-i|--input ARG) (-o|--output ARG) 
                [-f|--format] [-w|--watch] [--clear]

  Compile Haskell file(s) into a target language.

Available options:
  -h,--help                Show this help text
  -l,--language ARG        Target language
  -i,--input ARG           Path of input Haskell file or directory
  -o,--output ARG          Path of output file or directory
  -f,--format              Apply formatter on output file(s)
  -w,--watch               Watch for changes and convert automatically
  --clear                  Clear the output directory contents before conversion

Supported languages: Purescript, Scala


This CLI tool aims to perform a one-to-one mapping between Haskell and target language constructs. This can be done in most cases, because all of the available target languages support many of Haskell key features that are not necessarily available in other mainstream languages, such as Higher Kinded Types, Typeclasses, GADTs & Higher Rank Polymorphism.

However, the conversion can be lossy, so some information can be lost in the process. At the same time, it can be necessary to provide some extra information in the target language version of the source file (most prominently (type / kind) signatures, due to less powerful type inference mechanisms than Hindley-Millner in the target language).

The resulting files will have a dependency on some kind of prelude library that will expose all of the usual functions, data types, type classes and instances included in the Haskell prelude.

Also, bear in mind that in some cases due to different call semantics (lazy or call-by-need vs strict) and also runtime support for features (such as tail call optimization), the resulting files in the target language will probably need on some cases to be manually adapted post conversion, to preserve or approximate to the original Haskell code runtime characteristics.

In any case, it can be helpful to check the output files and manually adapt them as desired, because many Haskell idioms may not be the best match in the target language (This can be specially the case for languages that are not in the ML family (such as Scala)).


Sample Haskell snippet:

data Language
  = Purescript
  | Scala
  deriving (Bounded, Enum, Eq, Ord, Show)

parserOption :: Bookhound.Parser a -> Options.Applicative.Mod Options.Applicative.OptionFields a -> Parser a
parserOption parser = option $ eitherReader $ reader
    reader = mapLeft show . Bookhound.runParser parser . pack

Converted Purescript output (after formatting):

data Language
  = Purescript
  | Scala
derive instance Bounded Language
derive instance Enum Language
derive instance Eq Language
derive instance Ord Language
derive instance Show Language

parserOption :: forall a. Bookhound.Parser a -> Options.Applicative.Mod Options.Applicative.OptionFields a -> Parser a
parserOption parser = option $ eitherReader $ reader
    reader = mapLeft show <<< Bookhound.runParser parser <<< pack

Sample Haskell snippet:

action :: (ParseError -> IO ()) -> Opts -> IO ()
action errorAction Opts{language, sourcePath, targetPath, autoFormat} =
  readFileUtf8 sourcePath
  >>= (pack <<$>>) . traverse format . toTargetLanguage language
  >>= either errorAction createDirAndWriteFile

    createDirAndWriteFile x = createDirectoryIfMissing True finalDir *>
                              writeFileUtf8 finalPath x
    finalDir                = takeDirectory finalPath
    finalPath               = pathToLanguage language targetPath'

    targetPath' = if isDir targetPath then
                    replaceFileName targetPath (takeFileName sourcePath)

    format      = if autoFormat then
                    readProcess (formatterExec language)
                                ["--stdin", finalPath]

Converted Scala output (after formatting):

def action(x: ParseError => IO[Unit])(y: Opts): IO[Unit] =
  (x, y) match
    case (errorAction, Opts(language, sourcePath, targetPath, autoFormat)) =>
      def createDirAndWriteFile =
        createDirectoryIfMissing(true)(finalDir) *> writeFileUtf8(finalPath)(x)
      def finalDir =
      def finalPath =
        pathToLanguage(language, targetPath$)
      def targetPath$ =
        if isDir(targetPath) then
        else targetPath
      def format =
        if autoFormat then
          readProcess(formatterExec(language))(List("--stdin", finalPath))
        else pure

      >>= (pack <<&>> _) ^ traverse(format) ^ toTargetLanguage(language)
      >>= either(errorAction)(createDirAndWriteFile)

Supported GHC Syntax Extensions

# Syntax Sugar
- LambdaCase
- MultiWayIf
- PostfixOperators

# Types
- RankNTypes
- ExplicitForAll
- ScopedTypeVariables

# Records
- DuplicateRecordFields
- NoFieldSelectors
- NamedFieldPuns
- RecordWildCards
- OverloadedRecordDot

# Type Classes
- ConstrainedClassMethods
- MultiParamTypeClasses

# Deriving 
- StandaloneDeriving
- DerivingVia
- NewTypeDeriving
- AnyClassDeriving
- DerivingStrategies