├── installers
├── npm
│ ├── .gitignore
│ ├── .npmignore
│ ├── install.js
│ ├── package.json
│ ├── PUBLISHING.md
│ ├── bin
│ │ └── elm
│ ├── README.md
│ ├── download.js
│ └── troubleshooting.md
├── mac
│ ├── helper-scripts
│ │ ├── elm-startup.sh
│ │ └── uninstall.sh
│ ├── postinstall
│ ├── preinstall
│ ├── Resources
│ │ └── en.lproj
│ │ │ ├── welcome.rtf
│ │ │ └── conclusion.rtf
│ ├── Distribution.xml
│ ├── make-installer.sh
│ └── README.md
├── win
│ ├── logo.ico
│ ├── welcome.bmp
│ ├── inst.dat
│ ├── uninst.dat
│ ├── CreateInternetShortcut.nsh
│ ├── make_installer.cmd
│ ├── updatepath.vbs
│ ├── removefrompath.vbs
│ └── README.md
├── linux
│ ├── Dockerfile
│ └── README.md
└── README.md
├── cabal.config
├── ContributorAgreement.pdf
├── .gitignore
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── workflows
│ ├── set-issue-expectations.yml
│ └── set-pull-expectations.yml
└── CONTRIBUTING.md
├── compiler
└── src
│ ├── Elm
│ ├── Magnitude.hs
│ ├── Float.hs
│ ├── Compiler
│ │ ├── Imports.hs
│ │ └── Type.hs
│ ├── String.hs
│ └── Version.hs
│ ├── Reporting
│ ├── Report.hs
│ ├── Suggest.hs
│ ├── Annotation.hs
│ ├── Result.hs
│ ├── Render
│ │ └── Type
│ │ │ └── Localizer.hs
│ ├── Warning.hs
│ └── Error
│ │ └── Main.hs
│ ├── Data
│ ├── Map
│ │ └── Utils.hs
│ ├── NonEmptyList.hs
│ ├── Bag.hs
│ ├── OneOrMore.hs
│ └── Index.hs
│ ├── AST
│ ├── Utils
│ │ ├── Binop.hs
│ │ ├── Shader.hs
│ │ └── Type.hs
│ └── Source.hs
│ ├── Generate
│ ├── Html.hs
│ ├── Mode.hs
│ └── JavaScript
│ │ └── Functions.hs
│ ├── Type
│ ├── Instantiate.hs
│ ├── Occurs.hs
│ └── UnionFind.hs
│ ├── Parse
│ └── Symbol.hs
│ ├── Compile.hs
│ ├── Nitpick
│ └── Debug.hs
│ ├── Canonicalize
│ ├── Environment
│ │ └── Dups.hs
│ └── Type.hs
│ ├── Json
│ └── String.hs
│ └── Optimize
│ ├── Case.hs
│ └── Names.hs
├── worker
├── outlines
│ ├── repl
│ │ └── elm.json
│ └── compile
│ │ └── elm.json
├── nginx.conf
├── elm.json
└── src
│ ├── Cors.hs
│ ├── Main.hs
│ └── Artifacts.hs
├── .travis.yml
├── hints
├── tuples.md
├── repl.md
├── infinite-type.md
├── type-annotations.md
├── optimize.md
├── port-modules.md
├── implicit-casts.md
├── shadowing.md
├── comparing-records.md
├── comparing-custom-types.md
├── init.md
├── missing-patterns.md
└── recursive-alias.md
├── LICENSE
├── terminal
├── impl
│ └── Terminal
│ │ ├── Internal.hs
│ │ └── Helpers.hs
└── src
│ └── Main.hs
├── docs
├── elm.json
│ ├── application.md
│ └── package.md
└── upgrade-instructions
│ └── 0.19.1.md
└── README.md
/installers/npm/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/cabal.config:
--------------------------------------------------------------------------------
1 | profiling: False
2 | library-profiling: True
3 |
--------------------------------------------------------------------------------
/installers/npm/.npmignore:
--------------------------------------------------------------------------------
1 | README.md
2 | .gitignore
3 | .git
4 |
--------------------------------------------------------------------------------
/ContributorAgreement.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsheep/elm2node/HEAD/ContributorAgreement.pdf
--------------------------------------------------------------------------------
/installers/mac/helper-scripts/elm-startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | open 'http://guide.elm-lang.org'
4 |
--------------------------------------------------------------------------------
/installers/win/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsheep/elm2node/HEAD/installers/win/logo.ico
--------------------------------------------------------------------------------
/installers/mac/postinstall:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -ex
4 |
5 | echo "$(date)" > /tmp/elm-installer.log
6 |
--------------------------------------------------------------------------------
/installers/win/welcome.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebsheep/elm2node/HEAD/installers/win/welcome.bmp
--------------------------------------------------------------------------------
/installers/npm/install.js:
--------------------------------------------------------------------------------
1 |
2 | var download = require('./download.js');
3 |
4 | download(function() {});
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | elm-stuff
2 | dist
3 | cabal-dev
4 | .cabal-sandbox/
5 | cabal.sandbox.config
6 | .DS_Store
7 | *~
8 | travis.log
9 |
--------------------------------------------------------------------------------
/installers/win/inst.dat:
--------------------------------------------------------------------------------
1 | SetOutPath "$INSTDIR\bin"
2 | File "${FILES_SOURCE_PATH}\bin\elm.exe"
3 |
4 | SetOutPath "$INSTDIR"
5 | File "updatepath.vbs"
6 | File "removefrompath.vbs"
7 |
--------------------------------------------------------------------------------
/installers/win/uninst.dat:
--------------------------------------------------------------------------------
1 | Delete "$INSTDIR\bin\elm.exe"
2 | RmDir "$INSTDIR\bin"
3 |
4 | Delete "$INSTDIR\updatepath.vbs"
5 | Delete "$INSTDIR\removefrompath.vbs"
6 | RmDir "$INSTDIR"
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | **Quick Summary:** ???
3 |
4 |
5 | ## SSCCE
6 |
7 | ```elm
8 |
9 | ```
10 |
11 | - **Elm:** ???
12 | - **Browser:** ???
13 | - **Operating System:** ???
14 |
15 |
16 | ## Additional Details
17 |
18 | ???
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | **Quick Summary:** ???
3 |
4 |
5 | ## SSCCE
6 |
7 | ```elm
8 |
9 | ```
10 |
11 | - **Elm:** ???
12 | - **Browser:** ???
13 | - **Operating System:** ???
14 |
15 |
16 | ## Additional Details
17 |
18 | ???
19 |
--------------------------------------------------------------------------------
/installers/win/CreateInternetShortcut.nsh:
--------------------------------------------------------------------------------
1 | !macro CreateInternetShortcut FILENAME URL ICONFILE ICONINDEX
2 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "URL" "${URL}"
3 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "IconFile" "${ICONFILE}"
4 | WriteINIStr "${FILENAME}.url" "InternetShortcut" "IconIndex" "${ICONINDEX}"
5 | !macroend
--------------------------------------------------------------------------------
/compiler/src/Elm/Magnitude.hs:
--------------------------------------------------------------------------------
1 | module Elm.Magnitude
2 | ( Magnitude(..)
3 | , toChars
4 | )
5 | where
6 |
7 |
8 |
9 | -- MAGNITUDE
10 |
11 |
12 | data Magnitude
13 | = PATCH
14 | | MINOR
15 | | MAJOR
16 | deriving (Eq, Ord)
17 |
18 |
19 | toChars :: Magnitude -> String
20 | toChars magnitude =
21 | case magnitude of
22 | PATCH -> "PATCH"
23 | MINOR -> "MINOR"
24 | MAJOR -> "MAJOR"
25 |
--------------------------------------------------------------------------------
/installers/mac/preinstall:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | installdir=/usr/local/bin
6 |
7 | for bin in elm elm-compiler elm-package elm-reactor elm-repl
8 | do
9 | if [ -f $installdir/$bin ]; then
10 | sudo rm -f $installdir/$bin
11 | fi
12 | if [ -f $installdir/$bin-unwrapped ]; then
13 | sudo rm -f $installdir/$bin-unwrapped
14 | fi
15 | done
16 |
17 | sharedir=/usr/local/share/elm
18 | sudo rm -rf $sharedir
19 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Report.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | module Reporting.Report
3 | ( Report(..)
4 | )
5 | where
6 |
7 |
8 | import qualified Reporting.Annotation as A
9 | import qualified Reporting.Doc as D
10 |
11 |
12 |
13 | -- BUILD REPORTS
14 |
15 |
16 | data Report =
17 | Report
18 | { _title :: String
19 | , _region :: A.Region
20 | , _sgstns :: [String]
21 | , _message :: D.Doc
22 | }
23 |
--------------------------------------------------------------------------------
/installers/win/make_installer.cmd:
--------------------------------------------------------------------------------
1 |
2 | set version=%1
3 |
4 | mkdir files
5 | mkdir files\bin
6 |
7 | xcopy ..\..\dist\build\elm\elm.exe files\bin /s /e
8 | xcopy updatepath.vbs files
9 |
10 | if EXIST "%ProgramFiles%\NSIS" (
11 | set nsis="%ProgramFiles%\NSIS\makensis.exe"
12 | ) else (
13 | set nsis="%ProgramFiles(x86)%\NSIS\makensis.exe"
14 | )
15 |
16 | %nsis% /DPLATFORM_VERSION=%version% Nsisfile.nsi
17 |
18 | rd /s /q files
19 |
--------------------------------------------------------------------------------
/worker/outlines/repl/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "../../src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/core": "1.0.2"
10 | },
11 | "indirect": {
12 | "elm/json": "1.1.3"
13 | }
14 | },
15 | "test-dependencies": {
16 | "direct": {},
17 | "indirect": {}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/installers/win/updatepath.vbs:
--------------------------------------------------------------------------------
1 | Set WshShell = CreateObject("WScript.Shell")
2 | elmPath = WScript.Arguments(0)
3 | 'const PathRegKey = "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path"
4 | const PathRegKey = "HKCU\Environment\Path"
5 |
6 | on error resume next
7 | path = WshShell.RegRead(PathRegKey)
8 | if err.number <> 0 then
9 | path = ""
10 | end if
11 | on error goto 0
12 |
13 | newPath = elmPath & ";" & path
14 | Call WshShell.RegWrite(PathRegKey, newPath, "REG_EXPAND_SZ")
15 |
--------------------------------------------------------------------------------
/installers/mac/helper-scripts/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | echo "Warning: You are about to remove all Elm executables!"
6 |
7 | installdir=/usr/local/bin
8 |
9 | for bin in elm elm-compiler elm-get elm-reactor elm-repl elm-doc elm-server elm-package elm-make
10 | do
11 | if [ -f $installdir/$bin ]; then
12 | sudo rm -f $installdir/$bin
13 | fi
14 | if [ -f $installdir/$bin-unwrapped ]; then
15 | sudo rm -f $installdir/$bin-unwrapped
16 | fi
17 |
18 | done
19 |
20 | sharedir=/usr/local/share/elm
21 | sudo rm -rf $sharedir
22 |
--------------------------------------------------------------------------------
/installers/mac/Resources/en.lproj/welcome.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf2509
2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fmodern\fcharset0 CourierNewPSMT;}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\*\expandedcolortbl;;}
5 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0
6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0
7 |
8 | \f0\fs28 \cf0 Thank you for trying out Elm!\
9 | \
10 | This installer makes
11 | \f1 elm
12 | \f0 available in your terminal.}
--------------------------------------------------------------------------------
/worker/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name worker.elm-lang.org;
4 |
5 | location / {
6 | proxy_pass http://localhost:8000;
7 | }
8 | }
9 |
10 | server {
11 | listen 443 ssl;
12 | server_name worker.elm-lang.org;
13 |
14 | location / {
15 | proxy_pass http://localhost:8000;
16 | }
17 |
18 | ssl_certificate /etc/letsencrypt/live/worker.elm-lang.org/fullchain.pem; # managed by Certbot
19 | ssl_certificate_key /etc/letsencrypt/live/worker.elm-lang.org/privkey.pem; # managed by Certbot
20 | include /etc/letsencrypt/options-ssl-nginx.conf;
21 | ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
22 | }
23 |
--------------------------------------------------------------------------------
/worker/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.1",
10 | "elm/core": "1.0.2",
11 | "elm/html": "1.0.0",
12 | "elm/json": "1.1.3",
13 | "elm/project-metadata-utils": "1.0.0"
14 | },
15 | "indirect": {
16 | "elm/parser": "1.1.0",
17 | "elm/time": "1.0.0",
18 | "elm/url": "1.0.0",
19 | "elm/virtual-dom": "1.0.2"
20 | }
21 | },
22 | "test-dependencies": {
23 | "direct": {},
24 | "indirect": {}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/installers/mac/Resources/en.lproj/conclusion.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1252\cocoartf2509
2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fmodern\fcharset0 CourierNewPSMT;}
3 | {\colortbl;\red255\green255\blue255;}
4 | {\*\expandedcolortbl;;}
5 | \paperw11900\paperh16840\margl1440\margr1440\vieww11180\viewh8400\viewkind0
6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
7 |
8 | \f0\fs28 \cf0 Try opening the terminal and running commands like:\
9 | \
10 |
11 | \f1 elm init\
12 | elm make src/Main.elm --optimize\
13 | elm repl
14 | \f0 \
15 | \
16 | Check out {\field{\*\fldinst{HYPERLINK "https://guide.elm-lang.org/"}}{\fldrslt this tutorial}} for more advice!}
--------------------------------------------------------------------------------
/installers/win/removefrompath.vbs:
--------------------------------------------------------------------------------
1 | Set WshShell = CreateObject("WScript.Shell")
2 | ' Make sure there is no trailing slash at the end of elmBasePath
3 | elmBasePath = WScript.Arguments(0)
4 | 'const PathRegKey = "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\Path"
5 | const PathRegKey = "HKCU\Environment\Path"
6 |
7 | on error resume next
8 | path = WshShell.RegRead(PathRegKey)
9 | if err.number = 0 then
10 | Set regEx = New RegExp
11 | elmBasePath = Replace(Replace(Replace(elmBasePath, "\", "\\"), "(", "\("), ")", "\)")
12 | regEx.Pattern = elmBasePath & "\\\d+\.\d+(\.\d+|)\\bin(;|)"
13 | regEx.Global = True
14 | newPath = regEx.Replace(path, "")
15 | Call WshShell.RegWrite(PathRegKey, newPath, "REG_EXPAND_SZ")
16 | end if
17 | on error goto 0
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: minimal
2 | services: docker
3 |
4 | env:
5 | global:
6 | - LINUX_ARCHIVE=binary-for-linux-64-bit.gz
7 |
8 | before_install:
9 | - docker build -t elm -f installers/linux/Dockerfile .
10 | - docker cp $(docker create elm):/usr/local/bin/elm .
11 | - gzip -9 -c elm > $LINUX_ARCHIVE
12 |
13 | deploy:
14 | provider: releases
15 | api_key:
16 | secure: Yz2Lo4u9rZQ7Ee7ohAsrZpkqsYDUerCSMdSQIH8ryrf7phHhiloPEkTKsM+NupHqU/LEAVsunxbau4QrCEjA2vPavAPVk8cKomRUWK/YjbXHKa24hPkal2c+A2bnMQ6w3qYk/PjL9rW+Goq++/SNLcYZwHBV0Chl2blivMwWCSA=
17 | file: $LINUX_ARCHIVE
18 | skip_cleanup: true
19 | on:
20 | branch: master
21 | tags: true
22 |
23 | notifications:
24 | email:
25 | recipients:
26 | - rlefevre@dmy.fr
27 | on_success: change
28 | on_failure: change
29 |
--------------------------------------------------------------------------------
/installers/win/README.md:
--------------------------------------------------------------------------------
1 | # Installing on Windows
2 |
3 | The installer for Windows is available [here](https://guide.elm-lang.org/install.html).
4 |
5 |
6 |
7 |
8 | ## Uninstall
9 |
10 | First run the `C:\Program Files (x86)\Elm\0.19\uninstall.exe` file. This will remove Elm stuff from your `PATH`.
11 |
12 | Then remove the whole `C:\Users\\AppData\Roaming\elm` directory. Elm caches some packages and build artifacts to reduce compile times and to help you work offline. Getting rid of this directory will clear that information out!
13 |
14 |
15 |
16 | ## Building the Windows installer
17 |
18 | You will need the [NSIS installer](http://nsis.sourceforge.net/Download) to be installed.
19 |
20 | Once everything is installed, run something like this command:
21 |
22 | make_installer.cmd 0.19.0
23 |
24 | It will build an installer called `Elm-0.19.0-setup.exe`.
25 |
--------------------------------------------------------------------------------
/compiler/src/Elm/Float.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE EmptyDataDecls, FlexibleInstances #-}
3 | module Elm.Float
4 | ( Float
5 | , fromPtr
6 | , toBuilder
7 | )
8 | where
9 |
10 |
11 | import Prelude hiding (Float)
12 | import Data.Binary (Binary, get, put)
13 | import qualified Data.ByteString.Builder as B
14 | import qualified Data.Utf8 as Utf8
15 | import Data.Word (Word8)
16 | import Foreign.Ptr (Ptr)
17 |
18 |
19 |
20 | -- FLOATS
21 |
22 |
23 | type Float =
24 | Utf8.Utf8 ELM_FLOAT
25 |
26 |
27 | data ELM_FLOAT
28 |
29 |
30 |
31 | -- HELPERS
32 |
33 |
34 | fromPtr :: Ptr Word8 -> Ptr Word8 -> Float
35 | fromPtr =
36 | Utf8.fromPtr
37 |
38 |
39 | {-# INLINE toBuilder #-}
40 | toBuilder :: Float -> B.Builder
41 | toBuilder =
42 | Utf8.toBuilder
43 |
44 |
45 |
46 | -- BINARY
47 |
48 |
49 | instance Binary (Utf8.Utf8 ELM_FLOAT) where
50 | get = Utf8.getUnder256
51 | put = Utf8.putUnder256
52 |
--------------------------------------------------------------------------------
/worker/outlines/compile/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "../../src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.1",
10 | "elm/core": "1.0.2",
11 | "elm/file": "1.0.5",
12 | "elm/html": "1.0.0",
13 | "elm/http": "2.0.0",
14 | "elm/json": "1.1.3",
15 | "elm/random": "1.0.0",
16 | "elm/svg": "1.0.1",
17 | "elm/time": "1.0.0",
18 | "elm-explorations/linear-algebra": "1.0.3",
19 | "elm-explorations/webgl": "1.1.0",
20 | "evancz/elm-playground": "1.0.2"
21 | },
22 | "indirect": {
23 | "elm/bytes": "1.0.8",
24 | "elm/url": "1.0.0",
25 | "elm/virtual-dom": "1.0.2"
26 | }
27 | },
28 | "test-dependencies": {
29 | "direct": {},
30 | "indirect": {}
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/compiler/src/Data/Map/Utils.hs:
--------------------------------------------------------------------------------
1 | module Data.Map.Utils
2 | ( fromKeys
3 | , fromKeysA
4 | , fromValues
5 | , any
6 | )
7 | where
8 |
9 |
10 | import Prelude hiding (any)
11 | import qualified Data.Map as Map
12 | import Data.Map.Internal (Map(..))
13 |
14 |
15 |
16 | -- FROM KEYS
17 |
18 |
19 | fromKeys :: (Ord k) => (k -> v) -> [k] -> Map.Map k v
20 | fromKeys toValue keys =
21 | Map.fromList $ map (\k -> (k, toValue k)) keys
22 |
23 |
24 | fromKeysA :: (Applicative f, Ord k) => (k -> f v) -> [k] -> f (Map.Map k v)
25 | fromKeysA toValue keys =
26 | Map.fromList <$> traverse (\k -> (,) k <$> toValue k) keys
27 |
28 |
29 | fromValues :: (Ord k) => (v -> k) -> [v] -> Map.Map k v
30 | fromValues toKey values =
31 | Map.fromList $ map (\v -> (toKey v, v)) values
32 |
33 |
34 |
35 | -- ANY
36 |
37 |
38 | {-# INLINE any #-}
39 | any :: (v -> Bool) -> Map.Map k v -> Bool
40 | any isGood = go
41 | where
42 | go Tip = False
43 | go (Bin _ _ v l r) = isGood v || go l || go r
44 |
--------------------------------------------------------------------------------
/.github/workflows/set-issue-expectations.yml:
--------------------------------------------------------------------------------
1 | name: Set Issue Expectations
2 | on:
3 | issues:
4 | types: [opened]
5 | jobs:
6 | comment-on-issue:
7 | name: Comment On Issue
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/github@v1.0.0
11 | env:
12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 | with:
14 | args: |
15 | comment "Thanks for reporting this! To set expectations:
16 |
17 | - Issues are reviewed in [batches](https://github.com/elm/expectations/blob/master/batching.md), so it can take some time to get a response.
18 | - Ask questions a [community forum](https://elm-lang.org/community). You will get an answer quicker that way!
19 | - If you experience something similar, open a new issue. [We like duplicates](https://github.com/elm/expectations/blob/master/duplicates.md).
20 |
21 | Finally, please be patient with the core team. They are trying their best with limited resources."
22 |
--------------------------------------------------------------------------------
/hints/tuples.md:
--------------------------------------------------------------------------------
1 |
2 | # From Tuples to Records
3 |
4 | The largest tuple possible in Elm has three entries. Once you get to four, it is best to make a record with named entries.
5 |
6 | For example, it is _conceivable_ to represent a rectangle as four numbers like `(10,10,100,100)` but it would be more self-documenting to use a record like this:
7 |
8 | ```elm
9 | type alias Rectangle =
10 | { x : Float
11 | , y : Float
12 | , width : Float
13 | , height : Float
14 | }
15 | ```
16 |
17 | Now it is clear that the dimensions should be `Float` values. It is also clear that we are not using the convention of specifying the top-left and bottom-right corners. It could be clearer about whether the `x` and `y` is the point in the top-left or in the middle though!
18 |
19 | Anyway, using records like this also gives you access to syntax like `rect.x`, `.x`, and `{ rect | x = 40 }`. It is not clear how to design features like that for arbitrarily sized tuples, so we did not. We already have a way, and it is more self-documenting!
20 |
--------------------------------------------------------------------------------
/compiler/src/AST/Utils/Binop.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module AST.Utils.Binop
3 | ( Precedence(..)
4 | , Associativity(..)
5 | )
6 | where
7 |
8 |
9 | import Prelude hiding (Either(..))
10 | import Control.Monad (liftM)
11 | import Data.Binary
12 |
13 |
14 |
15 | -- BINOP STUFF
16 |
17 |
18 | newtype Precedence = Precedence Int
19 | deriving (Eq, Ord)
20 |
21 |
22 | data Associativity
23 | = Left
24 | | Non
25 | | Right
26 | deriving (Eq)
27 |
28 |
29 |
30 | -- BINARY
31 |
32 |
33 | instance Binary Precedence where
34 | get =
35 | liftM Precedence get
36 |
37 | put (Precedence n) =
38 | put n
39 |
40 |
41 | instance Binary Associativity where
42 | get =
43 | do n <- getWord8
44 | case n of
45 | 0 -> return Left
46 | 1 -> return Non
47 | 2 -> return Right
48 | _ -> fail "Error reading valid associativity from serialized string"
49 |
50 | put assoc =
51 | putWord8 $
52 | case assoc of
53 | Left -> 0
54 | Non -> 1
55 | Right -> 2
56 |
--------------------------------------------------------------------------------
/installers/npm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elm",
3 | "version": "0.19.1-3",
4 | "description": "Installer for Elm: just downloads the binary into node_modules",
5 | "preferGlobal": true,
6 | "license": "BSD-3-Clause",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/elm/compiler.git"
10 | },
11 | "homepage": "https://github.com/elm/compiler/tree/master/installers/npm",
12 | "bugs": "https://github.com/elm/compiler/issues",
13 | "author": {
14 | "name": "Evan Czaplicki",
15 | "email": "evan@elm-lang.org",
16 | "url": "https://github.com/evancz"
17 | },
18 | "engines": {
19 | "node": ">=7.0.0"
20 | },
21 | "scripts": {
22 | "install": "node install.js"
23 | },
24 | "files": [
25 | "install.js",
26 | "download.js",
27 | "bin",
28 | "bin/elm"
29 | ],
30 | "keywords": [
31 | "bin",
32 | "binary",
33 | "binaries",
34 | "elm",
35 | "install",
36 | "installer"
37 | ],
38 | "bin": { "elm": "bin/elm" },
39 | "dependencies": {
40 | "request": "^2.88.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Suggest.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Reporting.Suggest
4 | ( distance
5 | , sort
6 | , rank
7 | )
8 | where
9 |
10 |
11 | import qualified Data.Char as Char
12 | import qualified Data.List as List
13 | import qualified Text.EditDistance as Dist
14 |
15 |
16 |
17 | -- DISTANCE
18 |
19 |
20 | distance :: String -> String -> Int
21 | distance x y =
22 | Dist.restrictedDamerauLevenshteinDistance Dist.defaultEditCosts x y
23 |
24 |
25 |
26 | -- SORT
27 |
28 |
29 | sort :: String -> (a -> String) -> [a] -> [a]
30 | sort target toString values =
31 | List.sortOn (distance (toLower target) . toLower . toString) values
32 |
33 |
34 | toLower :: String -> String
35 | toLower string =
36 | map Char.toLower string
37 |
38 |
39 |
40 | -- RANK
41 |
42 |
43 | rank :: String -> (a -> String) -> [a] -> [(Int,a)]
44 | rank target toString values =
45 | let
46 | toRank v =
47 | distance (toLower target) (toLower (toString v))
48 |
49 | addRank v =
50 | (toRank v, v)
51 | in
52 | List.sortOn fst (map addRank values)
53 |
--------------------------------------------------------------------------------
/.github/workflows/set-pull-expectations.yml:
--------------------------------------------------------------------------------
1 | name: Set Pull Expectations
2 | on:
3 | pull_request:
4 | types: [opened]
5 | jobs:
6 | comment-on-pull:
7 | name: Comment On Pull
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/github@v1.0.0
11 | env:
12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 | with:
14 | args: |
15 | comment "Thanks for suggesting these code changes. To set expectations:
16 |
17 | - Pull requests are reviewed in [batches](https://github.com/elm/expectations/blob/master/batching.md), so it can take some time to get a response.
18 | - Smaller pull requests are easier to review. To fix nine typos, nine specific issues will always go faster than one big one. Learn why [here](https://github.com/elm/expectations/blob/master/small-pull-requests.md).
19 | - Reviewers may not know as much as you about certain situations, so add links to supporting evidence for important claims, especially regarding standards for CSS, HTTP, URI, etc.
20 |
21 | Finally, please be patient with the core team. They are trying their best with limited resources."
22 |
--------------------------------------------------------------------------------
/compiler/src/Generate/Html.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | {-# LANGUAGE QuasiQuotes #-}
3 | module Generate.Html
4 | ( sandwich
5 | )
6 | where
7 |
8 |
9 | import qualified Data.ByteString.Builder as B
10 | import Data.Monoid ((<>))
11 | import qualified Data.Name as Name
12 | import Text.RawString.QQ (r)
13 |
14 |
15 |
16 | -- SANDWICH
17 |
18 |
19 | sandwich :: Name.Name -> B.Builder -> B.Builder
20 | sandwich moduleName javascript =
21 | let name = Name.toBuilder moduleName in
22 | [r|
23 |
24 |
25 |
26 | |] <> name <> [r|
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
52 |
53 |
54 | |]
55 |
--------------------------------------------------------------------------------
/installers/linux/Dockerfile:
--------------------------------------------------------------------------------
1 | # Based initially on https://gist.github.com/rlefevre/1523f47e75310e28eee243c9c5651ac9
2 | #
3 | # Build Linux x64 binary from elm compiler top-level directory:
4 | # $ docker build -t elm -f installers/linux/Dockerfile .
5 | #
6 | # Retrieve elm Linux binary:
7 | # $ docker cp $(docker create elm):/usr/local/bin/elm DESTINATION_DIRECTORY
8 | #
9 | # Delete docker elm image:
10 | # $ docker rmi elm
11 | #
12 | # Display all images:
13 | # $ docker images -a
14 | #
15 | # Delete all unused docker images:
16 | # $ docker system prune -a
17 |
18 | # Use Alpine 3.11 with GHC 8.6.5
19 | FROM alpine:3.11
20 |
21 | # Install packages required to build elm
22 | RUN apk add --no-cache ghc cabal wget musl-dev zlib-dev zlib-static ncurses-dev ncurses-static
23 |
24 | WORKDIR /elm
25 |
26 | # Import source code
27 | COPY builder builder
28 | COPY compiler compiler
29 | COPY reactor reactor
30 | COPY terminal terminal
31 | COPY cabal.config elm.cabal LICENSE ./
32 |
33 | # Build statically linked elm binary
34 | RUN cabal new-update
35 | RUN cabal new-build --ghc-option=-optl=-static --ghc-option=-split-sections
36 | RUN cp ./dist-newstyle/build/x86_64-linux/ghc-*/elm-*/x/elm/build/elm/elm /usr/local/bin/elm
37 |
38 | # Remove debug symbols to optimize the binary size
39 | RUN strip -s /usr/local/bin/elm
40 |
--------------------------------------------------------------------------------
/worker/src/Cors.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Cors
3 | ( allow
4 | )
5 | where
6 |
7 |
8 | import qualified Data.HashSet as HashSet
9 | import Network.URI (parseURI)
10 | import Snap.Core (Snap, Method, method)
11 | import Snap.Util.CORS (CORSOptions(..), HashableMethod(..), OriginList(Origins), applyCORS, mkOriginSet)
12 |
13 |
14 |
15 | -- ALLOW
16 |
17 |
18 | allow :: Method -> [String] -> Snap () -> Snap ()
19 | allow method_ origins snap =
20 | applyCORS (toOptions method_ origins) $ method method_ $
21 | snap
22 |
23 |
24 |
25 | -- TO OPTIONS
26 |
27 |
28 | toOptions :: (Monad m) => Method -> [String] -> CORSOptions m
29 | toOptions method_ origins =
30 | let
31 | allowedOrigins = toOriginList origins
32 | allowedMethods = HashSet.singleton (HashableMethod method_)
33 | in
34 | CORSOptions
35 | { corsAllowOrigin = return allowedOrigins
36 | , corsAllowCredentials = return True
37 | , corsExposeHeaders = return HashSet.empty
38 | , corsAllowedMethods = return allowedMethods
39 | , corsAllowedHeaders = return
40 | }
41 |
42 |
43 | toOriginList :: [String] -> OriginList
44 | toOriginList origins =
45 | Origins $ mkOriginSet $
46 | case traverse parseURI origins of
47 | Just uris -> uris
48 | Nothing -> error "invalid entry given to toOriginList list"
49 |
--------------------------------------------------------------------------------
/installers/mac/Distribution.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Elm
4 |
5 |
6 |
7 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | binaries.pkg
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2012-present Evan Czaplicki
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 |
7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 |
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
--------------------------------------------------------------------------------
/installers/README.md:
--------------------------------------------------------------------------------
1 | # Installing Elm
2 |
3 | The normal path is to work through [the guide](https://guide.elm-lang.org/) until you need to install, but you can skip to installation directly by going [here](https://guide.elm-lang.org/install/terminal.html).
4 |
5 |
6 |
7 |
8 | ## Installing Multiple Versions
9 |
10 | The secret is that Elm is just a single executable file. If you are developing a project in `~/Desktop/project/` you can download this file into that directory and run commands like `~/Desktop/project/elm make src/Main.elm` or `./elm make src/Main.elm`. You just run the local copy of the executable file!
11 |
12 | The instructions for [Mac][mac] and [Linux][lin] explain how to do this in more detail. You can follow the same steps on Windows, but you need to do each step by hand. (E.g. download the file through your browser rather than with a terminal command.)
13 |
14 | [mac]: https://github.com/elm/compiler/blob/master/installers/mac/README.md
15 | [lin]: https://github.com/elm/compiler/blob/master/installers/linux/README.md
16 |
17 |
18 |
19 | ## Installing Previous Versions
20 |
21 | The past binaries for Mac, Linux, and Windows are hosted [here](https://github.com/elm/compiler/releases).
22 |
23 | You can download the executable files directly and use them locally.
24 |
25 |
26 |
27 |
28 | ## Uninstall
29 |
30 | - [Mac](https://github.com/elm/compiler/blob/master/installers/mac/README.md#uninstall)
31 | - [Linux](https://github.com/elm/compiler/blob/master/installers/linux/README.md#uninstall)
32 | - [Windows](https://github.com/elm/compiler/blob/master/installers/win/README.md#uninstall)
33 |
--------------------------------------------------------------------------------
/compiler/src/Generate/Mode.hs:
--------------------------------------------------------------------------------
1 | module Generate.Mode
2 | ( Mode(..)
3 | , isDebug
4 | , ShortFieldNames
5 | , shortenFieldNames
6 | )
7 | where
8 |
9 |
10 | import qualified Data.List as List
11 | import qualified Data.Map as Map
12 | import qualified Data.Maybe as Maybe
13 | import qualified Data.Name as Name
14 |
15 | import qualified AST.Optimized as Opt
16 | import qualified Elm.Compiler.Type.Extract as Extract
17 | import qualified Generate.JavaScript.Name as JsName
18 |
19 |
20 |
21 | -- MODE
22 |
23 |
24 | data Mode
25 | = Dev (Maybe Extract.Types)
26 | | Prod ShortFieldNames
27 |
28 |
29 | isDebug :: Mode -> Bool
30 | isDebug mode =
31 | case mode of
32 | Dev mi -> Maybe.isJust mi
33 | Prod _ -> False
34 |
35 |
36 |
37 | -- SHORTEN FIELD NAMES
38 |
39 |
40 | type ShortFieldNames =
41 | Map.Map Name.Name JsName.Name
42 |
43 |
44 | shortenFieldNames :: Opt.GlobalGraph -> ShortFieldNames
45 | shortenFieldNames (Opt.GlobalGraph _ frequencies) =
46 | Map.foldr addToShortNames Map.empty $
47 | Map.foldrWithKey addToBuckets Map.empty frequencies
48 |
49 |
50 | addToBuckets :: Name.Name -> Int -> Map.Map Int [Name.Name] -> Map.Map Int [Name.Name]
51 | addToBuckets field frequency buckets =
52 | Map.insertWith (++) frequency [field] buckets
53 |
54 |
55 | addToShortNames :: [Name.Name] -> ShortFieldNames -> ShortFieldNames
56 | addToShortNames fields shortNames =
57 | List.foldl' addField shortNames fields
58 |
59 |
60 | addField :: ShortFieldNames -> Name.Name -> ShortFieldNames
61 | addField shortNames field =
62 | let rename = JsName.fromInt (Map.size shortNames) in
63 | Map.insert field rename shortNames
64 |
--------------------------------------------------------------------------------
/compiler/src/Data/NonEmptyList.hs:
--------------------------------------------------------------------------------
1 | module Data.NonEmptyList
2 | ( List(..)
3 | , singleton
4 | , toList
5 | , sortBy
6 | )
7 | where
8 |
9 |
10 | import Control.Monad (liftM2)
11 | import Data.Binary (Binary, get, put)
12 | import qualified Data.List as List
13 |
14 |
15 |
16 | -- LIST
17 |
18 |
19 | data List a =
20 | List a [a]
21 |
22 |
23 | singleton :: a -> List a
24 | singleton a =
25 | List a []
26 |
27 |
28 | toList :: List a -> [a]
29 | toList (List x xs) =
30 | x:xs
31 |
32 |
33 |
34 | -- INSTANCES
35 |
36 | instance Show a => Show (List a) where
37 | show (List x xs) = show (x:xs)
38 |
39 | instance Functor List where
40 | fmap func (List x xs) = List (func x) (map func xs)
41 |
42 |
43 | instance Traversable List where
44 | traverse func (List x xs) = List <$> func x <*> traverse func xs
45 |
46 |
47 | instance Foldable List where
48 | foldr step state (List x xs) = step x (foldr step state xs)
49 | foldl step state (List x xs) = foldl step (step state x) xs
50 | foldl1 step (List x xs) = foldl step x xs
51 |
52 |
53 |
54 | -- SORT BY
55 |
56 |
57 | sortBy :: (Ord b) => (a -> b) -> List a -> List a
58 | sortBy toRank (List x xs) =
59 | let
60 | comparison a b =
61 | compare (toRank a) (toRank b)
62 | in
63 | case List.sortBy comparison xs of
64 | [] ->
65 | List x []
66 |
67 | y:ys ->
68 | case comparison x y of
69 | LT -> List x (y:ys)
70 | EQ -> List x (y:ys)
71 | GT -> List y (List.insertBy comparison x ys)
72 |
73 |
74 |
75 | -- BINARY
76 |
77 |
78 | instance (Binary a) => Binary (List a) where
79 | put (List x xs) = put x >> put xs
80 | get = liftM2 List get get
81 |
--------------------------------------------------------------------------------
/installers/npm/PUBLISHING.md:
--------------------------------------------------------------------------------
1 | # Publishing
2 |
3 | Here's how to update the `npm` installer.
4 |
5 |
6 | ## 1. GitHub Release
7 |
8 | Create a [GitHub Release](https://github.com/elm/compiler/releases) with the following files:
9 |
10 | 1. `binary-for-mac-64-bit.gz`
11 | 2. `binary-for-windows-64-bit.gz`
12 | 3. `binary-for-linux-64-bit.gz`
13 |
14 | Create each of these by running the `elm` executable for each platform through `gzip elm`.
15 |
16 |
17 | ## 2. Try a beta release
18 |
19 | In `package.json`, bump the version to `"0.19.2-beta"`.
20 |
21 | ```bash
22 | npm publish --tag beta
23 | ```
24 |
25 | To test that it works, run these commands:
26 |
27 | ```bash
28 | npm dist-tags ls elm
29 | npm install elm@beta --ignore-scripts
30 | ```
31 |
32 | The `latest` tag should not be changed, and there should be an additional `beta` tag.
33 |
34 | Try this on Windows, Linux, and Mac.
35 |
36 |
37 | ## 3. Publish final release
38 |
39 | Remove the `-beta` suffix from the version in `package.json`. Then run:
40 |
41 | ```bash
42 | npm publish
43 | ```
44 |
45 |
46 | ## 4. Tag the `latest-0.19.1` version
47 |
48 | Many compiler releases have needed multiple `npm` publications. Maybe something does not work on Windows or some dependency becomes insecure. Normal `npm` problems.
49 |
50 | The convention for each Elm release is to create a tag the latest one.
51 |
52 | ```bash
53 | npm dist-tag add elm@0.19.1-3 latest-0.19.1
54 | ```
55 |
56 | That way people who want a specific version can point to `latest-0.19.1` or `latest-0.18.0` instead of knowing the particular names of all the various publications.
57 |
58 | You can read more about dist-tags [here](https://docs.npmjs.com/cli/dist-tag).
59 |
60 |
--------------------------------------------------------------------------------
/hints/repl.md:
--------------------------------------------------------------------------------
1 |
2 | # REPL
3 |
4 | The REPL lets you interact with Elm values and functions in your terminal.
5 |
6 |
7 | ## Use
8 |
9 | You can type in expressions, definitions, custom types, and module imports using normal Elm syntax.
10 |
11 | ```elm
12 | > 1 + 1
13 | 2 : number
14 |
15 | > "hello" ++ "world"
16 | "helloworld" : String
17 | ```
18 |
19 | The same can be done with definitions and custom types:
20 |
21 | ```elm
22 | > fortyTwo = 42
23 | 42 : number
24 |
25 | > increment n = n + 1
26 | : number -> number
27 |
28 | > increment 41
29 | 42 : number
30 |
31 | > factorial n =
32 | | if n < 1 then
33 | | 1
34 | | else
35 | | n * factorial (n-1)
36 | |
37 | : number -> number
38 |
39 | > factorial 5
40 | 120 : number
41 |
42 | > type User
43 | | = Regular String
44 | | | Visitor String
45 | |
46 |
47 | > case Regular "Tom" of
48 | | Regular name -> "Hey again!"
49 | | Visitor name -> "Nice to meet you!"
50 | |
51 | "Hey again!" : String
52 | ```
53 |
54 | When you run `elm repl` in a project with an [`elm.json`](https://github.com/elm/compiler/blob/master/docs/elm.json/application.md) file, you can import any module available in the project. So if your project has an `elm/html` dependency, you could say:
55 |
56 | ```elm
57 | > import Html exposing (Html)
58 |
59 | > Html.text "hello"
60 | : Html msg
61 |
62 | > Html.text
63 | : String -> Html msg
64 | ```
65 |
66 | If you create a module in your project named `MyThing` in your project, you can say `import MyThing` in the REPL as well. Any module that is accessible in your project should be accessible in the REPL.
67 |
68 |
69 | ## Exit
70 |
71 | To exit the REPL, you can type `:exit`.
72 |
73 | You can also press `ctrl-d` or `ctrl-c` on some platforms.
74 |
--------------------------------------------------------------------------------
/compiler/src/AST/Utils/Shader.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE EmptyDataDecls #-}
3 | module AST.Utils.Shader
4 | ( Source
5 | , Types(..)
6 | , Type(..)
7 | , fromChars
8 | , toJsStringBuilder
9 | )
10 | where
11 |
12 |
13 | import Control.Monad (liftM)
14 | import Data.Binary (Binary, get, put)
15 | import qualified Data.ByteString as BS
16 | import qualified Data.ByteString.Builder as B
17 | import qualified Data.ByteString.UTF8 as BS_UTF8
18 | import qualified Data.Map as Map
19 | import qualified Data.Name as Name
20 |
21 |
22 |
23 | -- SOURCE
24 |
25 |
26 | newtype Source =
27 | Source BS.ByteString
28 |
29 |
30 |
31 | -- TYPES
32 |
33 |
34 | data Types =
35 | Types
36 | { _attribute :: Map.Map Name.Name Type
37 | , _uniform :: Map.Map Name.Name Type
38 | , _varying :: Map.Map Name.Name Type
39 | }
40 |
41 |
42 | data Type
43 | = Int
44 | | Float
45 | | V2
46 | | V3
47 | | V4
48 | | M4
49 | | Texture
50 |
51 |
52 |
53 | -- TO BUILDER
54 |
55 |
56 | toJsStringBuilder :: Source -> B.Builder
57 | toJsStringBuilder (Source src) =
58 | B.byteString src
59 |
60 |
61 |
62 | -- FROM CHARS
63 |
64 |
65 | fromChars :: [Char] -> Source
66 | fromChars chars =
67 | Source (BS_UTF8.fromString (escape chars))
68 |
69 |
70 | escape :: [Char] -> [Char]
71 | escape chars =
72 | case chars of
73 | [] ->
74 | []
75 |
76 | c:cs
77 | | c == '\r' -> escape cs
78 | | c == '\n' -> '\\' : 'n' : escape cs
79 | | c == '\"' -> '\\' : '"' : escape cs
80 | | c == '\'' -> '\\' : '\'' : escape cs
81 | | c == '\\' -> '\\' : '\\' : escape cs
82 | | otherwise -> c : escape cs
83 |
84 |
85 |
86 | -- BINARY
87 |
88 |
89 | instance Binary Source where
90 | get = liftM Source get
91 | put (Source a) = put a
92 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Elm
2 |
3 | Thanks helping with the development of Elm! This document describes the basic
4 | standards for opening pull requests and making the review process as smooth as
5 | possible.
6 |
7 | ## Licensing
8 |
9 | You need to sign the [contributor agreement](ContributorAgreement.pdf)
10 | and send it to before opening your pull request.
11 |
12 | ## Style Guide
13 |
14 | * Haskell — conform to [these guidelines][haskell]
15 | * JavaScript — use [Google's JS style guide][js]
16 |
17 | [haskell]: https://gist.github.com/evancz/0a1f3717c92fe71702be
18 | [js]: https://google.github.io/styleguide/javascriptguide.xml
19 |
20 | ## Branches
21 |
22 | * [The master branch][master] is the home of the next release of the compiler
23 | so new features and improvements get merged there. Most pull requests
24 | should target this branch!
25 |
26 | * [The stable branch][stable] is for tagging releases and critical bug fixes.
27 | This branch is handy for folks who want to build the most recent public
28 | release from source.
29 |
30 | [master]: http://github.com/elm-lang/elm/tree/master
31 | [stable]: http://github.com/elm-lang/elm/tree/stable
32 |
33 | If you are working on a fairly large feature, we will probably want to merge it
34 | in as its own branch and do some testing before bringing it into the master
35 | branch. This way we can keep releases of the master branch independent of new
36 | features.
37 |
38 | Note that the master branch of the compiler should always be in sync with the
39 | master branch of the [website][], and the stable branch of the compiler should
40 | always be in sync with the stable branch of the [website][]. Make sure that
41 | your changes maintain this compatibility.
42 |
43 | [website]: https://github.com/elm-lang/elm-lang.org
44 |
--------------------------------------------------------------------------------
/compiler/src/Elm/Compiler/Imports.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Elm.Compiler.Imports
4 | ( defaults
5 | )
6 | where
7 |
8 |
9 | import qualified Data.Name as Name
10 |
11 | import qualified AST.Source as Src
12 | import qualified Elm.ModuleName as ModuleName
13 | import qualified Reporting.Annotation as A
14 |
15 |
16 |
17 | -- DEFAULTS
18 |
19 |
20 | defaults :: [Src.Import]
21 | defaults =
22 | [ import_ ModuleName.basics Nothing Src.Open
23 | , import_ ModuleName.debug Nothing closed
24 | , import_ ModuleName.list Nothing (operator "::")
25 | , import_ ModuleName.maybe Nothing (typeOpen Name.maybe)
26 | , import_ ModuleName.result Nothing (typeOpen Name.result)
27 | , import_ ModuleName.string Nothing (typeClosed Name.string)
28 | , import_ ModuleName.char Nothing (typeClosed Name.char)
29 | , import_ ModuleName.tuple Nothing closed
30 | , import_ ModuleName.platform Nothing (typeClosed Name.program)
31 | , import_ ModuleName.cmd (Just Name.cmd) (typeClosed Name.cmd)
32 | , import_ ModuleName.sub (Just Name.sub) (typeClosed Name.sub)
33 | ]
34 |
35 |
36 | import_ :: ModuleName.Canonical -> Maybe Name.Name -> Src.Exposing -> Src.Import
37 | import_ (ModuleName.Canonical _ name) maybeAlias exposing =
38 | Src.Import (A.At A.zero name) maybeAlias exposing
39 |
40 |
41 |
42 | -- EXPOSING
43 |
44 |
45 | closed :: Src.Exposing
46 | closed =
47 | Src.Explicit []
48 |
49 |
50 | typeOpen :: Name.Name -> Src.Exposing
51 | typeOpen name =
52 | Src.Explicit [ Src.Upper (A.At A.zero name) (Src.Public A.zero) ]
53 |
54 |
55 | typeClosed :: Name.Name -> Src.Exposing
56 | typeClosed name =
57 | Src.Explicit [ Src.Upper (A.At A.zero name) Src.Private ]
58 |
59 |
60 | operator :: Name.Name -> Src.Exposing
61 | operator op =
62 | Src.Explicit [ Src.Operator A.zero op ]
63 |
--------------------------------------------------------------------------------
/compiler/src/Data/Bag.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Data.Bag
3 | ( Bag(..)
4 | , empty
5 | , one
6 | , append
7 | , map
8 | , toList
9 | , fromList
10 | )
11 | where
12 |
13 |
14 | import Prelude hiding (map)
15 | import qualified Data.List as List
16 |
17 |
18 |
19 | -- BAGS
20 |
21 |
22 | data Bag a
23 | = Empty
24 | | One a
25 | | Two (Bag a) (Bag a)
26 |
27 |
28 |
29 | -- HELPERS
30 |
31 |
32 | empty :: Bag a
33 | empty =
34 | Empty
35 |
36 |
37 | one :: a -> Bag a
38 | one =
39 | One
40 |
41 |
42 | append :: Bag a -> Bag a -> Bag a
43 | append left right =
44 | case (left, right) of
45 | (other, Empty) ->
46 | other
47 |
48 | (Empty, other) ->
49 | other
50 |
51 | (_, _) ->
52 | Two left right
53 |
54 |
55 |
56 | -- MAP
57 |
58 |
59 | map :: (a -> b) -> Bag a -> Bag b
60 | map func bag =
61 | case bag of
62 | Empty ->
63 | Empty
64 |
65 | One a ->
66 | One (func a)
67 |
68 | Two left right ->
69 | Two (map func left) (map func right)
70 |
71 |
72 |
73 | -- TO LIST
74 |
75 |
76 | toList :: Bag a -> [a]
77 | toList bag =
78 | toListHelp bag []
79 |
80 |
81 | toListHelp :: Bag a -> [a] -> [a]
82 | toListHelp bag list =
83 | case bag of
84 | Empty ->
85 | list
86 |
87 | One x ->
88 | x : list
89 |
90 | Two a b ->
91 | toListHelp a (toListHelp b list)
92 |
93 |
94 |
95 | -- FROM LIST
96 |
97 |
98 | fromList :: (a -> b) -> [a] -> Bag b
99 | fromList func list =
100 | case list of
101 | [] ->
102 | Empty
103 |
104 | first : rest ->
105 | List.foldl' (add func) (One (func first)) rest
106 |
107 |
108 | add :: (a -> b) -> Bag b -> a -> Bag b
109 | add func bag value =
110 | Two (One (func value)) bag
111 |
--------------------------------------------------------------------------------
/installers/mac/make-installer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Run the following command to create an installer:
3 | #
4 | # bash make-installer.sh
5 | #
6 |
7 |
8 |
9 | #### SETUP ####
10 |
11 | set -e
12 |
13 | # Create directory structure for new pkgs
14 | pkg_root=$(mktemp -d -t package-artifacts)
15 | pkg_binaries=$pkg_root
16 | pkg_scripts=$pkg_root/Scripts
17 |
18 | mkdir -p $pkg_binaries
19 | mkdir -p $pkg_scripts
20 |
21 | usr_binaries=/usr/local/bin
22 |
23 |
24 | #### BUILD ASSETS ####
25 |
26 | cp ../../dist/build/elm/elm $pkg_binaries/elm
27 |
28 | cp $(pwd)/preinstall $pkg_scripts
29 | cp $(pwd)/postinstall $pkg_scripts
30 |
31 | pkgbuild \
32 | --sign "Developer ID Installer: " \
33 | --identifier org.elm-lang.binary \
34 | --install-location $usr_binaries \
35 | --scripts $pkg_scripts \
36 | --filter 'Scripts.*' \
37 | --root $pkg_root \
38 | binaries.pkg
39 |
40 |
41 | #### BUNDLE ASSETS ####
42 |
43 | rm -f installer-for-mac.pkg
44 |
45 | productbuild \
46 | --sign "Developer ID Installer: " \
47 | --identifier org.elm-lang.installer \
48 | --distribution Distribution.xml \
49 | --package-path . \
50 | --resources Resources \
51 | installer-for-mac.pkg
52 |
53 |
54 | #### CLEAN UP ####
55 |
56 | rm binaries.pkg
57 | rm -rf $pkg_root
58 |
59 |
60 | #### BEGIN NOTARIZATION ####
61 |
62 | xcrun altool \
63 | --notarize-app \
64 | --primary-bundle-id "org.elm-lang.installer" \
65 | --username "" \
66 | --password "@keychain:Developer-altool" \
67 | --file "installer-for-mac.pkg"
68 |
69 | # From https://scriptingosx.com/2019/09/notarize-a-command-line-tool/
70 | #
71 | #### Check on notarization:
72 | #
73 | # xcrun altool \
74 | # --notarization-info "" \
75 | # --username "" \
76 | # --password "@keychain:Developer-altool"
77 | #
78 | #
79 | #### Staple Notarization:
80 | #
81 | # xcrun stapler staple installer-for-mac.pkg
82 |
--------------------------------------------------------------------------------
/compiler/src/Data/OneOrMore.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Data.OneOrMore
3 | ( OneOrMore(..)
4 | , one
5 | , more
6 | , map
7 | , destruct
8 | , getFirstTwo
9 | )
10 | where
11 |
12 |
13 | import Prelude hiding (map)
14 |
15 |
16 |
17 | -- ONE OR MORE
18 |
19 |
20 | data OneOrMore a
21 | = One a
22 | | More (OneOrMore a) (OneOrMore a)
23 |
24 |
25 | one :: a -> OneOrMore a
26 | one =
27 | One
28 |
29 |
30 | more :: OneOrMore a -> OneOrMore a -> OneOrMore a
31 | more =
32 | More
33 |
34 |
35 |
36 | -- MAP
37 |
38 |
39 | map :: (a -> b) -> OneOrMore a -> OneOrMore b
40 | map func oneOrMore =
41 | case oneOrMore of
42 | One value ->
43 | One (func value)
44 |
45 | More left right ->
46 | More (map func left) (map func right)
47 |
48 |
49 |
50 | -- DESTRUCT
51 |
52 |
53 | destruct :: (a -> [a] -> b) -> OneOrMore a -> b
54 | destruct func oneOrMore =
55 | destructLeft func oneOrMore []
56 |
57 |
58 | destructLeft :: (a -> [a] -> b) -> OneOrMore a -> [a] -> b
59 | destructLeft func oneOrMore xs =
60 | case oneOrMore of
61 | One x ->
62 | func x xs
63 |
64 | More a b ->
65 | destructLeft func a (destructRight b xs)
66 |
67 |
68 | destructRight :: OneOrMore a -> [a] -> [a]
69 | destructRight oneOrMore xs =
70 | case oneOrMore of
71 | One x ->
72 | x : xs
73 |
74 | More a b ->
75 | destructRight a (destructRight b xs)
76 |
77 |
78 |
79 | -- GET FIRST TWO
80 |
81 |
82 | getFirstTwo :: OneOrMore a -> OneOrMore a -> (a,a)
83 | getFirstTwo left right =
84 | case left of
85 | One x ->
86 | (x, getFirstOne right)
87 |
88 | More lleft lright ->
89 | getFirstTwo lleft lright
90 |
91 |
92 | getFirstOne :: OneOrMore a -> a
93 | getFirstOne oneOrMore =
94 | case oneOrMore of
95 | One x ->
96 | x
97 |
98 | More left _ ->
99 | getFirstOne left
100 |
--------------------------------------------------------------------------------
/compiler/src/Type/Instantiate.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Type.Instantiate
4 | ( FreeVars
5 | , fromSrcType
6 | )
7 | where
8 |
9 |
10 | import qualified Data.Map.Strict as Map
11 | import Data.Map.Strict ((!))
12 | import qualified Data.Name as Name
13 |
14 | import qualified AST.Canonical as Can
15 | import Type.Type
16 |
17 |
18 |
19 | -- FREE VARS
20 |
21 |
22 | type FreeVars =
23 | Map.Map Name.Name Type
24 |
25 |
26 |
27 | -- FROM SOURCE TYPE
28 |
29 |
30 | fromSrcType :: Map.Map Name.Name Type -> Can.Type -> IO Type
31 | fromSrcType freeVars sourceType =
32 | case sourceType of
33 | Can.TLambda arg result ->
34 | FunN
35 | <$> fromSrcType freeVars arg
36 | <*> fromSrcType freeVars result
37 |
38 | Can.TVar name ->
39 | return (freeVars ! name)
40 |
41 | Can.TType home name args ->
42 | AppN home name <$> traverse (fromSrcType freeVars) args
43 |
44 | Can.TAlias home name args aliasedType ->
45 | do targs <- traverse (traverse (fromSrcType freeVars)) args
46 | AliasN home name targs <$>
47 | case aliasedType of
48 | Can.Filled realType ->
49 | fromSrcType freeVars realType
50 |
51 | Can.Holey realType ->
52 | fromSrcType (Map.fromList targs) realType
53 |
54 | Can.TTuple a b maybeC ->
55 | TupleN
56 | <$> fromSrcType freeVars a
57 | <*> fromSrcType freeVars b
58 | <*> traverse (fromSrcType freeVars) maybeC
59 |
60 | Can.TUnit ->
61 | return UnitN
62 |
63 | Can.TRecord fields maybeExt ->
64 | RecordN
65 | <$> traverse (fromSrcFieldType freeVars) fields
66 | <*>
67 | case maybeExt of
68 | Nothing ->
69 | return EmptyRecordN
70 |
71 | Just ext ->
72 | return (freeVars ! ext)
73 |
74 |
75 | fromSrcFieldType :: Map.Map Name.Name Type -> Can.FieldType -> IO Type
76 | fromSrcFieldType freeVars (Can.FieldType _ tipe) =
77 | fromSrcType freeVars tipe
78 |
--------------------------------------------------------------------------------
/terminal/impl/Terminal/Internal.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE GADTs #-}
2 | module Terminal.Internal
3 | ( Command(..)
4 | , toName
5 | , Summary(..)
6 | , Flags(..)
7 | , Flag(..)
8 | , Parser(..)
9 | , Args(..)
10 | , CompleteArgs(..)
11 | , RequiredArgs(..)
12 | )
13 | where
14 |
15 |
16 | import Text.PrettyPrint.ANSI.Leijen (Doc)
17 |
18 |
19 |
20 | -- COMMAND
21 |
22 |
23 | data Command where
24 | Command
25 | :: String
26 | -> Summary
27 | -> String
28 | -> Doc
29 | -> Args args
30 | -> Flags flags
31 | -> (args -> flags -> IO ())
32 | -> Command
33 |
34 |
35 | toName :: Command -> String
36 | toName (Command name _ _ _ _ _ _) =
37 | name
38 |
39 |
40 |
41 | {-| The information that shows when you run the executable with no arguments.
42 | If you say it is `Common`, you need to tell people what it does. Try to keep
43 | it to two or three lines. If you say it is `Uncommon` you can rely on `Details`
44 | for a more complete explanation.
45 | -}
46 | data Summary = Common String | Uncommon
47 |
48 |
49 |
50 | -- FLAGS
51 |
52 |
53 | data Flags a where
54 | FDone :: a -> Flags a
55 | FMore :: Flags (a -> b) -> Flag a -> Flags b
56 |
57 |
58 | data Flag a where
59 | Flag :: String -> Parser a -> String -> Flag (Maybe a)
60 | OnOff :: String -> String -> Flag Bool
61 |
62 |
63 |
64 | -- PARSERS
65 |
66 |
67 | data Parser a =
68 | Parser
69 | { _singular :: String
70 | , _plural :: String
71 | , _parser :: String -> Maybe a
72 | , _suggest :: String -> IO [String]
73 | , _examples :: String -> IO [String]
74 | }
75 |
76 |
77 |
78 | -- ARGS
79 |
80 |
81 | newtype Args a =
82 | Args [CompleteArgs a]
83 |
84 |
85 | data CompleteArgs args where
86 | Exactly :: RequiredArgs args -> CompleteArgs args
87 | Multiple :: RequiredArgs ([a] -> args) -> Parser a -> CompleteArgs args
88 | Optional :: RequiredArgs (Maybe a -> args) -> Parser a -> CompleteArgs args
89 |
90 |
91 | data RequiredArgs a where
92 | Done :: a -> RequiredArgs a
93 | Required :: RequiredArgs (a -> b) -> Parser a -> RequiredArgs b
94 |
--------------------------------------------------------------------------------
/installers/npm/bin/elm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var child_process = require('child_process');
4 | var path = require('path');
5 | var fs = require('fs');
6 |
7 |
8 | // Some npm users enable --ignore-scripts (a good security measure) so
9 | // they do not run the post-install hook and install.js does not run.
10 | // Instead they will run this script.
11 | //
12 | // On Mac and Linux, we download the elm executable into the exact same
13 | // location as this file. Since npm uses symlinks on these platforms,
14 | // that means that the first run will invoke this file and subsequent
15 | // runs will call the elm binary directly.
16 | //
17 | // On Windows, we must download a file named elm.exe for it to run properly.
18 | // Instead of symlinks, npm creates two files:
19 | //
20 | // - node_modules/.bin/elm (a bash file)
21 | // - node_modules/.bin/elm.cmd (a batch file)
22 | //
23 | // Both files specifically invoke `node` to run the file listed at package.bin,
24 | // so there is no way around instantiating node for no reason on Windows. So
25 | // the existsSync check is needed so that it is not downloaded more than once.
26 |
27 |
28 | // figure out where to put the binary (calls path.resolve() to get path separators right on Windows)
29 | //
30 | var binaryPath = path.resolve(__dirname, 'elm') + (process.platform === 'win32' ? '.exe' : '');
31 |
32 | // Run the command directly if possible, otherwise download and then run.
33 | // This check is important for Windows where this file will be run all the time.
34 | //
35 | if (process.platform === 'win32')
36 | {
37 | fs.existsSync(binaryPath)
38 | ? runCommand()
39 | : require('../download.js')(runCommand);
40 | }
41 | else
42 | {
43 | require('../download.js')(runCommand);
44 | }
45 |
46 |
47 | function runCommand()
48 | {
49 | // Need double quotes and { shell: true } when there are spaces in the path on windows:
50 | // https://github.com/nodejs/node/issues/7367#issuecomment-229721296
51 | child_process
52 | .spawn('"' + binaryPath + '"', process.argv.slice(2), { stdio: 'inherit', shell: true })
53 | .on('exit', process.exit);
54 | }
55 |
--------------------------------------------------------------------------------
/installers/npm/README.md:
--------------------------------------------------------------------------------
1 | # Elm Installer
2 |
3 | [Elm](https://elm-lang.org) is a functional programming language that compiles to JavaScript.
4 |
5 | Head over to [The Official Guide](https://guide.elm-lang.org/) to start learning Elm!
6 |
7 |
8 |
9 |
10 | ## What is this package for?
11 |
12 | **Normal installs** ❌
13 |
14 | Use the instructions [here](https://guide.elm-lang.org/install/elm.html) instead.
15 |
16 | **Multiple versions** ✅
17 |
18 | People using Elm at work may use different versions of Elm in different projects. They can run `npm install elm@latest-0.19.1` in each project and use the binary at `./node_modules/.bin/elm` for compilation.
19 |
20 | **Continuous integration** ⚠️
21 |
22 | This works, but there are usually faster and more reliable options:
23 |
24 | 1. You can download `elm` directly from GitHub with [this script](https://github.com/elm/compiler/blob/master/installers/linux/README.md). This is all the `npm` installer does, but with extra HTTP requests to `npmjs.com` servers, making it slower and adding more failure points.
25 | 2. Many continuous integration have ways to cache files ([example](https://docs.travis-ci.com/user/caching/)) to make builds faster and more reliable.
26 |
27 | That said, it will definitely work to use the `npm` installer on CI if you prefer that option.
28 |
29 |
30 |
31 |
32 | ## Install Locally
33 |
34 | The following command should download the latest Elm 0.19.1 binary:
35 |
36 | ```
37 | npm install elm@latest-0.19.1
38 | ```
39 |
40 | You should be able to run `./node_modules/bin/elm --version` within your project and see `0.19.1`. Now you can compile with `./node_modules/bin/elm make src/Main.elm` and not disrupt other packages.
41 |
42 | Use `npm install elm@latest-0.19.0` or `npm install elm@latest-0.18.0` for earlier versions.
43 |
44 | **Note:** The `latest-X.Y.Z` convention is used in case we need to publish patches for the `npm` installer within a given Elm release. For example, say `npm` decides that some transitive dependency is not secure. Nothing is changing about Elm or the binaries, but we need to publish a new `npm` installer that fixes this issue.
45 |
46 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Annotation.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Reporting.Annotation
3 | ( Located(..)
4 | , Position(..)
5 | , Region(..)
6 | , traverse
7 | , toValue
8 | , merge
9 | , at
10 | , toRegion
11 | , mergeRegions
12 | , zero
13 | , one
14 | )
15 | where
16 |
17 |
18 | import Prelude hiding (traverse)
19 | import Control.Monad (liftM2)
20 | import Data.Binary (Binary, get, put)
21 | import Data.Word (Word16)
22 |
23 |
24 |
25 | -- LOCATED
26 |
27 |
28 | data Located a =
29 | At Region a -- PERF see if unpacking region is helpful
30 |
31 |
32 | instance Functor Located where
33 | fmap f (At region a) =
34 | At region (f a)
35 |
36 |
37 | traverse :: (Functor f) => (a -> f b) -> Located a -> f (Located b)
38 | traverse func (At region value) =
39 | At region <$> func value
40 |
41 |
42 | toValue :: Located a -> a
43 | toValue (At _ value) =
44 | value
45 |
46 |
47 | merge :: Located a -> Located b -> value -> Located value
48 | merge (At r1 _) (At r2 _) value =
49 | At (mergeRegions r1 r2) value
50 |
51 |
52 |
53 | -- POSITION
54 |
55 |
56 | data Position =
57 | Position
58 | {-# UNPACK #-} !Word16
59 | {-# UNPACK #-} !Word16
60 | deriving (Eq)
61 |
62 |
63 | at :: Position -> Position -> a -> Located a
64 | at start end a =
65 | At (Region start end) a
66 |
67 |
68 |
69 | -- REGION
70 |
71 |
72 | data Region = Region Position Position
73 | deriving (Eq)
74 |
75 |
76 | toRegion :: Located a -> Region
77 | toRegion (At region _) =
78 | region
79 |
80 |
81 | mergeRegions :: Region -> Region -> Region
82 | mergeRegions (Region start _) (Region _ end) =
83 | Region start end
84 |
85 |
86 | zero :: Region
87 | zero =
88 | Region (Position 0 0) (Position 0 0)
89 |
90 |
91 | one :: Region
92 | one =
93 | Region (Position 1 1) (Position 1 1)
94 |
95 |
96 | instance Binary Region where
97 | put (Region a b) = put a >> put b
98 | get = liftM2 Region get get
99 |
100 |
101 | instance Binary Position where
102 | put (Position a b) = put a >> put b
103 | get = liftM2 Position get get
104 |
--------------------------------------------------------------------------------
/compiler/src/Parse/Symbol.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE BangPatterns, OverloadedStrings #-}
3 | module Parse.Symbol
4 | ( operator
5 | , BadOperator(..)
6 | , binopCharSet
7 | )
8 | where
9 |
10 |
11 | import qualified Data.Char as Char
12 | import qualified Data.IntSet as IntSet
13 | import qualified Data.Name as Name
14 | import qualified Data.Vector as Vector
15 | import Foreign.Ptr (Ptr, plusPtr, minusPtr)
16 | import GHC.Word (Word8)
17 |
18 | import Parse.Primitives (Parser, Row, Col)
19 | import qualified Parse.Primitives as P
20 |
21 |
22 |
23 | -- OPERATOR
24 |
25 |
26 | data BadOperator
27 | = BadDot
28 | | BadPipe
29 | | BadArrow
30 | | BadEquals
31 | | BadHasType
32 |
33 |
34 | operator :: (Row -> Col -> x) -> (BadOperator -> Row -> Col -> x) -> Parser x Name.Name
35 | operator toExpectation toError =
36 | P.Parser $ \(P.State src pos end indent row col) cok _ cerr eerr ->
37 | let !newPos = chompOps pos end in
38 | if pos == newPos then
39 | eerr row col toExpectation
40 |
41 | else
42 | case Name.fromPtr pos newPos of
43 | "." -> eerr row col (toError BadDot)
44 | "|" -> cerr row col (toError BadPipe)
45 | "->" -> cerr row col (toError BadArrow)
46 | "=" -> cerr row col (toError BadEquals)
47 | ":" -> cerr row col (toError BadHasType)
48 | op ->
49 | let
50 | !newCol = col + fromIntegral (minusPtr newPos pos)
51 | !newState = P.State src newPos end indent row newCol
52 | in
53 | cok op newState
54 |
55 |
56 | chompOps :: Ptr Word8 -> Ptr Word8 -> Ptr Word8
57 | chompOps pos end =
58 | if pos < end && isBinopCharHelp (P.unsafeIndex pos) then
59 | chompOps (plusPtr pos 1) end
60 | else
61 | pos
62 |
63 |
64 | {-# INLINE isBinopCharHelp #-}
65 | isBinopCharHelp :: Word8 -> Bool
66 | isBinopCharHelp word =
67 | word < 128 && Vector.unsafeIndex binopCharVector (fromIntegral word)
68 |
69 |
70 | {-# NOINLINE binopCharVector #-}
71 | binopCharVector :: Vector.Vector Bool
72 | binopCharVector =
73 | Vector.generate 128 (\i -> IntSet.member i binopCharSet)
74 |
75 |
76 | {-# NOINLINE binopCharSet #-}
77 | binopCharSet :: IntSet.IntSet
78 | binopCharSet =
79 | IntSet.fromList (map Char.ord "+-/*=.<>:&|^?%!")
80 |
--------------------------------------------------------------------------------
/compiler/src/Type/Occurs.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Type.Occurs
4 | ( occurs
5 | )
6 | where
7 |
8 |
9 | import Data.Foldable (foldrM)
10 | import qualified Data.Map.Strict as Map
11 |
12 | import Type.Type as Type
13 | import qualified Type.UnionFind as UF
14 |
15 |
16 |
17 | -- OCCURS
18 |
19 |
20 | occurs :: Type.Variable -> IO Bool
21 | occurs var =
22 | occursHelp [] var False
23 |
24 |
25 | occursHelp :: [Type.Variable] -> Type.Variable -> Bool -> IO Bool
26 | occursHelp seen var foundCycle =
27 | if elem var seen then
28 | return True
29 |
30 | else
31 | do (Descriptor content _ _ _) <- UF.get var
32 | case content of
33 | FlexVar _ ->
34 | return foundCycle
35 |
36 | FlexSuper _ _ ->
37 | return foundCycle
38 |
39 | RigidVar _ ->
40 | return foundCycle
41 |
42 | RigidSuper _ _ ->
43 | return foundCycle
44 |
45 | Structure term ->
46 | let newSeen = var : seen in
47 | case term of
48 | App1 _ _ args ->
49 | foldrM (occursHelp newSeen) foundCycle args
50 |
51 | Fun1 a b ->
52 | occursHelp newSeen a =<<
53 | occursHelp newSeen b foundCycle
54 |
55 | EmptyRecord1 ->
56 | return foundCycle
57 |
58 | Record1 fields ext ->
59 | occursHelp newSeen ext =<<
60 | foldrM (occursHelp newSeen) foundCycle (Map.elems fields)
61 |
62 | Unit1 ->
63 | return foundCycle
64 |
65 | Tuple1 a b maybeC ->
66 | case maybeC of
67 | Nothing ->
68 | occursHelp newSeen a =<<
69 | occursHelp newSeen b foundCycle
70 |
71 | Just c ->
72 | occursHelp newSeen a =<<
73 | occursHelp newSeen b =<<
74 | occursHelp newSeen c foundCycle
75 |
76 | Alias _ _ args _ ->
77 | foldrM (occursHelp (var:seen)) foundCycle (map snd args)
78 |
79 | Error ->
80 | return foundCycle
81 |
--------------------------------------------------------------------------------
/docs/elm.json/application.md:
--------------------------------------------------------------------------------
1 | # `elm.json` for applications
2 |
3 | This is a decent baseline for pretty much any applications made with Elm. You will need these dependencies or more.
4 |
5 | ```json
6 | {
7 | "type": "application",
8 | "source-directories": [
9 | "src"
10 | ],
11 | "elm-version": "0.19.1",
12 | "dependencies": {
13 | "direct": {
14 | "elm/browser": "1.0.0",
15 | "elm/core": "1.0.0",
16 | "elm/html": "1.0.0",
17 | "elm/json": "1.0.0"
18 | },
19 | "indirect": {
20 | "elm/time": "1.0.0",
21 | "elm/url": "1.0.0",
22 | "elm/virtual-dom": "1.0.0"
23 | }
24 | },
25 | "test-dependencies": {
26 | "direct": {},
27 | "indirect": {}
28 | }
29 | }
30 | ```
31 |
32 |
33 |
34 |
35 | ## `"type"`
36 |
37 | Either `"application"` or `"package"`. All the other fields are based on this choice!
38 |
39 |
40 |
41 |
42 | ## `"source-directories"`
43 |
44 | A list of directories where Elm code lives. Most projects just use `"src"` for everything.
45 |
46 |
47 |
48 |
49 | ## `"elm-version"`
50 |
51 | The exact version of Elm this builds with. Should be `"0.19.1"` for most people!
52 |
53 |
54 |
55 |
56 | ## `"dependencies"`
57 |
58 | All the packages you depend upon. We use exact versions, so your `elm.json` file doubles as a "lock file" that ensures reliable builds.
59 |
60 | You can use modules from any `"direct"` dependency in your code. Some `"direct"` dependencies have their own dependencies that folks typically do not care about. These are the `"indirect"` dependencies. They are listed explicitly so that (1) builds are reproducible and (2) you can easily review the quantity and quality of dependencies.
61 |
62 | **Note:** We plan to eventually have a screen in `reactor` that helps add, remove, and upgrade packages. It can sometimes be tricky to keep all of the constraints happy, so we think having a UI will help a lot. If you get into trouble in the meantime, adding things back one-by-one often helps, and I hope you do not get into trouble!
63 |
64 |
65 |
66 |
67 | ## `"test-dependencies"`
68 |
69 | All the packages that you use in `tests/` with `elm-test` but not in the application you actually want to ship. This also uses exact versions to make tests more reliable.
70 |
--------------------------------------------------------------------------------
/hints/infinite-type.md:
--------------------------------------------------------------------------------
1 |
2 | # Hints for Infinite Types
3 |
4 | Infinite types are probably the trickiest kind of bugs to track down. **Writing down type annotations is usually the fastest way to figure them out.** Let's work through an example to get a feel for how these errors usually work though!
5 |
6 |
7 | ## Example
8 |
9 | A common way to get an infinite type error is very small typos. For example, do you see the problem in the following code?
10 |
11 | ```elm
12 | incrementNumbers list =
13 | List.map incrementNumbers list
14 |
15 | incrementNumber n =
16 | n + 1
17 | ```
18 |
19 | The issue is that `incrementNumbers` calls itself, not the `incrementNumber` function defined below. So there is an extra `s` in this program! Let's focus on that:
20 |
21 | ```elm
22 | incrementNumbers list =
23 | List.map incrementNumbers list -- BUG extra `s` makes this self-recursive
24 | ```
25 |
26 | Now the compiler does not know that anything is wrong yet. It just tries to figure out the types like normal. It knows that `incrementNumbers` is a function. The definition uses `List.map` so we can deduce that `list : List t1` and the result of this function call should be some other `List t2`. This also means that `incrementNumbers : List t1 -> List t2`.
27 |
28 | The issue is that `List.map` uses `incrementNumbers` on `list`! That means that each element of `list` (which has type `t1`) must be fed into `incrementNumbers` (which takes `List t1`)
29 |
30 | That means that `t1 = List t1`, which is an infinite type! If we start expanding this, we get `List (List (List (List (List ...))))` out to infinity!
31 |
32 | The point is mainly that we are in a confusing situation. The types are confusing. This explanation is confusing. The compiler is confused. It is a bad time. But luckily, the more type annotations you add, the better chance there is that you and the compiler can figure things out! So say we change our definition to:
33 |
34 | ```elm
35 | incrementNumbers : List Int -> List Int
36 | incrementNumbers list =
37 | List.map incrementNumbers list -- STILL HAS BUG
38 | ```
39 |
40 | Now we are going to get a pretty normal type error. Hey, you said that each element in the `list` is an `Int` but I cannot feed that into a `List Int -> List Int` function! Something like that.
41 |
42 | In summary, the root issue is often some small typo, and the best way out is to start adding type annotations on everything!
43 |
--------------------------------------------------------------------------------
/docs/upgrade-instructions/0.19.1.md:
--------------------------------------------------------------------------------
1 | # Upgrading to 0.19.1
2 |
3 | **There are no language changes**, so once you swap to `"elm-version": "0.19.1"` in your `elm.json`, most users should be able to proceed without any further code changes. **You may run into a handful of bugfixes though!** These cases are outlined below!
4 |
5 |
6 |
7 |
8 | ## Improvements
9 |
10 | - Parse error message quality (like [this](https://github.com/elm/error-message-catalog/issues/255) and [this](https://github.com/elm/error-message-catalog/issues/225))
11 | - Faster compilation, especially for incremental compiles
12 | - Uses filelocks so that cached files are not corrupted when plugins run `elm make` multiple times on the same project at the same time. (Still worth avoiding that though!)
13 | - More intuitive multiline declarations in REPL
14 | - Various bug fixes (e.g. `--debug`, `x /= 0`, `type Height = Height Float` in `--optimize`)
15 |
16 |
17 |
18 |
19 | ## Detectable Bug Fixes
20 |
21 | There are three known cases where code that compiled with 0.19.0 will not compile with 0.19.1 due to bug fixes:
22 |
23 |
24 | ### 1. Ambiguous Imports
25 |
26 | Say you have an import like this:
27 |
28 | ```elm
29 | import Html exposing (min)
30 | import Regex exposing (never)
31 |
32 | x = min
33 | y = never
34 | ```
35 |
36 | These should be reported as ambiguous usages since the names are also exposed by `Basics`, but there was a regression in 0.19.0 described [here](https://github.com/elm/compiler/issues/1945) such that they weren't caught in specific circumstances.
37 |
38 | The fix is to use a qualified name like `Html.min` or `Regex.never` to make it unambiguous.
39 |
40 | We found a couple instances of this in packages and have submitted PRs to the relevant authors in August 2019. You may run into this in your own code as well.
41 |
42 | For more details on why this is considered a regression, check out the details [here](https://github.com/elm/compiler/issues/1945#issuecomment-507871919) or try it in 0.18.0 to see how it worked before.
43 |
44 |
45 | ### 2. Tabs in Comments
46 |
47 | The 0.19.0 binaries did not catch tab characters in comments. The new parser is better at checking for tabs, so it will object when it finds these.
48 |
49 | Again, we found this in some packages and reached out to the relevant authors with PRs so patches would be published before the 0.19.1 release.
50 |
51 |
52 | ### 3. Port Module with no Ports
53 |
54 | If you have any files that start with:
55 |
56 | ```elm
57 | port module Main exposing (..)
58 | ```
59 |
60 | But they do not actually have any `port` declarations, the 0.19.1 binary will ask you to switch to a normal module declaration like `module Main exposing (..)`
61 |
--------------------------------------------------------------------------------
/worker/src/Main.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Main
4 | ( main
5 | )
6 | where
7 |
8 |
9 | import Control.Monad (msum)
10 | import qualified Data.ByteString as BS
11 | import Snap.Core
12 | import Snap.Http.Server
13 | import qualified System.Environment as Env
14 | import qualified System.Exit as Exit
15 | import qualified System.IO as IO
16 |
17 | import qualified Artifacts
18 | import qualified Cors
19 | import qualified Endpoint.Compile as Compile
20 | import qualified Endpoint.Donate as Donate
21 | import qualified Endpoint.Repl as Repl
22 |
23 |
24 |
25 | -- RUN THE DEV SERVER
26 |
27 |
28 | main :: IO ()
29 | main =
30 | do rArtifacts <- Artifacts.loadRepl
31 | cArtifacts <- Artifacts.loadCompile
32 | errorJS <- Compile.loadErrorJS
33 | manager <- Donate.getManager =<< getSecret
34 | let depsInfo = Artifacts.toDepsInfo cArtifacts
35 |
36 | httpServe config $ msum $
37 | [ ifTop $ status
38 | , path "repl" $ Repl.endpoint rArtifacts
39 | , path "compile" $ Compile.endpoint cArtifacts
40 | , path "compile/errors.js" $ serveJavaScript errorJS
41 | , path "compile/deps-info.json" $ serveDepsInfo depsInfo
42 | , path "donate" $ Donate.endpoint manager
43 | , notFound
44 | ]
45 |
46 |
47 | config :: Config Snap a
48 | config =
49 | setPort 8000 $ setAccessLog ConfigNoLog $ setErrorLog ConfigNoLog $ defaultConfig
50 |
51 |
52 | status :: Snap ()
53 | status =
54 | do modifyResponse $ setContentType "text/plain"
55 | writeBuilder "Status: OK"
56 |
57 |
58 | notFound :: Snap ()
59 | notFound =
60 | do modifyResponse $ setResponseStatus 404 "Not Found"
61 | modifyResponse $ setContentType "text/html; charset=utf-8"
62 | writeBuilder "Not Found"
63 |
64 |
65 | serveJavaScript :: BS.ByteString -> Snap ()
66 | serveJavaScript javascript =
67 | do modifyResponse $ setContentType "application/javascript"
68 | writeBS javascript
69 |
70 |
71 | serveDepsInfo :: BS.ByteString -> Snap ()
72 | serveDepsInfo json =
73 | Cors.allow GET ["https://elm-lang.org"] $
74 | do modifyResponse $ setContentType "application/json"
75 | writeBS json
76 |
77 |
78 |
79 | -- GET SECRET
80 |
81 |
82 | getSecret :: IO String
83 | getSecret =
84 | do maybeValue <- Env.lookupEnv "STRIPE_SECRET"
85 | case maybeValue of
86 | Just secret ->
87 | return secret
88 |
89 | Nothing ->
90 | do IO.hPutStrLn IO.stderr
91 | "Expecting environment variable STRIPE_SECRET to be defined:\n\
92 | \\n\
93 | \ STRIPE_SECRET=sk_test_abcdefghijklmnopqrstuvwxyz\n\
94 | \\n\
95 | \It is needed for handling donations with Stripe."
96 | Exit.exitFailure
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Elm To Node
2 |
3 | Use the pure functions defined in your [elm](http://elm-lang.or/) code in your Node.js server in a synchronous way!
4 |
5 |
6 |
7 | ## Install
8 |
9 | Only available on Linux right now.
10 |
11 | ✨ [Install on linux](https://github.com/sebsheep/elm2node/tree/0.1.0-alpha-2/installers/linux) ✨
12 |
13 |
14 |
15 | ## Basic Usage
16 |
17 | Say you have some wonderful elm code, computing sums and storing specific data:
18 |
19 | ```elm
20 | -- src/Main.elm
21 | module Main exposing (answer, sum)
22 |
23 | answer : Int
24 | answer = 42
25 |
26 | sum : { a: Float, b: Float} -> Float
27 | sum data =
28 | data.a + data.b
29 | ```
30 |
31 | You just have to run:
32 | ```sh
33 | elm2node src/Main.elm
34 | ```
35 |
36 | This will produce an `elm.js` file which is a valid Node.js module. Try it out in
37 | the Node.js repl:
38 |
39 | ```sh
40 | $ node
41 | > myModel = require('./elm.js')
42 | { answer: 42, sum: [Function: sum] }
43 | > myModel.answer
44 | 42
45 | > myModel.sum({a: 5, b: 7})
46 | 12
47 | ```
48 |
49 |
50 |
51 |
52 | ## Restrictions
53 |
54 | The exposed values do have some restrictions:
55 | * only "static" values or functions with one argument can be exposed
56 | * the only accepted types in exposed values are:
57 | `Int`, `Float`, `String`, `Maybe`, `Records`, `List`, `Array`, `Json.Encode.Value`
58 | * exposed user defined types are silently ignored.
59 |
60 |
61 |
62 | ## FAQ
63 |
64 | ### Can I export to Node.js a elm function taking multiple arguments?
65 | Short answer: no.
66 |
67 | More useful answer: you can simulate the mutiple arguments using an "JS object/elm record"
68 | as argument. For example, if you want your function sum two floats `a` and `b`, you can
69 | define:
70 | ```elm
71 | sum : { a: Float, b: Float} -> Float
72 | sum data =
73 | data.a + data.b
74 | ```
75 | In the Node.js server side, you'd have to write something like:
76 | ```js
77 | myModule.calc({a: 5, b: 7})
78 | ```
79 |
80 | However, as recommended in the [elm documentation](https://guide.elm-lang.org/interop/ports.html#notes),
81 | consider using a `Json.Encode.Value` as type and use a custom decoder to handle errors.
82 |
83 | ### I've tried to export `answer = 42` and it failed!
84 | If you omit the type annotation in the top level `answer = 42` declaration,
85 | the type of `answer` will be inferred as `number` which is not an exportable type.
86 |
87 | Add a type annotation like (it is a good habit to have, anyway!):
88 | ```elm
89 | answer : Int
90 | answer = 42
91 | ```
92 |
93 | ### Do I have the guarantee my elm code will be compiled the same way than with `elm make`?
94 | Yes! This tool was built from the official elm 0.19.1 compiler, just modifying how the compiler deal
95 | with exposed functions for the files given as arguments and removing useless stuff for our purpose
96 | (reactor, publishing packages...).
97 |
--------------------------------------------------------------------------------
/compiler/src/Compile.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}
2 | module Compile
3 | ( Artifacts(..)
4 | , compile
5 | )
6 | where
7 |
8 |
9 | import qualified Data.Map as Map
10 | import qualified Data.Name as Name
11 |
12 | import qualified AST.Source as Src
13 | import qualified AST.Canonical as Can
14 | import qualified AST.Optimized as Opt
15 | import qualified Canonicalize.Module as Canonicalize
16 | import qualified Elm.Interface as I
17 | import qualified Elm.ModuleName as ModuleName
18 | import qualified Elm.Package as Pkg
19 | import qualified Nitpick.PatternMatches as PatternMatches
20 | import qualified Optimize.Module as Optimize
21 | import qualified Reporting.Error as E
22 | import qualified Reporting.Result as R
23 | import qualified Reporting.Render.Type.Localizer as Localizer
24 | import qualified Type.Constrain.Module as Type
25 | import qualified Type.Solve as Type
26 |
27 | import System.IO.Unsafe (unsafePerformIO)
28 |
29 |
30 |
31 | -- COMPILE
32 |
33 |
34 | data Artifacts =
35 | Artifacts
36 | { _modul :: Can.Module
37 | , _types :: Map.Map Name.Name Can.Annotation
38 | , _graph :: Opt.LocalGraph
39 | }
40 |
41 |
42 | compile :: Pkg.Name -> Map.Map ModuleName.Raw I.Interface -> Src.Module -> Either E.Error Artifacts
43 | compile pkg ifaces modul =
44 | do canonical <- canonicalize pkg ifaces modul
45 | annotations <- typeCheck modul canonical
46 | () <- nitpick canonical
47 | objects <- optimize modul annotations canonical
48 | return (Artifacts canonical annotations objects)
49 |
50 |
51 |
52 | -- PHASES
53 |
54 |
55 | canonicalize :: Pkg.Name -> Map.Map ModuleName.Raw I.Interface -> Src.Module -> Either E.Error Can.Module
56 | canonicalize pkg ifaces modul =
57 | case snd $ R.run $ Canonicalize.canonicalize pkg ifaces modul of
58 | Right canonical ->
59 | Right canonical
60 |
61 | Left errors ->
62 | Left $ E.BadNames errors
63 |
64 |
65 | typeCheck :: Src.Module -> Can.Module -> Either E.Error (Map.Map Name.Name Can.Annotation)
66 | typeCheck modul canonical =
67 | case unsafePerformIO (Type.run =<< Type.constrain canonical) of
68 | Right annotations ->
69 | Right annotations
70 |
71 | Left errors ->
72 | Left (E.BadTypes (Localizer.fromModule modul) errors)
73 |
74 |
75 | nitpick :: Can.Module -> Either E.Error ()
76 | nitpick canonical =
77 | case PatternMatches.check canonical of
78 | Right () ->
79 | Right ()
80 |
81 | Left errors ->
82 | Left (E.BadPatterns errors)
83 |
84 |
85 | optimize :: Src.Module -> Map.Map Name.Name Can.Annotation -> Can.Module -> Either E.Error Opt.LocalGraph
86 | optimize modul annotations canonical =
87 | case snd $ R.run $ Optimize.optimize annotations canonical of
88 | Right localGraph ->
89 | Right localGraph
90 |
91 | Left errors ->
92 | Left (E.BadMains (Localizer.fromModule modul) errors)
93 |
--------------------------------------------------------------------------------
/hints/type-annotations.md:
--------------------------------------------------------------------------------
1 |
2 | # Hints for Type Annotation Problems
3 |
4 | At the root of this kind of issue is always the fact that a type annotation in your code does not match the corresponding definition. Now that may mean that the type annotation is "wrong" or it may mean that the definition is "wrong". The compiler cannot figure out your intent, only that there is some mismatch.
5 |
6 | This document is going to outline the various things that can go wrong and show some examples.
7 |
8 |
9 | ## Annotation vs. Definition
10 |
11 | The most common issue is with user-defined type variables that are too general. So let's say you have defined a function like this:
12 |
13 | ```elm
14 | addPair : (a, a) -> a
15 | addPair (x, y) =
16 | x + y
17 | ```
18 |
19 | The issue is that the type annotation is saying "I will accept a tuple containing literally *anything*" but the definition is using `(+)` which requires things to be numbers. So the compiler is going to infer that the true type of the definition is this:
20 |
21 | ```elm
22 | addPair : (number, number) -> number
23 | ```
24 |
25 | So you will probably see an error saying "I cannot match `a` with `number`" which is essentially saying, you are trying to provide a type annotation that is **too general**. You are saying `addPair` accepts anything, but in fact, it can only handle numbers.
26 |
27 | In cases like this, you want to go with whatever the compiler inferred. It is good at figuring this kind of stuff out ;)
28 |
29 |
30 | ## Annotation vs. Itself
31 |
32 | It is also possible to have a type annotation that clashes with itself. This is probably more rare, but someone will run into it eventually. Let's use another version of `addPair` with problems:
33 |
34 | ```elm
35 | addPair : (Int, Int) -> number
36 | addPair (x, y) =
37 | x + y
38 | ```
39 |
40 | In this case the annotation says we should get a `number` out, but because we were specific about the inputs being `Int`, the output should also be an `Int`.
41 |
42 |
43 | ## Annotation vs. Internal Annotation
44 |
45 | A quite tricky case is when an outer type annotation clashes with an inner type annotation. Here is an example of this:
46 |
47 | ```elm
48 | filter : (a -> Bool) -> List a -> List a
49 | filter isOkay list =
50 | let
51 | keepIfOkay : a -> Maybe a
52 | keepIfOkay x =
53 | if isOkay x then Just x else Nothing
54 | in
55 | List.filterMap keepIfOkay list
56 | ```
57 |
58 | This case is very unfortunate because all the type annotations are correct, but there is a detail of how type inference works right now that **user-defined type variables are not shared between annotations**. This can lead to probably the worst type error messages we have because the problem here is that `a` in the outer annotation does not equal `a` in the inner annotation.
59 |
60 | For now the best route is to leave off the inner annotation. It is unfortunate, and hopefully we will be able to do a nicer thing in future releases.
61 |
--------------------------------------------------------------------------------
/compiler/src/Data/Index.hs:
--------------------------------------------------------------------------------
1 | module Data.Index
2 | ( ZeroBased
3 | , first
4 | , second
5 | , third
6 | , next
7 | , toMachine
8 | , toHuman
9 | , indexedMap
10 | , indexedTraverse
11 | , indexedForA
12 | , VerifiedList(..)
13 | , indexedZipWith
14 | , indexedZipWithA
15 | )
16 | where
17 |
18 |
19 | import Control.Monad (liftM)
20 | import Data.Binary
21 |
22 |
23 |
24 | -- ZERO BASED
25 |
26 |
27 | newtype ZeroBased = ZeroBased Int
28 | deriving (Eq, Ord)
29 |
30 |
31 | first :: ZeroBased
32 | first =
33 | ZeroBased 0
34 |
35 |
36 | second :: ZeroBased
37 | second =
38 | ZeroBased 1
39 |
40 |
41 | third :: ZeroBased
42 | third =
43 | ZeroBased 2
44 |
45 |
46 | {-# INLINE next #-}
47 | next :: ZeroBased -> ZeroBased
48 | next (ZeroBased i) =
49 | ZeroBased (i + 1)
50 |
51 |
52 |
53 | -- DESTRUCT
54 |
55 |
56 | toMachine :: ZeroBased -> Int
57 | toMachine (ZeroBased index) =
58 | index
59 |
60 |
61 | toHuman :: ZeroBased -> Int
62 | toHuman (ZeroBased index) =
63 | index + 1
64 |
65 |
66 |
67 | -- INDEXED MAP
68 |
69 |
70 | {-# INLINE indexedMap #-}
71 | indexedMap :: (ZeroBased -> a -> b) -> [a] -> [b]
72 | indexedMap func xs =
73 | zipWith func (map ZeroBased [0 .. length xs]) xs
74 |
75 |
76 | {-# INLINE indexedTraverse #-}
77 | indexedTraverse :: (Applicative f) => (ZeroBased -> a -> f b) -> [a] -> f [b]
78 | indexedTraverse func xs =
79 | sequenceA (indexedMap func xs)
80 |
81 |
82 | {-# INLINE indexedForA #-}
83 | indexedForA :: (Applicative f) => [a] -> (ZeroBased -> a -> f b) -> f [b]
84 | indexedForA xs func =
85 | sequenceA (indexedMap func xs)
86 |
87 |
88 |
89 | -- VERIFIED/INDEXED ZIP
90 |
91 |
92 | data VerifiedList a
93 | = LengthMatch [a]
94 | | LengthMismatch Int Int
95 |
96 |
97 | indexedZipWith :: (ZeroBased -> a -> b -> c) -> [a] -> [b] -> VerifiedList c
98 | indexedZipWith func listX listY =
99 | indexedZipWithHelp func 0 listX listY []
100 |
101 |
102 | indexedZipWithHelp :: (ZeroBased -> a -> b -> c) -> Int -> [a] -> [b] -> [c] -> VerifiedList c
103 | indexedZipWithHelp func index listX listY revListZ =
104 | case (listX, listY) of
105 | ([], []) ->
106 | LengthMatch (reverse revListZ)
107 |
108 | (x:xs, y:ys) ->
109 | indexedZipWithHelp func (index + 1) xs ys $
110 | func (ZeroBased index) x y : revListZ
111 |
112 | (_, _) ->
113 | LengthMismatch (index + length listX) (index + length listY)
114 |
115 |
116 | indexedZipWithA :: (Applicative f) => (ZeroBased -> a -> b -> f c) -> [a] -> [b] -> f (VerifiedList c)
117 | indexedZipWithA func listX listY =
118 | case indexedZipWith func listX listY of
119 | LengthMatch xs ->
120 | LengthMatch <$> sequenceA xs
121 |
122 | LengthMismatch x y ->
123 | pure (LengthMismatch x y)
124 |
125 |
126 |
127 | -- BINARY
128 |
129 |
130 | instance Binary ZeroBased where
131 | get = liftM ZeroBased get
132 | put (ZeroBased n) = put n
133 |
--------------------------------------------------------------------------------
/hints/optimize.md:
--------------------------------------------------------------------------------
1 |
2 | # How to optimize Elm code
3 |
4 | When you are serving a website, there are two kinds of optimizations you want to do:
5 |
6 | 1. **Asset Size** — How can we send as few bits as possible?
7 | 2. **Performance** — How can those bits run as quickly as possible?
8 |
9 | It turns out that Elm does really well on both! We have [very small assets](https://elm-lang.org/news/small-assets-without-the-headache) and [very fast code](https://elm-lang.org/news/blazing-fast-html-round-two) when compared to the popular alternatives.
10 |
11 | Okay, but how do we get those numbers?
12 |
13 |
14 | ## Instructions
15 |
16 | Step one is to compile with the `--optimize` flag. This does things like shortening record field names and unboxing values.
17 |
18 | Step two is to call `uglifyjs` with a bunch of special flags. The flags unlock optimizations that are unreliable in normal JS code, but because Elm does not have side-effects, they work fine for us!
19 |
20 | Putting those together, here is how I would optimize `src/Main.elm` with two terminal commands:
21 |
22 | ```bash
23 | elm make src/Main.elm --optimize --output=elm.js
24 | uglifyjs elm.js --compress "pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe" | uglifyjs --mangle --output=elm.min.js
25 | ```
26 |
27 | After this you will have an `elm.js` and a significantly smaller `elm.min.js` file!
28 |
29 | **Note 1:** `uglifyjs` is called twice there. First to `--compress` and second to `--mangle`. This is necessary! Otherwise `uglifyjs` will ignore our `pure_funcs` flag.
30 |
31 | **Note 2:** If the `uglifyjs` command is not available in your terminal, you can run the command `npm install uglify-js --global` to download it. You probably already have `npm` from getting `elm repl` working, but if not, it is bundled with [nodejs](https://nodejs.org/).
32 |
33 | ## Scripts
34 |
35 | It is hard to remember all that, so it is probably a good idea to write a script that does it.
36 |
37 | I would maybe want to run `./optimize.sh src/Main.elm` and get out `elm.js` and `elm.min.js`, so on Mac or Linux, I would make a script called `optimize.sh` like this:
38 |
39 | ```bash
40 | #!/bin/sh
41 |
42 | set -e
43 |
44 | js="elm.js"
45 | min="elm.min.js"
46 |
47 | elm make --optimize --output=$js $@
48 |
49 | uglifyjs $js --compress "pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe" | uglifyjs --mangle --output=$min
50 |
51 | echo "Initial size: $(cat $js | wc -c) bytes ($js)"
52 | echo "Minified size:$(cat $min | wc -c) bytes ($min)"
53 | echo "Gzipped size: $(cat $min | gzip -c | wc -c) bytes"
54 | ```
55 |
56 | It also prints out all the asset sizes for you! Your server should be configured to gzip the assets it sends, so the last line is telling you how many bytes would _actually_ get sent to the user.
57 |
58 | Again, the important commands are `elm` and `uglifyjs` which work on any platform, so it should not be too tough to do something similar on Windows.
59 |
60 |
--------------------------------------------------------------------------------
/installers/npm/download.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var package = require('./package.json');
3 | var path = require('path');
4 | var request = require('request');
5 | var zlib = require('zlib');
6 |
7 |
8 |
9 | // MAIN
10 | //
11 | // This function is used by install.js and by the bin/elm backup that gets
12 | // called when --ignore-scripts is enabled. That's why install.js is so weird.
13 |
14 |
15 | module.exports = function(callback)
16 | {
17 | // figure out URL of binary
18 | var version = package.version.replace(/^(\d+\.\d+\.\d+).*$/, '$1'); // turn '1.2.3-alpha' into '1.2.3'
19 | var os = { 'darwin': 'mac', 'win32': 'windows', 'linux': 'linux' }[process.platform];
20 | var arch = { 'x64': '64-bit', 'ia32': '32-bit' }[process.arch];
21 | var url = 'https://github.com/elm/compiler/releases/download/' + version + '/binary-for-' + os + '-' + arch + '.gz';
22 |
23 | reportDownload(version, url);
24 |
25 | // figure out where to put the binary (calls path.resolve() to get path separators right on Windows)
26 | var binaryPath = path.resolve(__dirname, package.bin.elm) + (process.platform === 'win32' ? '.exe' : '');
27 |
28 | // set up handler for request failure
29 | function reportDownloadFailure(error)
30 | {
31 | exitFailure(url,'Something went wrong while fetching the following URL:\n\n' + url + '\n\nIt is saying:\n\n' + error);
32 | }
33 |
34 | // set up decompression pipe
35 | var gunzip = zlib.createGunzip().on('error', function(error) {
36 | exitFailure(url, 'I ran into trouble decompressing the downloaded binary. It is saying:\n\n' + error);
37 | });
38 |
39 | // set up file write pipe
40 | var write = fs.createWriteStream(binaryPath, {
41 | encoding: 'binary',
42 | mode: 0o755
43 | }).on('finish', callback).on('error', function(error) {
44 | exitFailure(url, 'I had some trouble writing file to disk. It is saying:\n\n' + error);
45 | });
46 |
47 | // put it all together
48 | request(url).on('error', reportDownloadFailure).pipe(gunzip).pipe(write);
49 | }
50 |
51 |
52 |
53 | // EXIT FAILURE
54 |
55 |
56 | function exitFailure(url, message)
57 | {
58 | console.error(
59 | '-- ERROR -----------------------------------------------------------------------\n\n'
60 | + message
61 | + '\n\nNOTE: You can avoid npm entirely by downloading directly from:\n'
62 | + url + '\nAll this package does is download that file and put it somewhere.\n\n'
63 | + '--------------------------------------------------------------------------------\n'
64 | );
65 | process.exit(1);
66 | }
67 |
68 |
69 |
70 | // REPORT DOWNLOAD
71 |
72 |
73 | function reportDownload(version, url)
74 | {
75 | console.log(
76 | '--------------------------------------------------------------------------------\n\n'
77 | + 'Downloading Elm ' + version + ' from GitHub.'
78 | + '\n\nNOTE: You can avoid npm entirely by downloading directly from:\n'
79 | + url + '\nAll this package does is download that file and put it somewhere.\n\n'
80 | + '--------------------------------------------------------------------------------\n'
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Result.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE Rank2Types #-}
3 | module Reporting.Result
4 | ( Result(..)
5 | , run
6 | , ok
7 | , warn
8 | , throw
9 | , mapError
10 | )
11 | where
12 |
13 |
14 | import qualified Data.OneOrMore as OneOrMore
15 | import qualified Reporting.Warning as Warning
16 |
17 |
18 |
19 | -- RESULT
20 |
21 |
22 | newtype Result info warnings error a =
23 | Result (
24 | forall result.
25 | info
26 | -> warnings
27 | -> (info -> warnings -> OneOrMore.OneOrMore error -> result)
28 | -> (info -> warnings -> a -> result)
29 | -> result
30 | )
31 |
32 |
33 | run :: Result () [w] e a -> ([w], Either (OneOrMore.OneOrMore e) a)
34 | run (Result k) =
35 | k () []
36 | (\() w e -> (reverse w, Left e))
37 | (\() w a -> (reverse w, Right a))
38 |
39 |
40 |
41 | -- HELPERS
42 |
43 |
44 | ok :: a -> Result i w e a
45 | ok a =
46 | Result $ \i w _ good ->
47 | good i w a
48 |
49 |
50 | warn :: Warning.Warning -> Result i [Warning.Warning] e ()
51 | warn warning =
52 | Result $ \i warnings _ good ->
53 | good i (warning:warnings) ()
54 |
55 |
56 | throw :: e -> Result i w e a
57 | throw e =
58 | Result $ \i w bad _ ->
59 | bad i w (OneOrMore.one e)
60 |
61 |
62 | mapError :: (e -> e') -> Result i w e a -> Result i w e' a
63 | mapError func (Result k) =
64 | Result $ \i w bad good ->
65 | let
66 | bad1 i1 w1 e1 =
67 | bad i1 w1 (OneOrMore.map func e1)
68 | in
69 | k i w bad1 good
70 |
71 |
72 |
73 | -- FANCY INSTANCE STUFF
74 |
75 |
76 | instance Functor (Result i w e) where
77 | fmap func (Result k) =
78 | Result $ \i w bad good ->
79 | let
80 | good1 i1 w1 value =
81 | good i1 w1 (func value)
82 | in
83 | k i w bad good1
84 |
85 |
86 | instance Applicative (Result i w e) where
87 | pure = ok
88 |
89 | (<*>) (Result kf) (Result kv) =
90 | Result $ \i w bad good ->
91 | let
92 | bad1 i1 w1 e1 =
93 | let
94 | bad2 i2 w2 e2 = bad i2 w2 (OneOrMore.more e1 e2)
95 | good2 i2 w2 _value = bad i2 w2 e1
96 | in
97 | kv i1 w1 bad2 good2
98 |
99 | good1 i1 w1 func =
100 | let
101 | bad2 i2 w2 e2 = bad i2 w2 e2
102 | good2 i2 w2 value = good i2 w2 (func value)
103 | in
104 | kv i1 w1 bad2 good2
105 | in
106 | kf i w bad1 good1
107 |
108 |
109 | instance Monad (Result i w e) where
110 | return = ok
111 |
112 | (>>=) (Result ka) callback =
113 | Result $ \i w bad good ->
114 | let
115 | good1 i1 w1 a =
116 | case callback a of
117 | Result kb -> kb i1 w1 bad good
118 | in
119 | ka i w bad good1
120 |
121 | (>>) (Result ka) (Result kb) =
122 | Result $ \i w bad good ->
123 | let
124 | good1 i1 w1 _ =
125 | kb i1 w1 bad good
126 | in
127 | ka i w bad good1
128 |
129 | -- PERF add INLINE to these?
130 |
--------------------------------------------------------------------------------
/hints/port-modules.md:
--------------------------------------------------------------------------------
1 |
2 | # No Ports in Packages
3 |
4 | The package ecosystem is one of the most important parts of Elm. Right now, our ecosystem has some compelling benefits:
5 |
6 | - There are many obvious default packages that work well.
7 | - Adding dependencies cannot introduce runtime exceptions.
8 | - Patch changes cannot lead to surprise build failures.
9 |
10 | These are really important factors if you want to *quickly* create *reliable* applications. The Elm community thinks this is valuable.
11 |
12 | Other communities think that the *number* of packages is a better measure of ecosystem health. That is a fine metric to use, but it is not the one we use for Elm. We would rather have 50 great packages than 100k packages of wildly varying quality.
13 |
14 |
15 | ## So what about ports?
16 |
17 | Imagine you install a new package that claims to support `localStorage`. You get it set up, working through any compile errors. You run it, but it does not seem to work! After trying to figure it out for hours, you realize there is some poorly documented `port` to hook up...
18 |
19 | Okay, now you need to hook up some JavaScript code. Is that JS file in the Elm package? Or is it on `npm`? Wait, what version on `npm` though? And is this patch version going to work as well? Also, how does this file fit into my build process? And assuming we get through all that, maybe the `port` has the same name as one of the ports in your project. Or it clashes with a `port` name in another package.
20 |
21 | **Suddenly adding dependencies is much more complicated and risky!** An experienced developer would always check for ports up front, spending a bunch of time manually classifying unacceptable packages. Most people would not know to do that and learn all the pitfalls through personal experience, ultimately spending even *more* time than the person who defensively checks to avoid these issues.
22 |
23 | So “ports in packages” would impose an enormous cost on application developers, and in the end, we would have a less reliable package ecosystem overall.
24 |
25 |
26 | ## Conclusion
27 |
28 | Our wager with the Elm package ecosystem is that it is better to get a package *right* than to get it *right now*. So while we could use “ports in packages” as a way to get twenty `localStorage` packages of varying quality *right now*, we are choosing not to go that route. Instead we ask that developers use ports directly in their application code, getting the same result a different way.
29 |
30 | Now this may not be the right choice for your particular project, and that is okay! We will be expanding our core libraries over time, as explained [here](https://github.com/elm-lang/projects/blob/master/roadmap.md#where-is-the-localstorage-package), and we hope you will circle back later to see if Elm has grown into a better fit!
31 |
32 | If you have more questions about this choice or what it means for your application, please come ask in [the Elm slack](http://elmlang.herokuapp.com/). Folks are friendly and happy to help out! Chances are that a `port` in your application will work great for your case once you learn more about how they are meant to be used.
33 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Render/Type/Localizer.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Reporting.Render.Type.Localizer
4 | ( Localizer
5 | , toDoc
6 | , toChars
7 | , empty
8 | , fromNames
9 | , fromModule
10 | )
11 | where
12 |
13 |
14 | import qualified Data.Map as Map
15 | import qualified Data.Name as Name
16 | import qualified Data.Set as Set
17 |
18 | import qualified AST.Source as Src
19 | import qualified Elm.ModuleName as ModuleName
20 | import Reporting.Doc ((<>))
21 | import qualified Reporting.Doc as D
22 | import qualified Reporting.Annotation as A
23 |
24 |
25 |
26 | -- LOCALIZER
27 |
28 |
29 | newtype Localizer =
30 | Localizer (Map.Map Name.Name Import)
31 |
32 |
33 | data Import =
34 | Import
35 | { _alias :: Maybe Name.Name
36 | , _exposing :: Exposing
37 | }
38 |
39 |
40 | data Exposing
41 | = All
42 | | Only (Set.Set Name.Name)
43 |
44 |
45 | empty :: Localizer
46 | empty =
47 | Localizer Map.empty
48 |
49 |
50 |
51 | -- LOCALIZE
52 |
53 |
54 | toDoc :: Localizer -> ModuleName.Canonical -> Name.Name -> D.Doc
55 | toDoc localizer home name =
56 | D.fromChars (toChars localizer home name)
57 |
58 |
59 | toChars :: Localizer -> ModuleName.Canonical -> Name.Name -> String
60 | toChars (Localizer localizer) moduleName@(ModuleName.Canonical _ home) name =
61 | case Map.lookup home localizer of
62 | Nothing ->
63 | Name.toChars home <> "." <> Name.toChars name
64 |
65 | Just (Import alias exposing) ->
66 | case exposing of
67 | All ->
68 | Name.toChars name
69 |
70 | Only set ->
71 | if Set.member name set then
72 | Name.toChars name
73 | else if name == Name.list && moduleName == ModuleName.list then
74 | "List"
75 | else
76 | Name.toChars (maybe home id alias) <> "." <> Name.toChars name
77 |
78 |
79 |
80 | -- FROM NAMES
81 |
82 |
83 | fromNames :: Map.Map Name.Name a -> Localizer
84 | fromNames names =
85 | Localizer $ Map.map (\_ -> Import Nothing All) names
86 |
87 |
88 |
89 | -- FROM MODULE
90 |
91 |
92 | fromModule :: Src.Module -> Localizer
93 | fromModule modul@(Src.Module _ _ _ imports _ _ _ _ _) =
94 | Localizer $ Map.fromList $
95 | (Src.getName modul, Import Nothing All) : map toPair imports
96 |
97 |
98 | toPair :: Src.Import -> (Name.Name, Import)
99 | toPair (Src.Import (A.At _ name) alias exposing) =
100 | ( name
101 | , Import alias (toExposing exposing)
102 | )
103 |
104 |
105 | toExposing :: Src.Exposing -> Exposing
106 | toExposing exposing =
107 | case exposing of
108 | Src.Open ->
109 | All
110 |
111 | Src.Explicit exposedList ->
112 | Only (foldr addType Set.empty exposedList)
113 |
114 |
115 | addType :: Src.Exposed -> Set.Set Name.Name -> Set.Set Name.Name
116 | addType exposed types =
117 | case exposed of
118 | Src.Lower _ -> types
119 | Src.Upper (A.At _ name) _ -> Set.insert name types
120 | Src.Operator _ _ -> types
121 |
--------------------------------------------------------------------------------
/compiler/src/Nitpick/Debug.hs:
--------------------------------------------------------------------------------
1 | module Nitpick.Debug
2 | ( hasDebugUses
3 | )
4 | where
5 |
6 |
7 | import qualified Data.Map.Utils as Map
8 |
9 | import qualified AST.Optimized as Opt
10 |
11 |
12 |
13 | -- HAS DEBUG USES
14 |
15 |
16 | hasDebugUses :: Opt.LocalGraph -> Bool
17 | hasDebugUses (Opt.LocalGraph _ graph _) =
18 | Map.any nodeHasDebug graph
19 |
20 |
21 | nodeHasDebug :: Opt.Node -> Bool
22 | nodeHasDebug node =
23 | case node of
24 | Opt.Define expr _ -> hasDebug expr
25 | Opt.DefineTailFunc _ expr _ -> hasDebug expr
26 | Opt.Ctor _ _ -> False
27 | Opt.Enum _ -> False
28 | Opt.Box -> False
29 | Opt.Link _ -> False
30 | Opt.Cycle _ vs fs _ -> any (hasDebug . snd) vs || any defHasDebug fs
31 | Opt.Manager _ -> False
32 | Opt.Kernel _ _ -> False
33 | Opt.PortIncoming expr _ -> hasDebug expr
34 | Opt.PortOutgoing expr _ -> hasDebug expr
35 |
36 |
37 | hasDebug :: Opt.Expr -> Bool
38 | hasDebug expression =
39 | case expression of
40 | Opt.Bool _ -> False
41 | Opt.Chr _ -> False
42 | Opt.Str _ -> False
43 | Opt.Int _ -> False
44 | Opt.Float _ -> False
45 | Opt.VarLocal _ -> False
46 | Opt.VarGlobal _ -> False
47 | Opt.VarEnum _ _ -> False
48 | Opt.VarBox _ -> False
49 | Opt.VarCycle _ _ -> False
50 | Opt.VarDebug _ _ _ _ -> True
51 | Opt.VarKernel _ _ -> False
52 | Opt.List exprs -> any hasDebug exprs
53 | Opt.Function _ expr -> hasDebug expr
54 | Opt.Call e es -> hasDebug e || any hasDebug es
55 | Opt.TailCall _ args -> any (hasDebug . snd) args
56 | Opt.If conds finally -> any (\(c,e) -> hasDebug c || hasDebug e) conds || hasDebug finally
57 | Opt.Let def body -> defHasDebug def || hasDebug body
58 | Opt.Destruct _ expr -> hasDebug expr
59 | Opt.Case _ _ d jumps -> deciderHasDebug d || any (hasDebug . snd) jumps
60 | Opt.Accessor _ -> False
61 | Opt.Access r _ -> hasDebug r
62 | Opt.Update r fs -> hasDebug r || any hasDebug fs
63 | Opt.Record fs -> any hasDebug fs
64 | Opt.Unit -> False
65 | Opt.Tuple a b c -> hasDebug a || hasDebug b || maybe False hasDebug c
66 | Opt.Shader _ _ _ -> False
67 |
68 |
69 | defHasDebug :: Opt.Def -> Bool
70 | defHasDebug def =
71 | case def of
72 | Opt.Def _ expr -> hasDebug expr
73 | Opt.TailDef _ _ expr -> hasDebug expr
74 |
75 |
76 | deciderHasDebug :: Opt.Decider Opt.Choice -> Bool
77 | deciderHasDebug decider =
78 | case decider of
79 | Opt.Leaf (Opt.Inline expr) -> hasDebug expr
80 | Opt.Leaf (Opt.Jump _) -> False
81 | Opt.Chain _ success failure -> deciderHasDebug success || deciderHasDebug failure
82 | Opt.FanOut _ tests fallback -> any (deciderHasDebug . snd) tests || deciderHasDebug fallback
83 |
84 |
85 |
86 | -- TODO: FIND GLOBALLY UNUSED DEFINITIONS?
87 | -- TODO: FIND PACKAGE USAGE STATS? (e.g. elm/core = 142, author/project = 2, etc.)
88 |
--------------------------------------------------------------------------------
/compiler/src/Generate/JavaScript/Functions.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
2 | module Generate.JavaScript.Functions
3 | ( functions
4 | )
5 | where
6 |
7 |
8 | import qualified Data.ByteString.Builder as B
9 | import Text.RawString.QQ (r)
10 |
11 |
12 |
13 | -- FUNCTIONS
14 |
15 |
16 | functions :: B.Builder
17 | functions = [r|
18 |
19 | function F(arity, fun, wrapper) {
20 | wrapper.a = arity;
21 | wrapper.f = fun;
22 | return wrapper;
23 | }
24 |
25 | function F2(fun) {
26 | return F(2, fun, function(a) { return function(b) { return fun(a,b); }; })
27 | }
28 | function F3(fun) {
29 | return F(3, fun, function(a) {
30 | return function(b) { return function(c) { return fun(a, b, c); }; };
31 | });
32 | }
33 | function F4(fun) {
34 | return F(4, fun, function(a) { return function(b) { return function(c) {
35 | return function(d) { return fun(a, b, c, d); }; }; };
36 | });
37 | }
38 | function F5(fun) {
39 | return F(5, fun, function(a) { return function(b) { return function(c) {
40 | return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
41 | });
42 | }
43 | function F6(fun) {
44 | return F(6, fun, function(a) { return function(b) { return function(c) {
45 | return function(d) { return function(e) { return function(f) {
46 | return fun(a, b, c, d, e, f); }; }; }; }; };
47 | });
48 | }
49 | function F7(fun) {
50 | return F(7, fun, function(a) { return function(b) { return function(c) {
51 | return function(d) { return function(e) { return function(f) {
52 | return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
53 | });
54 | }
55 | function F8(fun) {
56 | return F(8, fun, function(a) { return function(b) { return function(c) {
57 | return function(d) { return function(e) { return function(f) {
58 | return function(g) { return function(h) {
59 | return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
60 | });
61 | }
62 | function F9(fun) {
63 | return F(9, fun, function(a) { return function(b) { return function(c) {
64 | return function(d) { return function(e) { return function(f) {
65 | return function(g) { return function(h) { return function(i) {
66 | return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
67 | });
68 | }
69 |
70 | function A2(fun, a, b) {
71 | return fun.a === 2 ? fun.f(a, b) : fun(a)(b);
72 | }
73 | function A3(fun, a, b, c) {
74 | return fun.a === 3 ? fun.f(a, b, c) : fun(a)(b)(c);
75 | }
76 | function A4(fun, a, b, c, d) {
77 | return fun.a === 4 ? fun.f(a, b, c, d) : fun(a)(b)(c)(d);
78 | }
79 | function A5(fun, a, b, c, d, e) {
80 | return fun.a === 5 ? fun.f(a, b, c, d, e) : fun(a)(b)(c)(d)(e);
81 | }
82 | function A6(fun, a, b, c, d, e, f) {
83 | return fun.a === 6 ? fun.f(a, b, c, d, e, f) : fun(a)(b)(c)(d)(e)(f);
84 | }
85 | function A7(fun, a, b, c, d, e, f, g) {
86 | return fun.a === 7 ? fun.f(a, b, c, d, e, f, g) : fun(a)(b)(c)(d)(e)(f)(g);
87 | }
88 | function A8(fun, a, b, c, d, e, f, g, h) {
89 | return fun.a === 8 ? fun.f(a, b, c, d, e, f, g, h) : fun(a)(b)(c)(d)(e)(f)(g)(h);
90 | }
91 | function A9(fun, a, b, c, d, e, f, g, h, i) {
92 | return fun.a === 9 ? fun.f(a, b, c, d, e, f, g, h, i) : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
93 | }
94 |
95 | |]
96 |
--------------------------------------------------------------------------------
/compiler/src/Canonicalize/Environment/Dups.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Canonicalize.Environment.Dups
4 | ( detect
5 | , checkFields
6 | , checkFields'
7 | , Dict
8 | , none
9 | , one
10 | , insert
11 | , union
12 | , unions
13 | )
14 | where
15 |
16 |
17 | import qualified Data.Map as Map
18 | import qualified Data.Name as Name
19 |
20 | import qualified Data.OneOrMore as OneOrMore
21 | import qualified Reporting.Annotation as A
22 | import qualified Reporting.Error.Canonicalize as Error
23 | import qualified Reporting.Result as Result
24 |
25 |
26 |
27 | -- DUPLICATE TRACKER
28 |
29 |
30 | type Dict value =
31 | Map.Map Name.Name (OneOrMore.OneOrMore (Info value))
32 |
33 |
34 | data Info value =
35 | Info
36 | { _region :: A.Region
37 | , _value :: value
38 | }
39 |
40 |
41 |
42 | -- DETECT
43 |
44 |
45 | type ToError =
46 | Name.Name -> A.Region -> A.Region -> Error.Error
47 |
48 |
49 | detect :: ToError -> Dict a -> Result.Result i w Error.Error (Map.Map Name.Name a)
50 | detect toError dict =
51 | Map.traverseWithKey (detectHelp toError) dict
52 |
53 |
54 | detectHelp :: ToError -> Name.Name -> OneOrMore.OneOrMore (Info a) -> Result.Result i w Error.Error a
55 | detectHelp toError name values =
56 | case values of
57 | OneOrMore.One (Info _ value) ->
58 | return value
59 |
60 | OneOrMore.More left right ->
61 | let
62 | (Info r1 _, Info r2 _) =
63 | OneOrMore.getFirstTwo left right
64 | in
65 | Result.throw (toError name r1 r2)
66 |
67 |
68 |
69 | -- CHECK FIELDS
70 |
71 |
72 | checkFields :: [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name a)
73 | checkFields fields =
74 | detect Error.DuplicateField (foldr addField none fields)
75 |
76 |
77 | addField :: (A.Located Name.Name, a) -> Dict a -> Dict a
78 | addField (A.At region name, value) dups =
79 | Map.insertWith OneOrMore.more name (OneOrMore.one (Info region value)) dups
80 |
81 |
82 | checkFields' :: (A.Region -> a -> b) -> [(A.Located Name.Name, a)] -> Result.Result i w Error.Error (Map.Map Name.Name b)
83 | checkFields' toValue fields =
84 | detect Error.DuplicateField (foldr (addField' toValue) none fields)
85 |
86 |
87 | addField' :: (A.Region -> a -> b) -> (A.Located Name.Name, a) -> Dict b -> Dict b
88 | addField' toValue (A.At region name, value) dups =
89 | Map.insertWith OneOrMore.more name (OneOrMore.one (Info region (toValue region value))) dups
90 |
91 |
92 |
93 | -- BUILDING DICTIONARIES
94 |
95 |
96 | none :: Dict a
97 | none =
98 | Map.empty
99 |
100 |
101 | one :: Name.Name -> A.Region -> value -> Dict value
102 | one name region value =
103 | Map.singleton name (OneOrMore.one (Info region value))
104 |
105 |
106 | insert :: Name.Name -> A.Region -> a -> Dict a -> Dict a
107 | insert name region value dict =
108 | Map.insertWith (\new old -> OneOrMore.more old new) name (OneOrMore.one (Info region value)) dict
109 |
110 |
111 | union :: Dict a -> Dict a -> Dict a
112 | union a b =
113 | Map.unionWith OneOrMore.more a b
114 |
115 |
116 | unions :: [Dict a] -> Dict a
117 | unions dicts =
118 | Map.unionsWith OneOrMore.more dicts
119 |
--------------------------------------------------------------------------------
/hints/implicit-casts.md:
--------------------------------------------------------------------------------
1 |
2 | # Implicit Casts
3 |
4 | Many languages automatically convert from `Int` to `Float` when they think it is necessary. This conversion is often called an [implicit cast](https://en.wikipedia.org/wiki/Type_conversion).
5 |
6 | Languages that will add in implicit casts for addition include:
7 |
8 | - JavaScript
9 | - Python
10 | - Ruby
11 | - C
12 | - C++
13 | - C#
14 | - Java
15 | - Scala
16 |
17 | These languages generally agree that an `Int` may be implicitly cast to a `Float` when necessary. So everyone is doing it, why not Elm?!
18 |
19 | > **Note:** Ruby does not follow the trend. They implicitly cast a `Float` to an `Int`, truncating all the decimal points!
20 |
21 |
22 | ## Type Inference + Implicit Casts
23 |
24 | Elm comes from the ML-family of languages. Languages in the ML-family that **never** do implicit casts include:
25 |
26 | - Standard ML
27 | - OCaml
28 | - Elm
29 | - F#
30 | - Haskell
31 |
32 | Why would so many languages from this lineage require explicit conversions though?
33 |
34 | Well, we have to go back to the 1970s for some background. J. Roger Hindley and Robin Milner independently discovered an algorithm that could _efficiently_ figure out the type of everything in your program without any type annotations. Type Inference! Every ML-family language has some variation of this algorithm at the center of its design.
35 |
36 | For decades, the problem was that nobody could figure out how to combine type inference with implicit casts AND make the resulting algorithm efficient enough for daily use. As far as I know, Scala was the first widely known language to figure out how to combine these two things! Its creator, Martin Odersky did a lot of work on combining type inference and subtyping to make this possible.
37 |
38 | So for any ML-family language designed before Scala, it is safe to assume that implicit conversions just was not an option. Okay, but what about Elm?! It comes after Scala, so why not do it like them?!
39 |
40 | 1. You pay performance cost to mix type inference and implicit conversions. At least as far as anyone knows, it defeats an optimization that is crucial to getting _reliably_ good performance. It is fine in most cases, but it can be a real issue in very large code bases.
41 |
42 | 2. Based on experience reports from Scala users, it seemed like the convenience was not worth the hidden cost. Yes, you can convert `n` in `(n + 1.5)` and everything is nice, but when you are in larger programs that are sparsely annotated, it can be quite difficult to figure out what is going on.
43 |
44 | This user data may be confounded by the fact that Scala allows quite extensive conversions, not just from `Int` to `Float`, but I think it is worth taking seriously nonetheless. So it is _possible_, but it has tradeoffs.
45 |
46 |
47 | ## Conclusion
48 |
49 | First, based on the landscape of design possibilities, it seems like requiring _explicit_ conversions is a pretty nice balance. We can have type inference, it can produce friendly error messages, the algorithm is snappy, and an unintended implicit cast will not flow hundreds of lines before manifesting to the user.
50 |
51 | Second, Elm very much favors explicit code, so this also fits in with the overall spirit of the language and libraries.
52 |
53 | I hope that clarifies why you have to add those `toFloat` and `round` functions! It definitely can take some getting used to, but there are tons of folks who get past that acclimation period and really love the tradeoffs!
54 |
--------------------------------------------------------------------------------
/installers/npm/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | I very highly recommend asking for help on [the Elm slack](https://elmlang.herokuapp.com).
4 |
5 | There are a lot of things that can go wrong when installing software, and it can really help to have a second pair of eyes on your situation!
6 |
7 | This document goes through a couple options that may help you out.
8 |
9 |
10 |
11 |
12 | ## Can you skip npm entirely?
13 |
14 | The most reliable way to get Elm installed using the official installers for Mac and Windows [here][download].
15 |
16 | You can also download the binaries directly. On Linux, you could do it in the terminal like this:
17 |
18 | ```bash
19 | cd ~/Desktop/
20 | curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-linux-64-bit.gz
21 | gunzip elm.gz # unzip the file
22 | chmod +x elm # make the file executable
23 | sudo mv elm /usr/local/bin/ # put the executable in a directory likely to be listed in your PATH variable
24 | ```
25 |
26 | If these exact commands do not work for you, you can try to do the same thing by hand.
27 |
28 | Read the section below on `PATH` variables if you are not sure what that is!
29 |
30 | [download]: https://github.com/elm/compiler/releases/tag/0.19.1
31 |
32 |
33 |
34 |
35 | ## Do you need to use npm for some reason?
36 |
37 | The company running npm has a list of common troubleshooting situations [here](https://npm.community/c/support/troubleshooting), but it may be better to just try to find your specific case on Stack Overflow. Often there are permissions issues where you may need to use `sudo` with some command.
38 |
39 | ### Firewalls
40 |
41 | Some companies have a firewall.
42 |
43 | These companies usually have set the `HTTP_PROXY` or `HTTPS_PROXY` environment variable on your computer. This is more common with Windows computers.
44 |
45 | The result is that the request for `https://github.com/elm/compiler/releases/download/0.19.1/binary-for-windows-64-bit.gz` is being sent to a "proxy server" where they monitor traffic. Maybe they rule out certain domains, maybe they check data when it comes back from the actual URL, etc.
46 |
47 | It is probably best to ask someone about the situation on this, but you can test things out by temporarily using an alternate `HTTPS_PROXY` value with something like this:
48 |
49 | ```
50 | # Mac and Linux
51 | HTTPS_PROXY=http://proxy.example.com npm install -g elm
52 |
53 | # Windows
54 | set HTTPS_PROXY=http://proxy.example.com
55 | npm install -g elm
56 | ```
57 |
58 | Check out [this document](https://www.npmjs.com/package/request#controlling-proxy-behaviour-using-environment-variables) for more information on how environment variables like `NO_PROXY`, `HTTP_PROXY`, and `HTTPS_PROXY` are handled by the npm.
59 |
60 |
61 |
62 |
63 | ## Do you know what a `PATH` variable is?
64 |
65 | When you run a command like `elm make src/Main.elm`, your computer starts by trying to find a file called `elm`.
66 |
67 | The `PATH` is a list of directories to search within. On Mac and Linux, you can see these directories by running:
68 |
69 | ```
70 | $ echo $PATH
71 | /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/local/git/bin
72 | ```
73 |
74 | The are separated by `:` for some reason. So running `elm make src/Main.elm` starts by searching the `PATH` for files named `elm`. On my computer, it finds `/usr/local/bin/elm` and then can actually run the command.
75 |
76 | Is `elm` in one of the directories listed in your `PATH` variable? I recommend asking for help if you are in this scenario and unsure how to proceed.
77 |
--------------------------------------------------------------------------------
/hints/shadowing.md:
--------------------------------------------------------------------------------
1 |
2 | # Variable Shadowing
3 |
4 | Variable shadowing is when you define the same variable name twice in an ambiguous way. Here is a pretty reasonable use of shadowing:
5 |
6 | ```elm
7 | viewName : Maybe String -> Html msg
8 | viewName name =
9 | case name of
10 | Nothing ->
11 | ...
12 |
13 | Just name ->
14 | ...
15 | ```
16 |
17 | I define a `name` with type `Maybe String` and then in that second branch, I define a `name` that is a `String`. Now that there are two `name` values, it is not 100% obvious which one you want in that second branch.
18 |
19 | Most linters produce warnings on variable shadowing, so Elm makes “best practices” the default. Just rename the first one to `maybeName` and move on.
20 |
21 | This choice is relatively uncommon in programming languages though, so I want to provide the reasoning behind it.
22 |
23 |
24 | ## The Cost of Shadowing
25 |
26 | The code snippet from above is the best case scenario for variable shadowing. It is pretty clear really. But that is because it is a fake example. It does not even compile.
27 |
28 | In a large module that is evolving over time, this is going to cause bugs in a very predictable way. You will have two definitions, separated by hundreds of lines. For example:
29 |
30 | ```elm
31 | name : String
32 | name =
33 | "Tom"
34 |
35 | -- hundreds of lines
36 |
37 | viewName : String -> Html msg
38 | viewName name =
39 | ... name ... name ... name ...
40 | ```
41 |
42 | Okay, so the `viewName` function has an argument `name` and it uses it three times. Maybe the `viewName` function is 50 lines long in total, so those uses are not totally easy to see. This is fine so far, but say your colleague comes along five months later and wants to support first and last names. They refactor the code like this:
43 |
44 | ```elm
45 | viewName : String -> String -> Html msg
46 | viewName firstName lastName =
47 | ... name ... name ... name ...
48 | ```
49 |
50 | The code compiles, but it does not work as intended. They forgot to change all the uses of `name`, and because it shadows the top-level `name` value, it always shows up as `"Tom"`. It is a simple mistake, but it is always the last thing I think of.
51 |
52 | > Is the data being fetched properly? Let me log all of the JSON requests. Maybe the JSON decoders are messed up? Hmm. Maybe someone is transforming the name in a bad way at some point? Let me check my `update` code.
53 |
54 | Basically, a bunch of time gets wasted on something that could easily be detected by the compiler. But this bug is rare, right?
55 |
56 |
57 | ## Aggregate Cost
58 |
59 | Thinking of a unique and helpful name takes some extra time. Maybe 30 seconds. But it means that:
60 |
61 | 1. Your code is easier to read and understand later on. So you spend 30 seconds once `O(1)` rather than spending 10 seconds each time someone reads that code in the future `O(n)`.
62 |
63 | 2. The tricky shadowing bug described above is impossible. Say there is a 5% chance that any given edit produces a shadowing bug, and that resolving that shadowing bug takes one hour. That means the expected time for each edit increases by three minutes.
64 |
65 | If you are still skeptical, I encourage you can play around with the number of edits, time costs, and probabilities here. When shadowing is not allowed, the resulting overhead for the entire lifetime of the code is the 30 seconds it takes to pick a better name, so that is what you need to beat!
66 |
67 |
68 | ## Summary
69 |
70 | Without shadowing, the code easier to read and folks spend less time on pointless debugging. The net outcome is that folks have more time to make something wonderful with Elm!
71 |
--------------------------------------------------------------------------------
/hints/comparing-records.md:
--------------------------------------------------------------------------------
1 | # Comparing Records
2 |
3 | The built-in comparison operators work on a fixed set of types, like `Int` and `String`. That covers a lot of cases, but what happens when you want to compare records?
4 |
5 | This page aims to catalog these scenarios and offer alternative paths that can get you unstuck.
6 |
7 |
8 | ## Sorting Records
9 |
10 | Say we want a `view` function that can show a list of students sorted by different characteristics.
11 |
12 | We could create something like this:
13 |
14 | ```elm
15 | import Html exposing (..)
16 |
17 | type alias Student =
18 | { name : String
19 | , age : Int
20 | , gpa : Float
21 | }
22 |
23 | type Order = Name | Age | GPA
24 |
25 | viewStudents : Order -> List Student -> Html msg
26 | viewStudents order students =
27 | let
28 | orderlyStudents =
29 | case order of
30 | Name -> List.sortBy .name students
31 | Age -> List.sortBy .age students
32 | GPA -> List.sortBy .gpa students
33 | in
34 | ul [] (List.map viewStudent orderlyStudents)
35 |
36 | viewStudent : Student -> Html msg
37 | viewStudent student =
38 | li [] [ text student.name ]
39 | ```
40 |
41 | If you are worried about the performance of changing the order or updating information about particular students, you can start using the [`Html.Lazy`](https://package.elm-lang.org/packages/elm/html/latest/Html-Lazy) and [`Html.Keyed`](https://package.elm-lang.org/packages/elm/html/latest/Html-Keyed) modules. The updated code would look something like this:
42 |
43 | ```elm
44 | import Html exposing (..)
45 | import Html.Lazy exposing (lazy)
46 | import Html.Keyed as Keyed
47 |
48 | type Order = Name | Age | GPA
49 |
50 | type alias Student =
51 | { name : String
52 | , age : Int
53 | , gpa : Float
54 | }
55 |
56 | viewStudents : Order -> List Student -> Html msg
57 | viewStudents order students =
58 | let
59 | orderlyStudents =
60 | case order of
61 | Name -> List.sortBy .name students
62 | Age -> List.sortBy .age students
63 | GPA -> List.sortBy .gpa students
64 | in
65 | Keyed.ul [] (List.map viewKeyedStudent orderlyStudents)
66 |
67 | viewKeyedStudent : Student -> (String, Html msg)
68 | viewKeyedStudent student =
69 | ( student.name, lazy viewStudent student )
70 |
71 | viewStudent : Student -> Html msg
72 | viewStudent student =
73 | li [] [ text student.name ]
74 | ```
75 |
76 | By using `Keyed.ul` we help the renderer move the DOM nodes around based on their key. This makes it much cheaper to reorder a bunch of students. And by using `lazy` we help the renderer skip a bunch of work. If the `Student` is the same as last time, the render can skip over it.
77 |
78 | > **Note:** Some people are skeptical of having logic like this in `view` functions, but I think the alternative (maintaining sort order in your `Model`) has some serious downsides. Say a colleague is adding a message to `Add` students, but they do not know about the sort order rules needed for presentation. Bug! So in this alternate design, you must diligently test your `update` function to make sure that no message disturbs the sort order. This is bound to lead to bugs over time!
79 | >
80 | > With all the optimizations possible with `Html.Lazy` and `Html.Keyed`, I would always be inclined to work on optimizing my `view` functions rather than making my `update` functions more complicated and error prone.
81 |
82 |
83 | ## Something else?
84 |
85 | If you have some other situation, please tell us about it [here](https://github.com/elm/error-message-catalog/issues). That is a log of error messages that can be improved, and we can use the particulars of your scenario to add more advice on this page!
86 |
--------------------------------------------------------------------------------
/docs/elm.json/package.md:
--------------------------------------------------------------------------------
1 | # `elm.json` for packages
2 |
3 | This is roughly `elm.json` for the `elm/json` package:
4 |
5 | ```json
6 | {
7 | "type": "package",
8 | "name": "elm/json",
9 | "summary": "Encode and decode JSON values",
10 | "license": "BSD-3-Clause",
11 | "version": "1.0.0",
12 | "exposed-modules": [
13 | "Json.Decode",
14 | "Json.Encode"
15 | ],
16 | "elm-version": "0.19.0 <= v < 0.20.0",
17 | "dependencies": {
18 | "elm/core": "1.0.0 <= v < 2.0.0"
19 | },
20 | "test-dependencies": {}
21 | }
22 | ```
23 |
24 |
25 |
26 |
27 | ## `"type"`
28 |
29 | Either `"application"` or `"package"`. All the other fields are based on this choice.
30 |
31 |
32 |
33 |
34 | ## `"name"`
35 |
36 | The name of a GitHub repo like `"elm-lang/core"` or `"rtfeldman/elm-css"`.
37 |
38 | > **Note:** We currently only support GitHub repos to ensure that there are no author name collisions. This seems like a pretty tricky problem to solve in a pleasant way. For example, do we have to keep an author name registry and give them out as we see them? But if someone is the same person on two platforms? And how to make this all happen in a way this is really nice for typical Elm users? Etc. So adding other hosting endpoints is harder than it sounds.
39 |
40 |
41 |
42 |
43 | ## `"summary"`
44 |
45 | A short summary that will appear on [`package.elm-lang.org`](https://package.elm-lang.org/) that describes what the package is for. Must be under 80 characters.
46 |
47 |
48 |
49 |
50 | ## `"license"`
51 |
52 | An OSI approved SPDX code like `"BSD-3-Clause"` or `"MIT"`. These are the two most common licenses in the Elm ecosystem, and BSD-3-Clause is a good default. But you can see the full list of options [here](https://spdx.org/licenses/).
53 |
54 |
55 |
56 |
57 | ## `"version"`
58 |
59 | All packages start at `"1.0.0"` and from there, Elm automatically enforces semantic versioning by comparing API changes.
60 |
61 | So if you make a PATCH change and call `elm bump` it will update you to `"1.0.1"`. And if you then decide to remove a function (a MAJOR change) and call `elm bump` it will update you to `"2.0.0"`. Etc.
62 |
63 |
64 |
65 |
66 | ## `"exposed-modules"`
67 |
68 | A list of modules that will be exposed to people using your package. The order you list them will be the order they appear on [`package.elm-lang.org`](https://package.elm-lang.org/).
69 |
70 | **Note:** If you have five or more modules, you can use a labelled list like [this](https://github.com/elm-lang/core/blob/master/elm.json). We show the labels on the package website to help people sort through larger packages with distinct categories. Labels must be under 20 characters.
71 |
72 |
73 |
74 |
75 | ## `"elm-version"`
76 |
77 | The range of Elm compilers that work with your package. Right now `"0.19.0 <= v < 0.20.0"` is always what you want for this.
78 |
79 |
80 |
81 |
82 | ## `"dependencies"`
83 |
84 | A list of packages that you depend upon. In each application, there can only be one version of each package, so wide ranges are great. Fewer dependencies is even better though!
85 |
86 | > **Note:** Dependency ranges should only express _tested_ ranges. It is not nice to use optimistic ranges and end up causing build failures for your users down the line. Eventually we would like to have an automated system that tries to build and test packages as new packages come out. If it all works, we could send a PR to the author widening the range.
87 |
88 |
89 |
90 |
91 | ## `"test-dependencies"`
92 |
93 | Dependencies that are only used in the `tests/` directory by `elm test`. Values from these packages will not appear in any final build artifacts.
94 |
--------------------------------------------------------------------------------
/compiler/src/Elm/String.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
2 | {-# LANGUAGE BangPatterns, EmptyDataDecls, FlexibleInstances #-}
3 | module Elm.String
4 | ( String
5 | , toChars
6 | , toBuilder
7 | , Chunk(..)
8 | , fromChunks
9 | )
10 | where
11 |
12 |
13 | import Prelude hiding (String)
14 | import Data.Binary (Binary, get, put)
15 | import Data.Bits ((.&.), shiftR)
16 | import qualified Data.ByteString.Builder as B
17 | import qualified Data.Utf8 as Utf8
18 | import Data.Utf8 (MBA, newByteArray, copyFromPtr, freeze, writeWord8)
19 | import GHC.Exts (RealWorld, Ptr)
20 | import GHC.IO (stToIO, unsafeDupablePerformIO)
21 | import GHC.ST (ST)
22 | import GHC.Word (Word8)
23 |
24 |
25 |
26 | -- STRINGS
27 |
28 |
29 | type String =
30 | Utf8.Utf8 ELM_STRING
31 |
32 |
33 | data ELM_STRING
34 |
35 |
36 |
37 | -- HELPERS
38 |
39 |
40 | toChars :: String -> [Char]
41 | toChars =
42 | Utf8.toChars
43 |
44 |
45 | {-# INLINE toBuilder #-}
46 | toBuilder :: String -> B.Builder
47 | toBuilder =
48 | Utf8.toBuilder
49 |
50 |
51 |
52 | -- FROM CHUNKS
53 |
54 |
55 | data Chunk
56 | = Slice (Ptr Word8) Int
57 | | Escape Word8
58 | | CodePoint Int
59 |
60 |
61 | fromChunks :: [Chunk] -> String
62 | fromChunks chunks =
63 | unsafeDupablePerformIO (stToIO (
64 | do let !len = sum (map chunkToWidth chunks)
65 | mba <- newByteArray len
66 | writeChunks mba 0 chunks
67 | freeze mba
68 | ))
69 |
70 |
71 | chunkToWidth :: Chunk -> Int
72 | chunkToWidth chunk =
73 | case chunk of
74 | Slice _ len -> len
75 | Escape _ -> 2
76 | CodePoint c -> if c < 0xFFFF then 6 else 12
77 |
78 |
79 | writeChunks :: MBA RealWorld -> Int -> [Chunk] -> ST RealWorld ()
80 | writeChunks mba offset chunks =
81 | case chunks of
82 | [] ->
83 | return ()
84 |
85 | chunk : chunks ->
86 | case chunk of
87 | Slice ptr len ->
88 | do copyFromPtr ptr mba offset len
89 | let !newOffset = offset + len
90 | writeChunks mba newOffset chunks
91 |
92 | Escape word ->
93 | do writeWord8 mba offset 0x5C {- \ -}
94 | writeWord8 mba (offset + 1) word
95 | let !newOffset = offset + 2
96 | writeChunks mba newOffset chunks
97 |
98 | CodePoint code ->
99 | if code < 0xFFFF then
100 | do writeCode mba offset code
101 | let !newOffset = offset + 6
102 | writeChunks mba newOffset chunks
103 | else
104 | do let (hi,lo) = divMod (code - 0x10000) 0x400
105 | writeCode mba (offset ) (hi + 0xD800)
106 | writeCode mba (offset + 6) (lo + 0xDC00)
107 | let !newOffset = offset + 12
108 | writeChunks mba newOffset chunks
109 |
110 |
111 | writeCode :: MBA RealWorld -> Int -> Int -> ST RealWorld ()
112 | writeCode mba offset code =
113 | do writeWord8 mba offset 0x5C {- \ -}
114 | writeWord8 mba (offset + 1) 0x75 {- u -}
115 | writeHex mba (offset + 2) (shiftR code 12)
116 | writeHex mba (offset + 3) (shiftR code 8)
117 | writeHex mba (offset + 4) (shiftR code 4)
118 | writeHex mba (offset + 5) code
119 |
120 |
121 | writeHex :: MBA RealWorld -> Int -> Int -> ST RealWorld ()
122 | writeHex mba !offset !bits =
123 | do let !n = fromIntegral bits .&. 0x0F
124 | writeWord8 mba offset (if n < 10 then 0x30 + n else 0x37 + n)
125 |
126 |
127 |
128 | -- BINARY
129 |
130 |
131 | instance Binary (Utf8.Utf8 ELM_STRING) where
132 | get = Utf8.getVeryLong
133 | put = Utf8.putVeryLong
134 |
--------------------------------------------------------------------------------
/terminal/src/Main.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | module Main
3 | ( main
4 | )
5 | where
6 |
7 |
8 | import Prelude hiding (init)
9 | import qualified Data.List as List
10 | import qualified Text.PrettyPrint.ANSI.Leijen as P
11 | import Text.PrettyPrint.ANSI.Leijen ((<>))
12 | import Text.Read (readMaybe)
13 |
14 | import qualified Elm.Version as V
15 | import Terminal
16 | import Terminal.Helpers
17 |
18 | import qualified Make
19 |
20 |
21 |
22 |
23 | -- MAIN
24 |
25 |
26 | main :: IO ()
27 | main =
28 | Terminal.app intro outro make
29 |
30 | intro :: P.Doc
31 | intro =
32 | P.vcat
33 | [ P.fillSep
34 | ["Hi,","thank","you","for","trying","out"
35 | ,P.green "elm2node"
36 | ,P.green (P.text (V.toChars V.compiler)) <> "."
37 | ,"I hope you like it!"
38 | ]
39 | , ""
40 | , P.black "-------------------------------------------------------------------------------"
41 | , P.black "This is a modified elm compiler to produce a Node.js module exposing pure"
42 | , P.black "functions."
43 | , P.black "-------------------------------------------------------------------------------"
44 | ]
45 |
46 |
47 | outro :: P.Doc
48 | outro =
49 | P.fillSep $ map P.text $ words $
50 | "Be sure to ask on the Elm slack if you run into trouble! Folks are friendly and\
51 | \ happy to help out. They hang out there because it is fun, so be kind to get the\
52 | \ best results!"
53 |
54 |
55 | -- MAKE
56 |
57 |
58 | make :: Terminal.Command
59 | make =
60 | let
61 | details =
62 | "Compiles Elm code into Node.js module:"
63 |
64 | example =
65 | stack
66 | [ reflow
67 | "For example:"
68 | , P.indent 4 $ P.green "elm2node src/Main.elm"
69 | , reflow
70 | "This tries to compile an Elm file named src/Main.elm, generating an elm.js\
71 | \ Node.js module if possible. This Node.js module will expose the same values\
72 | \ exposed in the `Main.elm` file. Some restrictions:"
73 | , P.indent 4 $ P.vcat
74 | [ reflow "* only \"static\" values or functions with one argument can be exposed"
75 | , P.text "* the only accepted types in exposed values are:"
76 | , P.indent 4 $
77 | P.hcat $
78 | List.intersperse (P.text ", ") $
79 | List.map P.green
80 | ["Int", "Float", "String", "Maybe", "Records", "List", "Array", "Json.Value"]
81 |
82 | , reflow "* exposed user defined types are silently ignored."
83 | ]
84 | ]
85 |
86 | makeFlags =
87 | flags Make.Flags
88 | |-- flag "output" Make.output "Specify the name of the resulting JS file. For example --output=assets/elm.js to generate the JS at assets/elm.js or --output=/dev/null to generate no output at all!"
89 | |-- flag "report" Make.reportType "You can say --report=json to get error messages as JSON. This is only really useful if you are an editor plugin. Humans should avoid it!"
90 | |-- onOff "optimize" "Turn on optimizations to make code smaller and faster. For example, the compiler renames record fields to be as short as possible and unboxes values to reduce allocation."
91 |
92 | in
93 | Terminal.Command "make" Uncommon details example (oneOrMore elmFile) makeFlags Make.run
94 |
95 |
96 |
97 |
98 | -- HELPERS
99 |
100 |
101 | stack :: [P.Doc] -> P.Doc
102 | stack docs =
103 | P.vcat $ List.intersperse "" docs
104 |
105 |
106 | reflow :: String -> P.Doc
107 | reflow string =
108 | P.fillSep $ map P.text $ words string
109 |
--------------------------------------------------------------------------------
/hints/comparing-custom-types.md:
--------------------------------------------------------------------------------
1 | # Comparing Custom Types
2 |
3 | The built-in comparison operators work on a fixed set of types, like `Int` and `String`. That covers a lot of cases, but what happens when you want to compare custom types?
4 |
5 | This page aims to catalog these scenarios and offer alternative paths that can get you unstuck.
6 |
7 |
8 | ## Wrapped Types
9 |
10 | It is common to try to get some extra type safety by creating really simple custom types:
11 |
12 | ```elm
13 | type Id = Id Int
14 | type Age = Age Int
15 |
16 | type Comment = Comment String
17 | type Description = Description String
18 | ```
19 |
20 | By wrapping the primitive values like this, the type system can now help you make sure that you never mix up a `Id` and an `Age`. Those are different types! This trick is extra cool because it has no runtime cost in `--optimize` mode. The compiler can just use an `Int` or `String` directly when you use that flag!
21 |
22 | The problem arises when you want to use a `Id` as a key in a dictionary. This is a totally reasonable thing to do, but the current version of Elm cannot handle this scenario.
23 |
24 | Instead of creating a `Dict Id Info` type, one thing you can do is create a custom data structure like this:
25 |
26 | ```elm
27 | module User exposing (Id, Table, empty, get, add)
28 |
29 | import Dict exposing (Dict)
30 |
31 |
32 | -- USER
33 |
34 | type Id = Id Int
35 |
36 |
37 | -- TABLE
38 |
39 | type Table info =
40 | Table Int (Dict Int info)
41 |
42 | empty : Table info
43 | empty =
44 | Table 0 Dict.empty
45 |
46 | get : Id -> Table info -> Maybe info
47 | get (Id id) (Table _ dict) =
48 | Dict.get id dict
49 |
50 | add : info -> Table info -> (Table info, Id)
51 | add info (Table nextId dict) =
52 | ( Table (nextId + 1) (Dict.insert nextId info dict)
53 | , Id nextId
54 | )
55 | ```
56 |
57 | There are a couple nice things about this approach:
58 |
59 | 1. The only way to get a new `User.Id` is to `add` information to a `User.Table`.
60 | 2. All the operations on a `User.Table` are explicit. Does it make sense to remove users? To merge two tables together? Are there any special details to consider in those cases? This will always be captured explicitly in the interface of the `User` module.
61 | 3. If you ever want to switch the internal representation from `Dict` to `Array` or something else, it is no problem. All the changes will be within the `User` module.
62 |
63 | So while this approach is not as convenient as using a `Dict` directly, it has some benefits of its own that can be helpful in some cases.
64 |
65 |
66 | ## Enumerations to Ints
67 |
68 | Say you need to define a `trafficLightToInt` function:
69 |
70 | ```elm
71 | type TrafficLight = Green | Yellow | Red
72 |
73 | trafficLightToInt : TrafficLight -> Int
74 | trafficLightToInt trafficLight =
75 | ???
76 | ```
77 |
78 | We have heard that some people would prefer to use a dictionary for this sort of thing. That way you do not need to write the numbers yourself, they can be generated such that you never have a typo.
79 |
80 | I would recommend using a `case` expression though:
81 |
82 | ```elm
83 | type TrafficLight = Green | Yellow | Red
84 |
85 | trafficLightToInt : TrafficLight -> Int
86 | trafficLightToInt trafficLight =
87 | case trafficLight of
88 | Green -> 1
89 | Yellow -> 2
90 | Red -> 3
91 | ```
92 |
93 | This is really straight-forward while avoiding questions like “is `Green` less than or greater than `Red`?”
94 |
95 |
96 | ## Something else?
97 |
98 | If you have some other situation, please tell us about it [here](https://github.com/elm/error-message-catalog/issues). That is a log of error messages that can be improved, and we can use the particulars of your scenario to add more advice on this page!
99 |
--------------------------------------------------------------------------------
/compiler/src/Reporting/Warning.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Reporting.Warning
4 | ( Warning(..)
5 | , Context(..)
6 | , toReport
7 | )
8 | where
9 |
10 |
11 | import Data.Monoid ((<>))
12 | import qualified Data.Name as Name
13 |
14 | import qualified AST.Canonical as Can
15 | import qualified AST.Utils.Type as Type
16 | import qualified Reporting.Annotation as A
17 | import qualified Reporting.Doc as D
18 | import qualified Reporting.Report as Report
19 | import qualified Reporting.Render.Code as Code
20 | import qualified Reporting.Render.Type as RT
21 | import qualified Reporting.Render.Type.Localizer as L
22 |
23 |
24 |
25 | -- ALL POSSIBLE WARNINGS
26 |
27 |
28 | data Warning
29 | = UnusedImport A.Region Name.Name
30 | | UnusedVariable A.Region Context Name.Name
31 | | MissingTypeAnnotation A.Region Name.Name Can.Type
32 |
33 |
34 | data Context = Def | Pattern
35 |
36 |
37 |
38 | -- TO REPORT
39 |
40 |
41 | toReport :: L.Localizer -> Code.Source -> Warning -> Report.Report
42 | toReport localizer source warning =
43 | case warning of
44 | UnusedImport region moduleName ->
45 | Report.Report "unused import" region [] $
46 | Code.toSnippet source region Nothing
47 | (
48 | D.reflow $
49 | "Nothing from the `" <> Name.toChars moduleName <> "` module is used in this file."
50 | ,
51 | "I recommend removing unused imports."
52 | )
53 |
54 | UnusedVariable region context name ->
55 | let title = defOrPat context "unused definition" "unused variable" in
56 | Report.Report title region [] $
57 | Code.toSnippet source region Nothing
58 | (
59 | D.reflow $
60 | "You are not using `" <> Name.toChars name <> "` anywhere."
61 | ,
62 | D.stack
63 | [ D.reflow $
64 | "Is there a typo? Maybe you intended to use `" <> Name.toChars name
65 | <> "` somewhere but typed another name instead?"
66 | , D.reflow $
67 | defOrPat context
68 | ( "If you are sure there is no typo, remove the definition.\
69 | \ This way future readers will not have to wonder why it is there!"
70 | )
71 | ( "If you are sure there is no typo, replace `" <> Name.toChars name
72 | <> "` with _ so future readers will not have to wonder why it is there!"
73 | )
74 | ]
75 | )
76 |
77 | MissingTypeAnnotation region name inferredType ->
78 | Report.Report "missing type annotation" region [] $
79 | Code.toSnippet source region Nothing
80 | (
81 | D.reflow $
82 | case Type.deepDealias inferredType of
83 | Can.TLambda _ _ ->
84 | "The `" <> Name.toChars name <> "` function has no type annotation."
85 |
86 | _ ->
87 | "The `" <> Name.toChars name <> "` definition has no type annotation."
88 | ,
89 | D.stack
90 | [ "I inferred the type annotation myself though! You can copy it into your code:"
91 | , D.green $ D.hang 4 $ D.sep $
92 | [ D.fromName name <> " :"
93 | , RT.canToDoc localizer RT.None inferredType
94 | ]
95 | ]
96 | )
97 |
98 |
99 | defOrPat :: Context -> a -> a -> a
100 | defOrPat context def pat =
101 | case context of
102 | Def -> def
103 | Pattern -> pat
104 |
105 |
--------------------------------------------------------------------------------
/compiler/src/AST/Utils/Type.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module AST.Utils.Type
4 | ( delambda
5 | , dealias
6 | , deepDealias
7 | , iteratedDealias
8 | , FunctionType(..)
9 | , toFunctionType
10 | )
11 | where
12 |
13 |
14 | import qualified Data.Map as Map
15 | import qualified Data.Name as Name
16 |
17 | import AST.Canonical (Type(..), AliasType(..), FieldType(..), Annotation(..), FreeVars)
18 |
19 |
20 |
21 | -- DELAMBDA
22 |
23 |
24 | delambda :: Type -> [Type]
25 | delambda tipe =
26 | case tipe of
27 | TLambda arg result ->
28 | arg : delambda result
29 |
30 | _ ->
31 | [tipe]
32 |
33 |
34 |
35 | -- DEALIAS
36 |
37 |
38 | dealias :: [(Name.Name, Type)] -> AliasType -> Type
39 | dealias args aliasType =
40 | case aliasType of
41 | Holey tipe ->
42 | dealiasHelp (Map.fromList args) tipe
43 |
44 | Filled tipe ->
45 | tipe
46 |
47 |
48 | dealiasHelp :: Map.Map Name.Name Type -> Type -> Type
49 | dealiasHelp typeTable tipe =
50 | case tipe of
51 | TLambda a b ->
52 | TLambda
53 | (dealiasHelp typeTable a)
54 | (dealiasHelp typeTable b)
55 |
56 | TVar x ->
57 | Map.findWithDefault tipe x typeTable
58 |
59 | TRecord fields ext ->
60 | TRecord (Map.map (dealiasField typeTable) fields) ext
61 |
62 | TAlias home name args t' ->
63 | TAlias home name (map (fmap (dealiasHelp typeTable)) args) t'
64 |
65 | TType home name args ->
66 | TType home name (map (dealiasHelp typeTable) args)
67 |
68 | TUnit ->
69 | TUnit
70 |
71 | TTuple a b maybeC ->
72 | TTuple
73 | (dealiasHelp typeTable a)
74 | (dealiasHelp typeTable b)
75 | (fmap (dealiasHelp typeTable) maybeC)
76 |
77 |
78 | dealiasField :: Map.Map Name.Name Type -> FieldType -> FieldType
79 | dealiasField typeTable (FieldType index tipe) =
80 | FieldType index (dealiasHelp typeTable tipe)
81 |
82 |
83 |
84 | -- DEEP DEALIAS
85 |
86 |
87 | deepDealias :: Type -> Type
88 | deepDealias tipe =
89 | case tipe of
90 | TLambda a b ->
91 | TLambda (deepDealias a) (deepDealias b)
92 |
93 | TVar _ ->
94 | tipe
95 |
96 | TRecord fields ext ->
97 | TRecord (Map.map deepDealiasField fields) ext
98 |
99 | TAlias _ _ args tipe' ->
100 | deepDealias (dealias args tipe')
101 |
102 | TType home name args ->
103 | TType home name (map deepDealias args)
104 |
105 | TUnit ->
106 | TUnit
107 |
108 | TTuple a b c ->
109 | TTuple (deepDealias a) (deepDealias b) (fmap deepDealias c)
110 |
111 |
112 | deepDealiasField :: FieldType -> FieldType
113 | deepDealiasField (FieldType index tipe) =
114 | FieldType index (deepDealias tipe)
115 |
116 |
117 |
118 | -- ITERATED DEALIAS
119 |
120 |
121 | iteratedDealias :: Type -> Type
122 | iteratedDealias tipe =
123 | case tipe of
124 | TAlias _ _ args realType ->
125 | iteratedDealias (dealias args realType)
126 |
127 | _ ->
128 | tipe
129 |
130 |
131 |
132 | -- FUNCTION TYPE
133 |
134 | data FunctionType =
135 | FunctionType
136 | { _f_freeVars :: FreeVars
137 | , _resultType :: Type
138 | , _args :: [Type]
139 | }
140 |
141 | toFunctionType :: Annotation -> FunctionType
142 | toFunctionType (Forall freeVars tipe) =
143 | let
144 | (res, args) =
145 | gatherArgs tipe
146 | in
147 | FunctionType freeVars res args
148 |
149 |
150 | gatherArgs :: Type -> (Type, [Type])
151 | gatherArgs tipe =
152 | case tipe of
153 | TLambda arg res ->
154 | let
155 | (res2, args) =
156 | gatherArgs res
157 | in
158 | (res2, arg : args)
159 |
160 | _ ->
161 | (tipe, [])
162 |
--------------------------------------------------------------------------------
/hints/init.md:
--------------------------------------------------------------------------------
1 |
2 | # Creating an Elm project
3 |
4 | The main goal of `elm init` is to get you to this page!
5 |
6 | It just creates an `elm.json` file and a `src/` directory for your code.
7 |
8 |
9 | ## What is `elm.json`?
10 |
11 | This file describes your project. It lists all of the packages you depend upon, so it will say the particular version of [`elm/core`](https://package.elm-lang.org/packages/elm/core/latest/) and [`elm/html`](https://package.elm-lang.org/packages/elm/html/latest/) that you are using. It makes builds reproducible! You can read a bit more about it [here](https://github.com/elm/compiler/blob/master/docs/elm.json/application.md).
12 |
13 | You should generally not edit it by hand. It is better to add new dependencies with commands like `elm install elm/http` or `elm install elm/json`.
14 |
15 |
16 | ## What goes in `src/`?
17 |
18 | This is where all of your Elm files live. It is best to start with a file called `src/Main.elm`. As you work through [the official guide](https://guide.elm-lang.org/), you can put the code examples in that `src/Main.elm` file.
19 |
20 |
21 | ## How do I compile it?
22 |
23 | Run `elm reactor` in your project. Now you can go to [`http://localhost:8000`](http://localhost:8000) and browse through all the files in your project. If you navigate to `.elm` files, it will compile them for you!
24 |
25 | If you want to do things more manually, you can run `elm make src/Main.elm` and it will produce an `index.html` file that you can look at in your browser.
26 |
27 |
28 | ## How do I structure my directories?
29 |
30 | Many folks get anxious about their project structure. “If I get it wrong, I am doomed!” This anxiety makes sense in languages where refactoring is risky, but Elm is not one of those languages!
31 |
32 | So we recommend that newcomers staying in one file until you get into the 600 to 1000 range. Push out of your comfort zone. Having the experience of being fine in large files will help you understand the boundaries in Elm, rather than just defaulting to the boundaries you learned in another language.
33 |
34 | The talk [The Life of a File](https://youtu.be/XpDsk374LDE) gets into this a lot more. The advice about building modules around a specific [custom type](https://guide.elm-lang.org/types/custom_types.html) is particularly important! You will see that emphasized a lot as you work through the official guide.
35 |
36 |
37 | ## How do I write tests?
38 |
39 | Elm will catch a bunch of errors statically, and I think it is worth skipping tests at first to get a feeling for when tests will actually help you _in Elm_.
40 |
41 | From there, we have a great testing package called [`elm-explorations/test`](https://github.com/elm-explorations/test) that can help you out! It is particularly helpful for teams working on a large codebase. When you are editing code you have never seen before, tests can capture additional details and constraints that are not otherwise apparent!
42 |
43 |
44 | ## How do I start fancier projects?
45 |
46 | I wanted `elm init` to generate as little code as possible. It is mainly meant to get you to this page! If you would like a more elaborate starting point, I recommend starting projects with commands like these:
47 |
48 | ```bash
49 | git clone https://github.com/evancz/elm-todomvc.git
50 | git clone https://github.com/rtfeldman/elm-spa-example.git
51 | ```
52 |
53 | The idea is that Elm projects should be so simple that nobody needs a tool to generate a bunch of stuff. This also captures the fact that project structure _should_ evolve organically as your application develops, never ending up exactly the same as other projects.
54 |
55 | But if you have something particular you want, I recommend creating your own starter recipe and using `git clone` when you start new projects. That way (1) you can get exactly what you want and (2) we do not end up with a complex `elm init` that ends up being confusing for beginners!
56 |
--------------------------------------------------------------------------------
/installers/mac/README.md:
--------------------------------------------------------------------------------
1 | # Install Instructions
2 |
3 | The easiest way to install is to to use [the Mac installer](https://github.com/elm/compiler/releases/download/0.19.1/installer-for-mac.pkg)!
4 |
5 | But it is also possible to install by running the following commands in your terminal:
6 |
7 | ```bash
8 | # Move to your Desktop so you can see what is going on easier.
9 | #
10 | cd ~/Desktop/
11 |
12 | # Download the 0.19.1 binary for Linux.
13 | #
14 | # +-----------+----------------------+
15 | # | FLAG | MEANING |
16 | # +-----------+----------------------+
17 | # | -L | follow redirects |
18 | # | -o elm.gz | name the file elm.gz |
19 | # +-----------+----------------------+
20 | #
21 | curl -L -o elm.gz https://github.com/elm/compiler/releases/download/0.19.1/binary-for-mac-64-bit.gz
22 |
23 | # There should now be a file named `elm.gz` on your Desktop.
24 | #
25 | # The downloaded file is compressed to make it faster to download.
26 | # This next command decompresses it, replacing `elm.gz` with `elm`.
27 | #
28 | gunzip elm.gz
29 |
30 | # There should now be a file named `elm` on your Desktop!
31 | #
32 | # Every file has "permissions" about whether it can be read, written, or executed.
33 | # So before we use this file, we need to mark this file as executable:
34 | #
35 | chmod +x elm
36 |
37 | # The `elm` file is now executable. That means running `~/Desktop/elm --help`
38 | # should work. Saying `./elm --help` works the same.
39 | #
40 | # But we want to be able to say `elm --help` without specifying the full file
41 | # path every time. We can do this by moving the `elm` binary to one of the
42 | # directories listed in your `PATH` environment variable:
43 | #
44 | sudo mv elm /usr/local/bin/
45 |
46 | # Now it should be possible to run the `elm` binary just by saying its name!
47 | #
48 | elm --help
49 | ```
50 |
51 |
52 |
53 | ## What is the `PATH` variable?
54 |
55 | When you run a command like `elm make src/Main.elm`, your computer starts by trying to find an executable file called `elm`.
56 |
57 | The `PATH` is the list of directories that get searched. You can see these directories by running:
58 |
59 | ```bash
60 | echo $PATH
61 | ```
62 |
63 | This prints `/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin` on my computer. The directories are separated by a `:` so there are five possibilities listed here.
64 |
65 | When I run `elm make src/Main.elm`, my terminal starts by searching these five directories for an executable file named `elm`. It finds `/usr/local/bin/elm` and then runs `/usr/local/bin/elm make src/Main.elm` with whatever arguments I gave.
66 |
67 | So the `PATH` environment variable is a convention that allows you to refer to a specific executable file without knowing exactly where it lives on your computer. This is actually how all "terminal commands" work! Commands like `ls` are really executable files that live in directories listed in your `PATH` variable.
68 |
69 | So the point of running `sudo mv elm /usr/local/bin/` is to turn the `elm` binary into a terminal command, allowing us to call it just like `ls` and `cd`.
70 |
71 | **Note:** Why do we need to use `sudo` for that one command? Imagine if some program was able to add executables named `ls` or `cd` to `/usr/local/bin` that did something tricky and unexpected. That would be a security problem! Many distributions make this scenario less likely by requiring special permissions to modify the `/usr/local/bin/` directory.
72 |
73 |
74 |
75 |
76 | ## Uninstall
77 |
78 | The following commands should remove everything:
79 |
80 | ```bash
81 | # Remove the `elm` executable.
82 | #
83 | sudo rm /usr/local/bin/elm
84 |
85 | # Remove any cached files. The files here reduce compile times when
86 | # starting new projects and make it possible to work offline in more
87 | # cases. No need to keep it around if you are uninstalling though!
88 | #
89 | rm -r ~/.elm/
90 | ```
91 |
92 | If you have any Elm projects still on your computer, you can remove their `elm-stuff/` directories as well.
93 |
94 |
--------------------------------------------------------------------------------
/terminal/impl/Terminal/Helpers.hs:
--------------------------------------------------------------------------------
1 | {-# LANGUAGE OverloadedStrings #-}
2 | module Terminal.Helpers
3 | ( version
4 | , elmFile
5 | , package
6 | )
7 | where
8 |
9 |
10 | import qualified Data.ByteString.UTF8 as BS_UTF8
11 | import qualified Data.Char as Char
12 | import qualified Data.List as List
13 | import qualified Data.Map as Map
14 | import qualified Data.Utf8 as Utf8
15 | import qualified System.FilePath as FP
16 |
17 | import Terminal (Parser(..))
18 | import qualified Deps.Registry as Registry
19 | import qualified Elm.Package as Pkg
20 | import qualified Elm.Version as V
21 | import qualified Parse.Primitives as P
22 | import qualified Stuff
23 | import qualified Reporting.Suggest as Suggest
24 |
25 |
26 |
27 | -- VERSION
28 |
29 |
30 | version :: Parser V.Version
31 | version =
32 | Parser
33 | { _singular = "version"
34 | , _plural = "versions"
35 | , _parser = parseVersion
36 | , _suggest = suggestVersion
37 | , _examples = return . exampleVersions
38 | }
39 |
40 |
41 | parseVersion :: String -> Maybe V.Version
42 | parseVersion chars =
43 | case P.fromByteString V.parser (,) (BS_UTF8.fromString chars) of
44 | Right vsn -> Just vsn
45 | Left _ -> Nothing
46 |
47 |
48 | suggestVersion :: String -> IO [String]
49 | suggestVersion _ =
50 | return []
51 |
52 |
53 | exampleVersions :: String -> [String]
54 | exampleVersions chars =
55 | let
56 | chunks = map Utf8.toChars (Utf8.split 0x2E {-.-} (Utf8.fromChars chars))
57 | isNumber cs = not (null cs) && all Char.isDigit cs
58 | in
59 | if all isNumber chunks then
60 | case chunks of
61 | [x] -> [ x ++ ".0.0" ]
62 | [x,y] -> [ x ++ "." ++ y ++ ".0" ]
63 | x:y:z:_ -> [ x ++ "." ++ y ++ "." ++ z ]
64 | _ -> ["1.0.0", "2.0.3"]
65 |
66 | else
67 | ["1.0.0", "2.0.3"]
68 |
69 |
70 |
71 | -- ELM FILE
72 |
73 |
74 | elmFile :: Parser FilePath
75 | elmFile =
76 | Parser
77 | { _singular = "elm file"
78 | , _plural = "elm files"
79 | , _parser = parseElmFile
80 | , _suggest = \_ -> return []
81 | , _examples = exampleElmFiles
82 | }
83 |
84 |
85 | parseElmFile :: String -> Maybe FilePath
86 | parseElmFile chars =
87 | if FP.takeExtension chars == ".elm" then
88 | Just chars
89 | else
90 | Nothing
91 |
92 |
93 | exampleElmFiles :: String -> IO [String]
94 | exampleElmFiles _ =
95 | return ["Main.elm","src/Main.elm"]
96 |
97 |
98 |
99 | -- PACKAGE
100 |
101 |
102 | package :: Parser Pkg.Name
103 | package =
104 | Parser
105 | { _singular = "package"
106 | , _plural = "packages"
107 | , _parser = parsePackage
108 | , _suggest = suggestPackages
109 | , _examples = examplePackages
110 | }
111 |
112 |
113 | parsePackage :: String -> Maybe Pkg.Name
114 | parsePackage chars =
115 | case P.fromByteString Pkg.parser (,) (BS_UTF8.fromString chars) of
116 | Right pkg -> Just pkg
117 | Left _ -> Nothing
118 |
119 |
120 | suggestPackages :: String -> IO [String]
121 | suggestPackages given =
122 | do cache <- Stuff.getPackageCache
123 | maybeRegistry <- Registry.read cache
124 | return $
125 | case maybeRegistry of
126 | Nothing ->
127 | []
128 |
129 | Just (Registry.Registry _ versions) ->
130 | filter (List.isPrefixOf given) $
131 | map Pkg.toChars (Map.keys versions)
132 |
133 |
134 | examplePackages :: String -> IO [String]
135 | examplePackages given =
136 | do cache <- Stuff.getPackageCache
137 | maybeRegistry <- Registry.read cache
138 | return $
139 | case maybeRegistry of
140 | Nothing ->
141 | [ "elm/json"
142 | , "elm/http"
143 | , "elm/random"
144 | ]
145 |
146 | Just (Registry.Registry _ versions) ->
147 | map Pkg.toChars $ take 4 $
148 | Suggest.sort given Pkg.toChars (Map.keys versions)
149 |
--------------------------------------------------------------------------------
/installers/linux/README.md:
--------------------------------------------------------------------------------
1 | # Install Instructions
2 |
3 | The pre-compiled binary for Linux works on a very wide range of distributions.
4 |
5 | It should be possible to install it by running the following commands in your terminal:
6 |
7 | ```bash
8 | # Move to your Desktop so you can see what is going on easier.
9 | #
10 | cd ~/Desktop/
11 |
12 | # Download the 0.1.0 binary for Linux.
13 | #
14 | # +----------------+---------------------------+
15 | # | FLAG | MEANING |
16 | # +----------------+---------------------------+
17 | # | -L | follow redirects |
18 | # | -o elm2node.gz | name the file elm2node.gz |
19 | # +----------------+---------------------------+
20 | #
21 | curl -L -o elm2node.gz https://github.com/sebsheep/elm2node/releases/download/0.1.0-alpha-2/elm2node.gz
22 |
23 | # There should now be a file named `elm2node.gz` on your Desktop.
24 | #
25 | # The downloaded file is compressed to make it faster to download.
26 | # This next command decompresses it, replacing `elm.gz` with `elm`.
27 | #
28 | gunzip elm2node.gz
29 |
30 | # There should now be a file named `elm2node` on your Desktop!
31 | #
32 | # Every file has "permissions" about whether it can be read, written, or executed.
33 | # So before we use this file, we need to mark this file as executable:
34 | #
35 | chmod +x elm2node
36 |
37 | # The `elm2node` file is now executable. That means running `~/Desktop/elm2node --help`
38 | # should work. Saying `./elm2node --help` works the same.
39 | #
40 | # But we want to be able to say `elm2node --help` without specifying the full file
41 | # path every time. We can do this by moving the `elm2node` binary to one of the
42 | # directories listed in your `PATH` environment variable:
43 | #
44 | sudo mv elm2node /usr/local/bin/
45 |
46 | # Now it should be possible to run the `elm2node` binary just by saying its name!
47 | #
48 | elm2node --help
49 | ```
50 |
51 |
52 |
53 | ## Wait, what is the `PATH` variable?
54 |
55 | When you run a command like `elm2node src/Main.elm`, your computer starts by trying to find an executable file called `elm2node`.
56 |
57 | The `PATH` is the list of directories that get searched. You can see these directories by running:
58 |
59 | ```bash
60 | echo $PATH
61 | ```
62 |
63 | This prints `/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin` on my computer. The directories are separated by a `:` so there are five possibilities listed here.
64 |
65 | When I run `elm2node src/Main.elm`, my terminal starts by searching these five directories for an executable file named `elm2node`. It finds `/usr/local/bin/elm2node` and then runs `/usr/local/bin/elm2node src/Main.elm` with whatever arguments I gave.
66 |
67 | So the `PATH` environment variable is a convention that allows you to refer to a specific executable file without knowing exactly where it lives on your computer. This is actually how all "terminal commands" work! Commands like `ls` are really executable files that live in directories listed in your `PATH` variable.
68 |
69 | So the point of running `sudo mv elm2node /usr/local/bin/` is to turn the `elm` binary into a terminal command, allowing us to call it just like `ls` and `cd`.
70 |
71 | **Note:** Why do we need to use `sudo` for that one command? Imagine if some program was able to add executables named `ls` or `cd` to `/usr/local/bin` that did something tricky and unexpected. That would be a security problem! Many distributions make this scenario less likely by requiring special permissions to modify the `/usr/local/bin/` directory.
72 |
73 |
74 |
75 |
76 | ## Uninstall
77 |
78 | The following command should remove everything:
79 |
80 | ```bash
81 | # Remove the `elm2node` executable.
82 | #
83 | sudo rm /usr/local/bin/elm2node
84 | ```
85 |
86 |
87 |
88 | ## I did'nt write these instructions!
89 |
90 | These instructions are almost the same than [the ones to install the official elm compiler](https://github.com/elm/compiler/blob/master/installers/linux/README.md),
91 | I've just replace `elm` by `elm2node` at some places.
92 |
93 | I think these instructions are very well written and would looooove that all install instructions
94 | being so nice and instructive, instead of cryptic bash commands!
95 |
96 | So thank you Evan for those wonderfull instructions!
--------------------------------------------------------------------------------
/compiler/src/Reporting/Error/Main.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Reporting.Error.Main
4 | ( Error(..)
5 | , toReport
6 | )
7 | where
8 |
9 |
10 | import qualified Data.Name as Name
11 |
12 | import qualified AST.Canonical as Can
13 | import qualified Reporting.Annotation as A
14 | import qualified Reporting.Doc as D
15 | import qualified Reporting.Error.Canonicalize as E
16 | import qualified Reporting.Render.Code as Code
17 | import qualified Reporting.Render.Type as RT
18 | import qualified Reporting.Render.Type.Localizer as L
19 | import qualified Reporting.Report as Report
20 |
21 |
22 |
23 | -- ERROR
24 |
25 |
26 | data Error
27 | = BadType A.Region Can.Type
28 | | BadCycle A.Region Name.Name [Name.Name]
29 | | BadFlags A.Region Can.Type E.InvalidPayload
30 |
31 |
32 |
33 | -- TO REPORT
34 |
35 |
36 | toReport :: L.Localizer -> Code.Source -> Error -> Report.Report
37 | toReport localizer source err =
38 | case err of
39 | BadType region tipe ->
40 | Report.Report "BAD MAIN TYPE" region [] $
41 | Code.toSnippet source region Nothing
42 | (
43 | "I cannot handle this type of `main` value:"
44 | ,
45 | D.stack
46 | [ "The type of `main` value I am seeing is:"
47 | , D.indent 4 $ D.dullyellow $ RT.canToDoc localizer RT.None tipe
48 | , D.reflow $
49 | "I only know how to handle Html, Svg, and Programs\
50 | \ though. Modify `main` to be one of those types of values!"
51 | ]
52 | )
53 |
54 | BadCycle region name names ->
55 | Report.Report "BAD MAIN" region [] $
56 | Code.toSnippet source region Nothing
57 | (
58 | "A `main` definition cannot be defined in terms of itself."
59 | ,
60 | D.stack
61 | [ D.reflow $
62 | "It should be a boring value with no recursion. But\
63 | \ instead it is involved in this cycle of definitions:"
64 | , D.cycle 4 name names
65 | ]
66 | )
67 |
68 | BadFlags region _badType invalidPayload ->
69 | let
70 | formatDetails (aBadKindOfThing, butThatIsNoGood) =
71 | Report.Report "BAD FLAGS" region [] $
72 | Code.toSnippet source region Nothing
73 | (
74 | D.reflow $
75 | "Your `main` program wants " ++ aBadKindOfThing ++ " from JavaScript."
76 | ,
77 | butThatIsNoGood
78 | )
79 | in
80 | formatDetails $
81 | case invalidPayload of
82 | E.ExtendedRecord ->
83 | (
84 | "an extended record"
85 | ,
86 | D.reflow $
87 | "But the exact shape of the record must be known at compile time. No type variables!"
88 | )
89 |
90 | E.Function ->
91 | (
92 | "a function"
93 | ,
94 | D.reflow $
95 | "But if I allowed functions from JS, it would be possible to sneak\
96 | \ side-effects and runtime exceptions into Elm!"
97 | )
98 |
99 | E.TypeVariable name ->
100 | (
101 | "an unspecified type"
102 | ,
103 | D.reflow $
104 | "But type variables like `" ++ Name.toChars name ++ "` cannot be given as flags.\
105 | \ I need to know exactly what type of data I am getting, so I can guarantee that\
106 | \ unexpected data cannot sneak in and crash the Elm program."
107 | )
108 |
109 | E.UnsupportedType name ->
110 | (
111 | "a `" ++ Name.toChars name ++ "` value"
112 | ,
113 | D.stack
114 | [ D.reflow $ "I cannot handle that. The types that CAN be in flags include:"
115 | , D.indent 4 $
116 | D.reflow $
117 | "Ints, Floats, Bools, Strings, Maybes, Lists, Arrays,\
118 | \ tuples, records, and JSON values."
119 | , D.reflow $
120 | "Since JSON values can flow through, you can use JSON encoders and decoders\
121 | \ to allow other types through as well. More advanced users often just do\
122 | \ everything with encoders and decoders for more control and better errors."
123 | ]
124 | )
125 |
--------------------------------------------------------------------------------
/compiler/src/AST/Source.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module AST.Source
3 | ( Expr, Expr_(..), VarType(..)
4 | , Def(..)
5 | , Pattern, Pattern_(..)
6 | , Type, Type_(..)
7 | , Module(..)
8 | , getName
9 | , getImportName
10 | , Import(..)
11 | , Value(..)
12 | , Union(..)
13 | , Alias(..)
14 | , Infix(..)
15 | , Port(..)
16 | , Effects(..)
17 | , Manager(..)
18 | , Docs(..)
19 | , Comment(..)
20 | , Exposing(..)
21 | , Exposed(..)
22 | , Privacy(..)
23 | )
24 | where
25 |
26 |
27 | import Data.Name (Name)
28 | import qualified Data.Name as Name
29 |
30 | import qualified AST.Utils.Binop as Binop
31 | import qualified AST.Utils.Shader as Shader
32 | import qualified Elm.Float as EF
33 | import qualified Elm.String as ES
34 | import qualified Parse.Primitives as P
35 | import qualified Reporting.Annotation as A
36 |
37 |
38 |
39 | -- EXPRESSIONS
40 |
41 |
42 | type Expr = A.Located Expr_
43 |
44 |
45 | data Expr_
46 | = Chr ES.String
47 | | Str ES.String
48 | | Int Int
49 | | Float EF.Float
50 | | Var VarType Name
51 | | VarQual VarType Name Name
52 | | List [Expr]
53 | | Op Name
54 | | Negate Expr
55 | | Binops [(Expr, A.Located Name)] Expr
56 | | Lambda [Pattern] Expr
57 | | Call Expr [Expr]
58 | | If [(Expr, Expr)] Expr
59 | | Let [A.Located Def] Expr
60 | | Case Expr [(Pattern, Expr)]
61 | | Accessor Name
62 | | Access Expr (A.Located Name)
63 | | Update (A.Located Name) [(A.Located Name, Expr)]
64 | | Record [(A.Located Name, Expr)]
65 | | Unit
66 | | Tuple Expr Expr [Expr]
67 | | Shader Shader.Source Shader.Types
68 |
69 |
70 | data VarType = LowVar | CapVar
71 |
72 |
73 |
74 | -- DEFINITIONS
75 |
76 |
77 | data Def
78 | = Define (A.Located Name) [Pattern] Expr (Maybe Type)
79 | | Destruct Pattern Expr
80 |
81 |
82 |
83 | -- PATTERN
84 |
85 |
86 | type Pattern = A.Located Pattern_
87 |
88 |
89 | data Pattern_
90 | = PAnything
91 | | PVar Name
92 | | PRecord [A.Located Name]
93 | | PAlias Pattern (A.Located Name)
94 | | PUnit
95 | | PTuple Pattern Pattern [Pattern]
96 | | PCtor A.Region Name [Pattern]
97 | | PCtorQual A.Region Name Name [Pattern]
98 | | PList [Pattern]
99 | | PCons Pattern Pattern
100 | | PChr ES.String
101 | | PStr ES.String
102 | | PInt Int
103 |
104 |
105 |
106 | -- TYPE
107 |
108 |
109 | type Type =
110 | A.Located Type_
111 |
112 |
113 | data Type_
114 | = TLambda Type Type
115 | | TVar Name
116 | | TType A.Region Name [Type]
117 | | TTypeQual A.Region Name Name [Type]
118 | | TRecord [(A.Located Name, Type)] (Maybe (A.Located Name))
119 | | TUnit
120 | | TTuple Type Type [Type]
121 |
122 |
123 |
124 | -- MODULE
125 |
126 |
127 | data Module =
128 | Module
129 | { _name :: Maybe (A.Located Name)
130 | , _exports :: A.Located Exposing
131 | , _docs :: Docs
132 | , _imports :: [Import]
133 | , _values :: [A.Located Value]
134 | , _unions :: [A.Located Union]
135 | , _aliases :: [A.Located Alias]
136 | , _binops :: [A.Located Infix]
137 | , _effects :: Effects
138 | }
139 |
140 |
141 | getName :: Module -> Name
142 | getName (Module maybeName _ _ _ _ _ _ _ _) =
143 | case maybeName of
144 | Just (A.At _ name) ->
145 | name
146 |
147 | Nothing ->
148 | Name._Main
149 |
150 |
151 | getImportName :: Import -> Name
152 | getImportName (Import (A.At _ name) _ _) =
153 | name
154 |
155 |
156 | data Import =
157 | Import
158 | { _import :: A.Located Name
159 | , _alias :: Maybe Name
160 | , _exposing :: Exposing
161 | }
162 |
163 |
164 | data Value = Value (A.Located Name) [Pattern] Expr (Maybe Type)
165 | data Union = Union (A.Located Name) [A.Located Name] [(A.Located Name, [Type])]
166 | data Alias = Alias (A.Located Name) [A.Located Name] Type
167 | data Infix = Infix Name Binop.Associativity Binop.Precedence Name
168 | data Port = Port (A.Located Name) Type
169 |
170 |
171 | data Effects
172 | = NoEffects
173 | | Ports [Port]
174 | | Manager A.Region Manager
175 |
176 |
177 | data Manager
178 | = Cmd (A.Located Name)
179 | | Sub (A.Located Name)
180 | | Fx (A.Located Name) (A.Located Name)
181 |
182 |
183 | data Docs
184 | = NoDocs A.Region
185 | | YesDocs Comment [(Name, Comment)]
186 |
187 |
188 | newtype Comment =
189 | Comment P.Snippet
190 |
191 |
192 |
193 | -- EXPOSING
194 |
195 |
196 | data Exposing
197 | = Open
198 | | Explicit [Exposed]
199 |
200 |
201 | data Exposed
202 | = Lower (A.Located Name)
203 | | Upper (A.Located Name) Privacy
204 | | Operator A.Region Name
205 |
206 |
207 | data Privacy
208 | = Public A.Region
209 | | Private
210 |
--------------------------------------------------------------------------------
/hints/missing-patterns.md:
--------------------------------------------------------------------------------
1 |
2 | # Hints for Missing Patterns
3 |
4 | Elm checks to make sure that all possible inputs to a function or `case` are handled. This gives us the guarantee that no Elm code is ever going to crash because data had an unexpected shape.
5 |
6 | There are a couple techniques for making this work for you in every scenario.
7 |
8 |
9 | ## The danger of wildcard patterns
10 |
11 | A common scenario is that you want to add a tag to a custom type that is used in a bunch of places. For example, maybe you are working different variations of users in a chat room:
12 |
13 | ```elm
14 | type User
15 | = Regular String Int
16 | | Anonymous
17 |
18 | toName : User -> String
19 | toName user =
20 | case user of
21 | Regular name _ ->
22 | name
23 |
24 | _ ->
25 | "anonymous"
26 | ```
27 |
28 | Notice the wildcard pattern in `toName`. This will hurt us! Say we add a `Visitor String` variant to `User` at some point. Now we have a bug that visitor names are reported as `"anonymous"`, and the compiler cannot help us!
29 |
30 | So instead, it is better to explicitly list all possible variants, like this:
31 |
32 | ```elm
33 | type User
34 | = Regular String Int
35 | | Visitor String
36 | | Anonymous
37 |
38 | toName : User -> String
39 | toName user =
40 | case user of
41 | Regular name _ ->
42 | name
43 |
44 | Anonymous ->
45 | "anonymous"
46 | ```
47 |
48 | Now the compiler will say "hey, what should `toName` do when it sees a `Visitor`?" This is a tiny bit of extra work, but it is very worth it!
49 |
50 |
51 | ## I want to go fast!
52 |
53 | Imagine that the `User` type appears in 20 or 30 functions across your project. When we add a `Visitor` variant, the compiler points out all the places that need to be updated. That is very convenient, but in a big project, maybe you want to get through it extra quickly.
54 |
55 | In that case, it can be helpful to use [`Debug.todo`](https://package.elm-lang.org/packages/elm-lang/core/latest/Debug#todo) to leave some code incomplete:
56 |
57 | ```elm
58 | type User
59 | = Regular String Int
60 | | Visitor String
61 | | Anonymous
62 |
63 | toName : User -> String
64 | toName user =
65 | case user of
66 | Regular name _ ->
67 | name
68 |
69 | Visitor _ ->
70 | Debug.todo "give the visitor name"
71 |
72 | Anonymous ->
73 | "anonymous"
74 |
75 | -- and maybe a bunch of other things
76 | ```
77 |
78 | In this case it is easier to just write the implementation, but the point is that on more complex functions, you can put things off a bit.
79 |
80 | The Elm compiler is actually aware of `Debug.todo` so when it sees it in a `case` like this, it will crash with a bunch of helpful information. It will tell you:
81 |
82 | 1. The name of the module that contains the code.
83 | 2. The line numbers of the `case` containing the TODO.
84 | 3. The particular value that led to this TODO.
85 |
86 | From that information you have a pretty good idea of what went wrong and can go fix it.
87 |
88 | I tend to use `Debug.todo` as the message when my goal is to go quick because it makes it easy to go and find all remaining todos in my code before a release.
89 |
90 |
91 | ## A list that definitely is not empty
92 |
93 | This can come up from time to time, but Elm **will not** let you write code like this:
94 |
95 | ```elm
96 | last : List a -> a
97 | last list =
98 | case list of
99 | [x] ->
100 | x
101 |
102 | _ :: rest ->
103 | last rest
104 | ```
105 |
106 | This is no good. It does not handle the empty list. There are two ways to handle this. One is to make the function return a `Maybe` like this:
107 |
108 | ```elm
109 | last : List a -> Maybe a
110 | last list =
111 | case list of
112 | [] ->
113 | Nothing
114 |
115 | [x] ->
116 | Just x
117 |
118 | _ :: rest ->
119 | last rest
120 | ```
121 |
122 | This is nice because it lets users know that there might be a failure, so they can recover from it however they want.
123 |
124 | The other option is to “unroll the list” one level to ensure that no one can ever provide an empty list in the first place:
125 |
126 | ```elm
127 | last : a -> List a -> a
128 | last first rest =
129 | case rest of
130 | [] ->
131 | first
132 |
133 | newFirst :: newRest ->
134 | last newFirst newRest
135 | ```
136 |
137 | By demanding the first element of the list as an argument, it becomes impossible to call this function if you have an empty list!
138 |
139 | This “unroll the list” trick is quite useful. I recommend using it directly, not through some external library. It is nothing special. Just a useful idea!
140 |
--------------------------------------------------------------------------------
/worker/src/Artifacts.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Artifacts
3 | ( Artifacts(..)
4 | , loadCompile
5 | , loadRepl
6 | , toDepsInfo
7 | )
8 | where
9 |
10 |
11 | import Control.Concurrent (readMVar)
12 | import Control.Monad (liftM2)
13 | import qualified Data.ByteString as BS
14 | import qualified Data.ByteString.Builder as B
15 | import qualified Data.ByteString.Lazy as LBS
16 | import qualified Data.Map as Map
17 | import qualified Data.Name as N
18 | import qualified Data.OneOrMore as OneOrMore
19 | import qualified System.Directory as Dir
20 | import System.FilePath ((>))
21 |
22 | import qualified AST.Canonical as Can
23 | import qualified AST.Optimized as Opt
24 | import qualified BackgroundWriter as BW
25 | import qualified Elm.Details as Details
26 | import qualified Elm.Interface as I
27 | import qualified Elm.ModuleName as ModuleName
28 | import qualified Elm.Package as Pkg
29 | import Json.Encode ((==>))
30 | import qualified Json.Encode as E
31 | import qualified Json.String as Json
32 | import qualified Reporting
33 |
34 |
35 |
36 | -- ARTIFACTS
37 |
38 |
39 | data Artifacts =
40 | Artifacts
41 | { _ifaces :: Map.Map ModuleName.Raw I.Interface
42 | , _graph :: Opt.GlobalGraph
43 | }
44 |
45 |
46 | loadCompile :: IO Artifacts
47 | loadCompile =
48 | load ("outlines" > "compile")
49 |
50 |
51 | loadRepl :: IO Artifacts
52 | loadRepl =
53 | load ("outlines" > "repl")
54 |
55 |
56 |
57 | -- LOAD
58 |
59 |
60 | load :: FilePath -> IO Artifacts
61 | load dir =
62 | BW.withScope $ \scope ->
63 | do putStrLn $ "Loading " ++ dir > "elm.json"
64 | style <- Reporting.terminal
65 | root <- fmap (> dir) Dir.getCurrentDirectory
66 | result <- Details.load style scope root
67 | case result of
68 | Left _ ->
69 | error $ "Ran into some problem loading elm.json\nTry running `elm make` in: " ++ dir
70 |
71 | Right details ->
72 | do omvar <- Details.loadObjects root details
73 | imvar <- Details.loadInterfaces root details
74 | mdeps <- readMVar imvar
75 | mobjs <- readMVar omvar
76 | case liftM2 (,) mdeps mobjs of
77 | Nothing ->
78 | error $ "Ran into some weird problem loading elm.json\nTry running `elm make` in: " ++ dir
79 |
80 | Just (deps, objs) ->
81 | return $ Artifacts (toInterfaces deps) objs
82 |
83 |
84 | toInterfaces :: Map.Map ModuleName.Canonical I.DependencyInterface -> Map.Map ModuleName.Raw I.Interface
85 | toInterfaces deps =
86 | Map.mapMaybe toUnique $ Map.fromListWith OneOrMore.more $
87 | Map.elems (Map.mapMaybeWithKey getPublic deps)
88 |
89 |
90 | getPublic :: ModuleName.Canonical -> I.DependencyInterface -> Maybe (ModuleName.Raw, OneOrMore.OneOrMore I.Interface)
91 | getPublic (ModuleName.Canonical _ name) dep =
92 | case dep of
93 | I.Public iface -> Just (name, OneOrMore.one iface)
94 | I.Private _ _ _ -> Nothing
95 |
96 |
97 | toUnique :: OneOrMore.OneOrMore a -> Maybe a
98 | toUnique oneOrMore =
99 | case oneOrMore of
100 | OneOrMore.One value -> Just value
101 | OneOrMore.More _ _ -> Nothing
102 |
103 |
104 |
105 | -- TO DEPS INFO
106 |
107 |
108 | toDepsInfo :: Artifacts -> BS.ByteString
109 | toDepsInfo (Artifacts ifaces _) =
110 | LBS.toStrict $ B.toLazyByteString $ E.encodeUgly $ encode ifaces
111 |
112 |
113 |
114 | -- ENCODE
115 |
116 |
117 | encode :: Map.Map ModuleName.Raw I.Interface -> E.Value
118 | encode ifaces =
119 | E.dict Json.fromName encodeInterface ifaces
120 |
121 |
122 | encodeInterface :: I.Interface -> E.Value
123 | encodeInterface (I.Interface pkg values unions aliases binops) =
124 | E.object
125 | [ "pkg" ==> E.chars (Pkg.toChars pkg)
126 | , "ops" ==> E.list E.name (Map.keys binops)
127 | , "values" ==> E.list E.name (Map.keys values)
128 | , "aliases" ==> E.list E.name (Map.keys (Map.filter isPublicAlias aliases))
129 | , "types" ==> E.dict Json.fromName (E.list E.name) (Map.mapMaybe toPublicUnion unions)
130 | ]
131 |
132 |
133 | isPublicAlias :: I.Alias -> Bool
134 | isPublicAlias alias =
135 | case alias of
136 | I.PublicAlias _ -> True
137 | I.PrivateAlias _ -> False
138 |
139 |
140 | toPublicUnion :: I.Union -> Maybe [N.Name]
141 | toPublicUnion union =
142 | case union of
143 | I.OpenUnion (Can.Union _ variants _ _) ->
144 | Just (map getVariantName variants)
145 |
146 | I.ClosedUnion _ ->
147 | Just []
148 |
149 | I.PrivateUnion _ ->
150 | Nothing
151 |
152 |
153 | getVariantName :: Can.Ctor -> N.Name
154 | getVariantName (Can.Ctor name _ _ _) =
155 | name
156 |
--------------------------------------------------------------------------------
/compiler/src/Type/UnionFind.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -funbox-strict-fields #-}
2 | {-# LANGUAGE BangPatterns #-}
3 | module Type.UnionFind
4 | ( Point
5 | , fresh
6 | , union
7 | , equivalent
8 | , redundant
9 | , get
10 | , set
11 | , modify
12 | )
13 | where
14 |
15 |
16 | {- This is based on the following implementations:
17 |
18 | - https://hackage.haskell.org/package/union-find-0.2/docs/src/Data-UnionFind-IO.html
19 | - http://yann.regis-gianas.org/public/mini/code_UnionFind.html
20 |
21 | It seems like the OCaml one came first, but I am not sure.
22 |
23 | Compared to the Haskell implementation, the major changes here include:
24 |
25 | 1. No more reallocating PointInfo when changing the weight
26 | 2. Using the strict modifyIORef
27 |
28 | -}
29 |
30 |
31 | import Control.Monad ( when )
32 | import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)
33 | import Data.Word (Word32)
34 |
35 |
36 |
37 | -- POINT
38 |
39 |
40 | newtype Point a =
41 | Pt (IORef (PointInfo a))
42 | deriving Eq
43 |
44 |
45 | data PointInfo a
46 | = Info {-# UNPACK #-} !(IORef Word32) {-# UNPACK #-} !(IORef a)
47 | | Link {-# UNPACK #-} !(Point a)
48 |
49 |
50 |
51 | -- HELPERS
52 |
53 |
54 | fresh :: a -> IO (Point a)
55 | fresh value =
56 | do weight <- newIORef 1
57 | desc <- newIORef value
58 | link <- newIORef (Info weight desc)
59 | return (Pt link)
60 |
61 |
62 | repr :: Point a -> IO (Point a)
63 | repr point@(Pt ref) =
64 | do pInfo <- readIORef ref
65 | case pInfo of
66 | Info _ _ ->
67 | return point
68 |
69 | Link point1@(Pt ref1) ->
70 | do point2 <- repr point1
71 | when (point2 /= point1) $
72 | do pInfo1 <- readIORef ref1
73 | writeIORef ref pInfo1
74 | return point2
75 |
76 |
77 | get :: Point a -> IO a
78 | get point@(Pt ref) =
79 | do pInfo <- readIORef ref
80 | case pInfo of
81 | Info _ descRef ->
82 | readIORef descRef
83 |
84 | Link (Pt ref1) ->
85 | do link' <- readIORef ref1
86 | case link' of
87 | Info _ descRef ->
88 | readIORef descRef
89 |
90 | Link _ ->
91 | get =<< repr point
92 |
93 |
94 | set :: Point a -> a -> IO ()
95 | set point@(Pt ref) newDesc =
96 | do pInfo <- readIORef ref
97 | case pInfo of
98 | Info _ descRef ->
99 | writeIORef descRef newDesc
100 |
101 | Link (Pt ref1) ->
102 | do link' <- readIORef ref1
103 | case link' of
104 | Info _ descRef ->
105 | writeIORef descRef newDesc
106 |
107 | Link _ ->
108 | do newPoint <- repr point
109 | set newPoint newDesc
110 |
111 |
112 | modify :: Point a -> (a -> a) -> IO ()
113 | modify point@(Pt ref) func =
114 | do pInfo <- readIORef ref
115 | case pInfo of
116 | Info _ descRef ->
117 | modifyIORef' descRef func
118 |
119 | Link (Pt ref1) ->
120 | do link' <- readIORef ref1
121 | case link' of
122 | Info _ descRef ->
123 | modifyIORef' descRef func
124 |
125 | Link _ ->
126 | do newPoint <- repr point
127 | modify newPoint func
128 |
129 |
130 | union :: Point a -> Point a -> a -> IO ()
131 | union p1 p2 newDesc =
132 | do point1@(Pt ref1) <- repr p1
133 | point2@(Pt ref2) <- repr p2
134 |
135 | Info w1 d1 <- readIORef ref1
136 | Info w2 d2 <- readIORef ref2
137 |
138 | if point1 == point2
139 | then writeIORef d1 newDesc
140 | else do
141 | weight1 <- readIORef w1
142 | weight2 <- readIORef w2
143 |
144 | let !newWeight = weight1 + weight2
145 |
146 | if weight1 >= weight2
147 | then
148 | do writeIORef ref2 (Link point1)
149 | writeIORef w1 newWeight
150 | writeIORef d1 newDesc
151 | else
152 | do writeIORef ref1 (Link point2)
153 | writeIORef w2 newWeight
154 | writeIORef d2 newDesc
155 |
156 |
157 | equivalent :: Point a -> Point a -> IO Bool
158 | equivalent p1 p2 =
159 | do v1 <- repr p1
160 | v2 <- repr p2
161 | return (v1 == v2)
162 |
163 |
164 | redundant :: Point a -> IO Bool
165 | redundant (Pt ref) =
166 | do pInfo <- readIORef ref
167 | case pInfo of
168 | Info _ _ ->
169 | return False
170 |
171 | Link _ ->
172 | return True
173 |
--------------------------------------------------------------------------------
/compiler/src/Elm/Version.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE BangPatterns, UnboxedTuples #-}
3 | module Elm.Version
4 | ( Version(..)
5 | , one
6 | , max
7 | , compiler
8 | , bumpPatch
9 | , bumpMinor
10 | , bumpMajor
11 | , toChars
12 | --
13 | , decoder
14 | , encode
15 | --
16 | , parser
17 | )
18 | where
19 |
20 |
21 | import Prelude hiding (max)
22 | import Control.Monad (liftM3)
23 | import Data.Binary (Binary, get, put, getWord8, putWord8)
24 | import qualified Data.Version as Version
25 | import Data.Word (Word8, Word16)
26 | import Foreign.Ptr (Ptr, plusPtr, minusPtr)
27 | import qualified Paths_elm2node
28 |
29 | import qualified Json.Decode as D
30 | import qualified Json.Encode as E
31 | import qualified Parse.Primitives as P
32 | import Parse.Primitives (Row, Col)
33 |
34 |
35 |
36 | -- VERSION
37 |
38 |
39 | data Version =
40 | Version
41 | { _major :: {-# UNPACK #-} !Word16
42 | , _minor :: {-# UNPACK #-} !Word16
43 | , _patch :: {-# UNPACK #-} !Word16
44 | }
45 | deriving (Eq, Ord)
46 |
47 |
48 | one :: Version
49 | one =
50 | Version 1 0 0
51 |
52 |
53 | max :: Version
54 | max =
55 | Version maxBound 0 0
56 |
57 |
58 | compiler :: Version
59 | compiler =
60 | case map fromIntegral (Version.versionBranch Paths_elm2node.version) of
61 | major : minor : patch : _ ->
62 | Version major minor patch
63 |
64 | [major, minor] ->
65 | Version major minor 0
66 |
67 | [major] ->
68 | Version major 0 0
69 |
70 | [] ->
71 | error "could not detect version of elm-compiler you are using"
72 |
73 |
74 |
75 | -- BUMP
76 |
77 |
78 | bumpPatch :: Version -> Version
79 | bumpPatch (Version major minor patch) =
80 | Version major minor (patch + 1)
81 |
82 |
83 | bumpMinor :: Version -> Version
84 | bumpMinor (Version major minor _patch) =
85 | Version major (minor + 1) 0
86 |
87 |
88 | bumpMajor :: Version -> Version
89 | bumpMajor (Version major _minor _patch) =
90 | Version (major + 1) 0 0
91 |
92 |
93 |
94 | -- TO CHARS
95 |
96 |
97 | toChars :: Version -> [Char]
98 | toChars (Version major minor patch) =
99 | show major ++ '.' : show minor ++ '.' : show patch
100 |
101 |
102 |
103 | -- JSON
104 |
105 |
106 | decoder :: D.Decoder (Row, Col) Version
107 | decoder =
108 | D.customString parser (,)
109 |
110 |
111 | encode :: Version -> E.Value
112 | encode version =
113 | E.chars (toChars version)
114 |
115 |
116 |
117 | -- BINARY
118 |
119 |
120 | instance Binary Version where
121 | get =
122 | do word <- getWord8
123 | if word == 255
124 | then liftM3 Version get get get
125 | else
126 | do minor <- getWord8
127 | patch <- getWord8
128 | return (Version (fromIntegral word) (fromIntegral minor) (fromIntegral patch))
129 |
130 | put (Version major minor patch) =
131 | if major < 255 && minor < 256 && patch < 256 then
132 | do putWord8 (fromIntegral major)
133 | putWord8 (fromIntegral minor)
134 | putWord8 (fromIntegral patch)
135 | else
136 | do putWord8 255
137 | put major
138 | put minor
139 | put patch
140 |
141 |
142 |
143 | -- PARSER
144 |
145 |
146 | parser :: P.Parser (Row, Col) Version
147 | parser =
148 | do major <- numberParser
149 | P.word1 0x2E {-.-} (,)
150 | minor <- numberParser
151 | P.word1 0x2E {-.-} (,)
152 | patch <- numberParser
153 | return (Version major minor patch)
154 |
155 |
156 | numberParser :: P.Parser (Row, Col) Word16
157 | numberParser =
158 | P.Parser $ \(P.State src pos end indent row col) cok _ _ eerr ->
159 | if pos >= end then
160 | eerr row col (,)
161 | else
162 | let !word = P.unsafeIndex pos in
163 | if word == 0x30 {-0-} then
164 |
165 | let
166 | !newState = P.State src (plusPtr pos 1) end indent row (col + 1)
167 | in
168 | cok 0 newState
169 |
170 | else if isDigit word then
171 |
172 | let
173 | (# total, newPos #) = chompWord16 (plusPtr pos 1) end (fromIntegral (word - 0x30))
174 | !newState = P.State src newPos end indent row (col + fromIntegral (minusPtr newPos pos))
175 | in
176 | cok total newState
177 |
178 | else
179 | eerr row col (,)
180 |
181 |
182 | chompWord16 :: Ptr Word8 -> Ptr Word8 -> Word16 -> (# Word16, Ptr Word8 #)
183 | chompWord16 pos end total =
184 | if pos >= end then
185 | (# total, pos #)
186 | else
187 | let !word = P.unsafeIndex pos in
188 | if isDigit word then
189 | chompWord16 (plusPtr pos 1) end (10 * total + fromIntegral (word - 0x30))
190 | else
191 | (# total, pos #)
192 |
193 |
194 | isDigit :: Word8 -> Bool
195 | isDigit word =
196 | 0x30 {-0-} <= word && word <= 0x39 {-9-}
197 |
--------------------------------------------------------------------------------
/compiler/src/Json/String.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
2 | {-# LANGUAGE BangPatterns, EmptyDataDecls #-}
3 | module Json.String
4 | ( String
5 | , isEmpty
6 | --
7 | , fromPtr
8 | , fromName
9 | , fromChars
10 | , fromSnippet
11 | , fromComment
12 | --
13 | , toChars
14 | , toBuilder
15 | )
16 | where
17 |
18 |
19 | import Prelude hiding (String)
20 | import qualified Data.ByteString.Builder as B
21 | import qualified Data.Coerce as Coerce
22 | import qualified Data.Name as Name
23 | import qualified Data.Utf8 as Utf8
24 | import Data.Utf8 (MBA, newByteArray, copyFromPtr, freeze, writeWord8)
25 | import Data.Word (Word8)
26 | import Foreign.Ptr (Ptr, plusPtr, minusPtr)
27 | import Foreign.ForeignPtr (withForeignPtr)
28 | import GHC.Exts (RealWorld)
29 | import GHC.IO (stToIO, unsafeDupablePerformIO, unsafePerformIO)
30 | import GHC.ST (ST)
31 |
32 | import qualified Parse.Primitives as P
33 |
34 |
35 |
36 | -- JSON STRINGS
37 |
38 |
39 | -- INVARIANT: any Json.String is appropriately escaped already
40 | -- PERF: is this the right representation for Json.String? Maybe ByteString instead?
41 | --
42 | type String =
43 | Utf8.Utf8 JSON_STRING
44 |
45 |
46 | data JSON_STRING
47 |
48 |
49 | isEmpty :: String -> Bool
50 | isEmpty =
51 | Utf8.isEmpty
52 |
53 |
54 |
55 | -- FROM
56 |
57 |
58 | fromPtr :: Ptr Word8 -> Ptr Word8 -> String
59 | fromPtr =
60 | Utf8.fromPtr
61 |
62 |
63 | fromChars :: [Char] -> String
64 | fromChars =
65 | Utf8.fromChars
66 |
67 |
68 | fromSnippet :: P.Snippet -> String
69 | fromSnippet =
70 | Utf8.fromSnippet
71 |
72 |
73 | fromName :: Name.Name -> String
74 | fromName =
75 | Coerce.coerce
76 |
77 |
78 |
79 | -- TO
80 |
81 |
82 | toChars :: String -> [Char]
83 | toChars =
84 | Utf8.toChars
85 |
86 |
87 | {-# INLINE toBuilder #-}
88 | toBuilder :: String -> B.Builder
89 | toBuilder =
90 | Utf8.toBuilder
91 |
92 |
93 |
94 | -- FROM COMMENT
95 |
96 |
97 | fromComment :: P.Snippet -> String
98 | fromComment (P.Snippet fptr off len _ _) =
99 | unsafePerformIO $ withForeignPtr fptr $ \ptr ->
100 | let
101 | !pos = plusPtr ptr off
102 | !end = plusPtr pos len
103 | !str = fromChunks (chompChunks pos end pos [])
104 | in
105 | return str
106 |
107 |
108 | chompChunks :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> [Chunk] -> [Chunk]
109 | chompChunks pos end start revChunks =
110 | if pos >= end then
111 | reverse (addSlice start end revChunks)
112 | else
113 | let
114 | !word = P.unsafeIndex pos
115 | in
116 | case word of
117 | 0x0A {-\n-} -> chompEscape 0x6E {-n-} pos end start revChunks
118 | 0x22 {-"-} -> chompEscape 0x22 {-"-} pos end start revChunks
119 | 0x5C {-\-} -> chompEscape 0x5C {-\-} pos end start revChunks
120 | 0x0D {-\r-} ->
121 | let
122 | !newPos = plusPtr pos 1
123 | in
124 | chompChunks newPos end newPos (addSlice start pos revChunks)
125 |
126 | _ ->
127 | let
128 | !width = P.getCharWidth word
129 | !newPos = plusPtr pos width
130 | in
131 | chompChunks newPos end start revChunks
132 |
133 |
134 | chompEscape :: Word8 -> Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> [Chunk] -> [Chunk]
135 | chompEscape escape pos end start revChunks =
136 | let
137 | !pos1 = plusPtr pos 1
138 | in
139 | chompChunks pos1 end pos1 (Escape escape : addSlice start pos revChunks)
140 |
141 |
142 | addSlice :: Ptr Word8 -> Ptr Word8 -> [Chunk] -> [Chunk]
143 | addSlice start end revChunks =
144 | if start == end
145 | then revChunks
146 | else Slice start (minusPtr end start) : revChunks
147 |
148 |
149 |
150 | -- FROM CHUNKS
151 |
152 |
153 | data Chunk
154 | = Slice (Ptr Word8) Int
155 | | Escape Word8
156 |
157 |
158 | fromChunks :: [Chunk] -> String
159 | fromChunks chunks =
160 | unsafeDupablePerformIO (stToIO (
161 | do let !len = sum (map chunkToWidth chunks)
162 | mba <- newByteArray len
163 | writeChunks mba 0 chunks
164 | freeze mba
165 | ))
166 |
167 |
168 | chunkToWidth :: Chunk -> Int
169 | chunkToWidth chunk =
170 | case chunk of
171 | Slice _ len -> len
172 | Escape _ -> 2
173 |
174 |
175 | writeChunks :: MBA RealWorld -> Int -> [Chunk] -> ST RealWorld ()
176 | writeChunks mba offset chunks =
177 | case chunks of
178 | [] ->
179 | return ()
180 |
181 | chunk : chunks ->
182 | case chunk of
183 | Slice ptr len ->
184 | do copyFromPtr ptr mba offset len
185 | let !newOffset = offset + len
186 | writeChunks mba newOffset chunks
187 |
188 | Escape word ->
189 | do writeWord8 mba offset 0x5C {- \ -}
190 | writeWord8 mba (offset + 1) word
191 | let !newOffset = offset + 2
192 | writeChunks mba newOffset chunks
193 |
--------------------------------------------------------------------------------
/compiler/src/Optimize/Case.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | module Optimize.Case
3 | ( optimize
4 | )
5 | where
6 |
7 |
8 | import Control.Arrow (second)
9 | import qualified Data.Map as Map
10 | import Data.Map ((!))
11 | import qualified Data.Maybe as Maybe
12 | import qualified Data.Name as Name
13 |
14 | import qualified AST.Canonical as Can
15 | import qualified AST.Optimized as Opt
16 | import qualified Optimize.DecisionTree as DT
17 |
18 |
19 |
20 | -- OPTIMIZE A CASE EXPRESSION
21 |
22 |
23 | optimize :: Name.Name -> Name.Name -> [(Can.Pattern, Opt.Expr)] -> Opt.Expr
24 | optimize temp root optBranches =
25 | let
26 | (patterns, indexedBranches) =
27 | unzip (zipWith indexify [0..] optBranches)
28 |
29 | decider = treeToDecider (DT.compile patterns)
30 | targetCounts = countTargets decider
31 |
32 | (choices, maybeJumps) =
33 | unzip (map (createChoices targetCounts) indexedBranches)
34 | in
35 | Opt.Case temp root
36 | (insertChoices (Map.fromList choices) decider)
37 | (Maybe.catMaybes maybeJumps)
38 |
39 |
40 | indexify :: Int -> (a,b) -> ((a,Int), (Int,b))
41 | indexify index (pattern, branch) =
42 | ( (pattern, index)
43 | , (index, branch)
44 | )
45 |
46 |
47 |
48 | -- TREE TO DECIDER
49 | --
50 | -- Decision trees may have some redundancies, so we convert them to a Decider
51 | -- which has special constructs to avoid code duplication when possible.
52 |
53 |
54 | treeToDecider :: DT.DecisionTree -> Opt.Decider Int
55 | treeToDecider tree =
56 | case tree of
57 | DT.Match target ->
58 | Opt.Leaf target
59 |
60 | -- zero options
61 | DT.Decision _ [] Nothing ->
62 | error "compiler bug, somehow created an empty decision tree"
63 |
64 | -- one option
65 | DT.Decision _ [(_, subTree)] Nothing ->
66 | treeToDecider subTree
67 |
68 | DT.Decision _ [] (Just subTree) ->
69 | treeToDecider subTree
70 |
71 | -- two options
72 | DT.Decision path [(test, successTree)] (Just failureTree) ->
73 | toChain path test successTree failureTree
74 |
75 | DT.Decision path [(test, successTree), (_, failureTree)] Nothing ->
76 | toChain path test successTree failureTree
77 |
78 | -- many options
79 | DT.Decision path edges Nothing ->
80 | let
81 | (necessaryTests, fallback) =
82 | (init edges, snd (last edges))
83 | in
84 | Opt.FanOut
85 | path
86 | (map (second treeToDecider) necessaryTests)
87 | (treeToDecider fallback)
88 |
89 | DT.Decision path edges (Just fallback) ->
90 | Opt.FanOut path (map (second treeToDecider) edges) (treeToDecider fallback)
91 |
92 |
93 | toChain :: DT.Path -> DT.Test -> DT.DecisionTree -> DT.DecisionTree -> Opt.Decider Int
94 | toChain path test successTree failureTree =
95 | let
96 | failure =
97 | treeToDecider failureTree
98 | in
99 | case treeToDecider successTree of
100 | Opt.Chain testChain success subFailure | failure == subFailure ->
101 | Opt.Chain ((path, test) : testChain) success failure
102 |
103 | success ->
104 | Opt.Chain [(path, test)] success failure
105 |
106 |
107 |
108 | -- INSERT CHOICES
109 | --
110 | -- If a target appears exactly once in a Decider, the corresponding expression
111 | -- can be inlined. Whether things are inlined or jumps is called a "choice".
112 |
113 |
114 | countTargets :: Opt.Decider Int -> Map.Map Int Int
115 | countTargets decisionTree =
116 | case decisionTree of
117 | Opt.Leaf target ->
118 | Map.singleton target 1
119 |
120 | Opt.Chain _ success failure ->
121 | Map.unionWith (+) (countTargets success) (countTargets failure)
122 |
123 | Opt.FanOut _ tests fallback ->
124 | Map.unionsWith (+) (map countTargets (fallback : map snd tests))
125 |
126 |
127 | createChoices
128 | :: Map.Map Int Int
129 | -> (Int, Opt.Expr)
130 | -> ( (Int, Opt.Choice), Maybe (Int, Opt.Expr) )
131 | createChoices targetCounts (target, branch) =
132 | if targetCounts ! target == 1 then
133 | ( (target, Opt.Inline branch)
134 | , Nothing
135 | )
136 |
137 | else
138 | ( (target, Opt.Jump target)
139 | , Just (target, branch)
140 | )
141 |
142 |
143 | insertChoices
144 | :: Map.Map Int Opt.Choice
145 | -> Opt.Decider Int
146 | -> Opt.Decider Opt.Choice
147 | insertChoices choiceDict decider =
148 | let
149 | go =
150 | insertChoices choiceDict
151 | in
152 | case decider of
153 | Opt.Leaf target ->
154 | Opt.Leaf (choiceDict ! target)
155 |
156 | Opt.Chain testChain success failure ->
157 | Opt.Chain testChain (go success) (go failure)
158 |
159 | Opt.FanOut path tests fallback ->
160 | Opt.FanOut path (map (second go) tests) (go fallback)
161 |
162 |
--------------------------------------------------------------------------------
/compiler/src/Optimize/Names.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | {-# LANGUAGE Rank2Types #-}
4 | module Optimize.Names
5 | ( Tracker
6 | , run
7 | , generate
8 | , registerKernel
9 | , registerGlobal
10 | , registerDebug
11 | , registerCtor
12 | , registerField
13 | , registerFieldDict
14 | , registerFieldList
15 | )
16 | where
17 |
18 |
19 | import qualified Data.Map as Map
20 | import qualified Data.Name as Name
21 | import qualified Data.Set as Set
22 |
23 | import qualified AST.Canonical as Can
24 | import qualified AST.Optimized as Opt
25 | import qualified Data.Index as Index
26 | import qualified Elm.ModuleName as ModuleName
27 | import qualified Reporting.Annotation as A
28 |
29 |
30 |
31 | -- GENERATOR
32 |
33 |
34 | newtype Tracker a =
35 | Tracker (
36 | forall r.
37 | Int
38 | -> Set.Set Opt.Global
39 | -> Map.Map Name.Name Int
40 | -> (Int -> Set.Set Opt.Global -> Map.Map Name.Name Int -> a -> r)
41 | -> r
42 | )
43 |
44 |
45 | run :: Tracker a -> (Set.Set Opt.Global, Map.Map Name.Name Int, a)
46 | run (Tracker k) =
47 | k 0 Set.empty Map.empty
48 | (\_uid deps fields value -> (deps, fields, value))
49 |
50 |
51 | generate :: Tracker Name.Name
52 | generate =
53 | Tracker $ \uid deps fields ok ->
54 | ok (uid + 1) deps fields (Name.fromVarIndex uid)
55 |
56 |
57 | registerKernel :: Name.Name -> a -> Tracker a
58 | registerKernel home value =
59 | Tracker $ \uid deps fields ok ->
60 | ok uid (Set.insert (Opt.toKernelGlobal home) deps) fields value
61 |
62 |
63 | registerGlobal :: ModuleName.Canonical -> Name.Name -> Tracker Opt.Expr
64 | registerGlobal home name =
65 | Tracker $ \uid deps fields ok ->
66 | let global = Opt.Global home name in
67 | ok uid (Set.insert global deps) fields (Opt.VarGlobal global)
68 |
69 |
70 | registerDebug :: Name.Name -> ModuleName.Canonical -> A.Region -> Tracker Opt.Expr
71 | registerDebug name home region =
72 | Tracker $ \uid deps fields ok ->
73 | let global = Opt.Global ModuleName.debug name in
74 | ok uid (Set.insert global deps) fields (Opt.VarDebug name home region Nothing)
75 |
76 |
77 | registerCtor :: ModuleName.Canonical -> Name.Name -> Index.ZeroBased -> Can.CtorOpts -> Tracker Opt.Expr
78 | registerCtor home name index opts =
79 | Tracker $ \uid deps fields ok ->
80 | let
81 | global = Opt.Global home name
82 | newDeps = Set.insert global deps
83 | in
84 | case opts of
85 | Can.Normal ->
86 | ok uid newDeps fields (Opt.VarGlobal global)
87 |
88 | Can.Enum ->
89 | ok uid newDeps fields $
90 | case name of
91 | "True" | home == ModuleName.basics -> Opt.Bool True
92 | "False" | home == ModuleName.basics -> Opt.Bool False
93 | _ -> Opt.VarEnum global index
94 |
95 | Can.Unbox ->
96 | ok uid (Set.insert identity newDeps) fields (Opt.VarBox global)
97 |
98 |
99 | identity :: Opt.Global
100 | identity =
101 | Opt.Global ModuleName.basics Name.identity
102 |
103 |
104 | registerField :: Name.Name -> a -> Tracker a
105 | registerField name value =
106 | Tracker $ \uid d fields ok ->
107 | ok uid d (Map.insertWith (+) name 1 fields) value
108 |
109 |
110 | registerFieldDict :: Map.Map Name.Name v -> a -> Tracker a
111 | registerFieldDict newFields value =
112 | Tracker $ \uid d fields ok ->
113 | ok uid d (Map.unionWith (+) fields (Map.map toOne newFields)) value
114 |
115 |
116 | toOne :: a -> Int
117 | toOne _ = 1
118 |
119 |
120 | registerFieldList :: [Name.Name] -> a -> Tracker a
121 | registerFieldList names value =
122 | Tracker $ \uid deps fields ok ->
123 | ok uid deps (foldr addOne fields names) value
124 |
125 |
126 | addOne :: Name.Name -> Map.Map Name.Name Int -> Map.Map Name.Name Int
127 | addOne name fields =
128 | Map.insertWith (+) name 1 fields
129 |
130 |
131 |
132 | -- INSTANCES
133 |
134 |
135 | instance Functor Tracker where
136 | fmap func (Tracker kv) =
137 | Tracker $ \n d f ok ->
138 | let
139 | ok1 n1 d1 f1 value =
140 | ok n1 d1 f1 (func value)
141 | in
142 | kv n d f ok1
143 |
144 |
145 | instance Applicative Tracker where
146 | {-# INLINE pure #-}
147 | pure value =
148 | Tracker $ \n d f ok -> ok n d f value
149 |
150 | (<*>) (Tracker kf) (Tracker kv) =
151 | Tracker $ \n d f ok ->
152 | let
153 | ok1 n1 d1 f1 func =
154 | let
155 | ok2 n2 d2 f2 value =
156 | ok n2 d2 f2 (func value)
157 | in
158 | kv n1 d1 f1 ok2
159 | in
160 | kf n d f ok1
161 |
162 |
163 | instance Monad Tracker where
164 | return = pure
165 |
166 | (>>=) (Tracker k) callback =
167 | Tracker $ \n d f ok ->
168 | let
169 | ok1 n1 d1 f1 a =
170 | case callback a of
171 | Tracker kb -> kb n1 d1 f1 ok
172 | in
173 | k n d f ok1
174 |
--------------------------------------------------------------------------------
/compiler/src/Elm/Compiler/Type.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Elm.Compiler.Type
4 | ( Type(..)
5 | , RT.Context(..)
6 | , toDoc
7 | , DebugMetadata(..)
8 | , Alias(..)
9 | , Union(..)
10 | , encode
11 | , decoder
12 | , encodeMetadata
13 | )
14 | where
15 |
16 |
17 | import qualified Data.Name as Name
18 |
19 | import qualified AST.Source as Src
20 | import qualified Json.Decode as D
21 | import qualified Json.Encode as E
22 | import Json.Encode ((==>))
23 | import qualified Json.String as Json
24 | import qualified Parse.Primitives as P
25 | import qualified Parse.Type as Type
26 | import qualified Reporting.Annotation as A
27 | import qualified Reporting.Doc as D
28 | import qualified Reporting.Render.Type as RT
29 | import qualified Reporting.Render.Type.Localizer as L
30 |
31 |
32 |
33 | -- TYPES
34 |
35 |
36 | data Type
37 | = Lambda Type Type
38 | | Var Name.Name
39 | | Type Name.Name [Type]
40 | | Record [(Name.Name, Type)] (Maybe Name.Name)
41 | | Unit
42 | | Tuple Type Type [Type]
43 |
44 |
45 | data DebugMetadata =
46 | DebugMetadata
47 | { _message :: Type
48 | , _aliases :: [Alias]
49 | , _unions :: [Union]
50 | }
51 |
52 |
53 | data Alias = Alias Name.Name [Name.Name] Type
54 | data Union = Union Name.Name [Name.Name] [(Name.Name, [Type])]
55 |
56 |
57 |
58 | -- TO DOC
59 |
60 |
61 | toDoc :: L.Localizer -> RT.Context -> Type -> D.Doc
62 | toDoc localizer context tipe =
63 | case tipe of
64 | Lambda _ _ ->
65 | let
66 | a:b:cs =
67 | map (toDoc localizer RT.Func) (collectLambdas tipe)
68 | in
69 | RT.lambda context a b cs
70 |
71 | Var name ->
72 | D.fromName name
73 |
74 | Unit ->
75 | "()"
76 |
77 | Tuple a b cs ->
78 | RT.tuple
79 | (toDoc localizer RT.None a)
80 | (toDoc localizer RT.None b)
81 | (map (toDoc localizer RT.None) cs)
82 |
83 | Type name args ->
84 | RT.apply
85 | context
86 | (D.fromName name)
87 | (map (toDoc localizer RT.App) args)
88 |
89 | Record fields ext ->
90 | RT.record
91 | (map (entryToDoc localizer) fields)
92 | (fmap D.fromName ext)
93 |
94 |
95 | entryToDoc :: L.Localizer -> (Name.Name, Type) -> (D.Doc, D.Doc)
96 | entryToDoc localizer (field, fieldType) =
97 | ( D.fromName field, toDoc localizer RT.None fieldType )
98 |
99 |
100 | collectLambdas :: Type -> [Type]
101 | collectLambdas tipe =
102 | case tipe of
103 | Lambda arg body ->
104 | arg : collectLambdas body
105 |
106 | _ ->
107 | [tipe]
108 |
109 |
110 |
111 | -- JSON for TYPE
112 |
113 |
114 | encode :: Type -> E.Value
115 | encode tipe =
116 | E.chars $ D.toLine (toDoc L.empty RT.None tipe)
117 |
118 |
119 | decoder :: D.Decoder () Type
120 | decoder =
121 | let
122 | parser =
123 | P.specialize (\_ _ _ -> ()) (fromRawType . fst <$> Type.expression)
124 | in
125 | D.customString parser (\_ _ -> ())
126 |
127 |
128 | fromRawType :: Src.Type -> Type
129 | fromRawType (A.At _ astType) =
130 | case astType of
131 | Src.TLambda t1 t2 ->
132 | Lambda (fromRawType t1) (fromRawType t2)
133 |
134 | Src.TVar x ->
135 | Var x
136 |
137 | Src.TUnit ->
138 | Unit
139 |
140 | Src.TTuple a b cs ->
141 | Tuple
142 | (fromRawType a)
143 | (fromRawType b)
144 | (map fromRawType cs)
145 |
146 | Src.TType _ name args ->
147 | Type name (map fromRawType args)
148 |
149 | Src.TTypeQual _ _ name args ->
150 | Type name (map fromRawType args)
151 |
152 | Src.TRecord fields ext ->
153 | let fromField (A.At _ field, tipe) = (field, fromRawType tipe) in
154 | Record
155 | (map fromField fields)
156 | (fmap A.toValue ext)
157 |
158 |
159 |
160 | -- JSON for PROGRAM
161 |
162 |
163 | encodeMetadata :: DebugMetadata -> E.Value
164 | encodeMetadata (DebugMetadata msg aliases unions) =
165 | E.object
166 | [ "message" ==> encode msg
167 | , "aliases" ==> E.object (map toTypeAliasField aliases)
168 | , "unions" ==> E.object (map toCustomTypeField unions)
169 | ]
170 |
171 |
172 | toTypeAliasField :: Alias -> ( Json.String, E.Value )
173 | toTypeAliasField (Alias name args tipe) =
174 | ( Json.fromName name
175 | , E.object
176 | [ "args" ==> E.list E.name args
177 | , "type" ==> encode tipe
178 | ]
179 | )
180 |
181 |
182 | toCustomTypeField :: Union -> ( Json.String, E.Value )
183 | toCustomTypeField (Union name args constructors) =
184 | ( Json.fromName name
185 | , E.object
186 | [ "args" ==> E.list E.name args
187 | , "tags" ==> E.object (map toVariantObject constructors)
188 | ]
189 | )
190 |
191 |
192 | toVariantObject :: (Name.Name, [Type]) -> ( Json.String, E.Value )
193 | toVariantObject (name, args) =
194 | ( Json.fromName name, E.list encode args )
195 |
--------------------------------------------------------------------------------
/compiler/src/Canonicalize/Type.hs:
--------------------------------------------------------------------------------
1 | {-# OPTIONS_GHC -Wall #-}
2 | {-# LANGUAGE OverloadedStrings #-}
3 | module Canonicalize.Type
4 | ( toAnnotation
5 | , canonicalize
6 | )
7 | where
8 |
9 |
10 | import qualified Data.List as List
11 | import qualified Data.Map as Map
12 | import qualified Data.Name as Name
13 |
14 | import qualified AST.Canonical as Can
15 | import qualified AST.Source as Src
16 | import qualified Canonicalize.Environment as Env
17 | import qualified Canonicalize.Environment.Dups as Dups
18 | import qualified Reporting.Annotation as A
19 | import qualified Reporting.Error.Canonicalize as Error
20 | import qualified Reporting.Result as Result
21 |
22 |
23 |
24 | -- RESULT
25 |
26 |
27 | type Result i w a =
28 | Result.Result i w Error.Error a
29 |
30 |
31 |
32 | -- TO ANNOTATION
33 |
34 |
35 | toAnnotation :: Env.Env -> Src.Type -> Result i w Can.Annotation
36 | toAnnotation env srcType =
37 | do tipe <- canonicalize env srcType
38 | Result.ok $ Can.Forall (addFreeVars Map.empty tipe) tipe
39 |
40 |
41 |
42 | -- CANONICALIZE TYPES
43 |
44 |
45 | canonicalize :: Env.Env -> Src.Type -> Result i w Can.Type
46 | canonicalize env (A.At typeRegion tipe) =
47 | case tipe of
48 | Src.TVar x ->
49 | Result.ok (Can.TVar x)
50 |
51 | Src.TType region name args ->
52 | canonicalizeType env typeRegion name args =<<
53 | Env.findType region env name
54 |
55 | Src.TTypeQual region home name args ->
56 | canonicalizeType env typeRegion name args =<<
57 | Env.findTypeQual region env home name
58 |
59 | Src.TLambda a b ->
60 | Can.TLambda
61 | <$> canonicalize env a
62 | <*> canonicalize env b
63 |
64 | Src.TRecord fields ext ->
65 | do cfields <- sequenceA =<< Dups.checkFields (canonicalizeFields env fields)
66 | return $ Can.TRecord cfields (fmap A.toValue ext)
67 |
68 | Src.TUnit ->
69 | Result.ok Can.TUnit
70 |
71 | Src.TTuple a b cs ->
72 | Can.TTuple
73 | <$> canonicalize env a
74 | <*> canonicalize env b
75 | <*>
76 | case cs of
77 | [] ->
78 | Result.ok Nothing
79 |
80 | [c] ->
81 | Just <$> canonicalize env c
82 |
83 | _ ->
84 | Result.throw $ Error.TupleLargerThanThree typeRegion
85 |
86 |
87 | canonicalizeFields :: Env.Env -> [(A.Located Name.Name, Src.Type)] -> [(A.Located Name.Name, Result i w Can.FieldType)]
88 | canonicalizeFields env fields =
89 | let
90 | len = fromIntegral (length fields)
91 | canonicalizeField index (name, srcType) =
92 | (name, Can.FieldType index <$> canonicalize env srcType)
93 | in
94 | zipWith canonicalizeField [0..len] fields
95 |
96 |
97 |
98 | -- CANONICALIZE TYPE
99 |
100 |
101 | canonicalizeType :: Env.Env -> A.Region -> Name.Name -> [Src.Type] -> Env.Type -> Result i w Can.Type
102 | canonicalizeType env region name args info =
103 | do cargs <- traverse (canonicalize env) args
104 | case info of
105 | Env.Alias arity home argNames aliasedType ->
106 | checkArity arity region name args $
107 | Can.TAlias home name (zip argNames cargs) (Can.Holey aliasedType)
108 |
109 | Env.Union arity home ->
110 | checkArity arity region name args $
111 | Can.TType home name cargs
112 |
113 |
114 | checkArity :: Int -> A.Region -> Name.Name -> [A.Located arg] -> answer -> Result i w answer
115 | checkArity expected region name args answer =
116 | let actual = length args in
117 | if expected == actual then
118 | Result.ok answer
119 | else
120 | Result.throw (Error.BadArity region Error.TypeArity name expected actual)
121 |
122 |
123 |
124 | -- ADD FREE VARS
125 |
126 |
127 | addFreeVars :: Map.Map Name.Name () -> Can.Type -> Map.Map Name.Name ()
128 | addFreeVars freeVars tipe =
129 | case tipe of
130 | Can.TLambda arg result ->
131 | addFreeVars (addFreeVars freeVars result) arg
132 |
133 | Can.TVar var ->
134 | Map.insert var () freeVars
135 |
136 | Can.TType _ _ args ->
137 | List.foldl' addFreeVars freeVars args
138 |
139 | Can.TRecord fields Nothing ->
140 | Map.foldl addFieldFreeVars freeVars fields
141 |
142 | Can.TRecord fields (Just ext) ->
143 | Map.foldl addFieldFreeVars (Map.insert ext () freeVars) fields
144 |
145 | Can.TUnit ->
146 | freeVars
147 |
148 | Can.TTuple a b maybeC ->
149 | case maybeC of
150 | Nothing ->
151 | addFreeVars (addFreeVars freeVars a) b
152 |
153 | Just c ->
154 | addFreeVars (addFreeVars (addFreeVars freeVars a) b) c
155 |
156 | Can.TAlias _ _ args _ ->
157 | List.foldl' (\fvs (_,arg) -> addFreeVars fvs arg) freeVars args
158 |
159 |
160 | addFieldFreeVars :: Map.Map Name.Name () -> Can.FieldType -> Map.Map Name.Name ()
161 | addFieldFreeVars freeVars (Can.FieldType _ tipe) =
162 | addFreeVars freeVars tipe
163 |
--------------------------------------------------------------------------------
/hints/recursive-alias.md:
--------------------------------------------------------------------------------
1 |
2 | # Hints for Recursive Type Aliases
3 |
4 | At the root of this issue is the distinction between a `type` and a `type alias`.
5 |
6 |
7 | ## What is a type alias?
8 |
9 | When you create a type alias, you are just creating a shorthand to refer to an existing type. So when you say the following:
10 |
11 | ```elm
12 | type alias Time = Float
13 |
14 | type alias Degree = Float
15 |
16 | type alias Weight = Float
17 | ```
18 |
19 | You have not created any *new* types, you just made some alternate names for `Float`. You can write down things like this and it'll work fine:
20 |
21 | ```elm
22 | add : Time -> Degree -> Weight
23 | add time degree =
24 | time + degree
25 | ```
26 |
27 | This is kind of a weird way to use type aliases though. The typical usage would be for records, where you do not want to write out the whole thing every time. Stuff like this:
28 |
29 | ```elm
30 | type alias Person =
31 | { name : String
32 | , age : Int
33 | , height : Float
34 | }
35 | ```
36 |
37 | It is much easier to write down `Person` in a type, and then it will just expand out to the underlying type when the compiler checks the program.
38 |
39 |
40 | ## Recursive type aliases?
41 |
42 | Okay, so let's say you have some type that may contain itself. In Elm, a common example of this is a comment that might have subcomments:
43 |
44 | ```elm
45 | type alias Comment =
46 | { message : String
47 | , upvotes : Int
48 | , downvotes : Int
49 | , responses : List Comment
50 | }
51 | ```
52 |
53 | Now remember that type *aliases* are just alternate names for the real type. So to make `Comment` into a concrete type, the compiler would start expanding it out.
54 |
55 | ```elm
56 | { message : String
57 | , upvotes : Int
58 | , downvotes : Int
59 | , responses :
60 | List
61 | { message : String
62 | , upvotes : Int
63 | , downvotes : Int
64 | , responses :
65 | List
66 | { message : String
67 | , upvotes : Int
68 | , downvotes : Int
69 | , responses : List ...
70 | }
71 | }
72 | }
73 | ```
74 |
75 | The compiler cannot deal with values like this. It would just keep expanding forever.
76 |
77 |
78 | ## Recursive types!
79 |
80 | In cases where you want a recursive type, you need to actually create a brand new type. This is what the `type` keyword is for. A simple example of this can be seen when defining a linked list:
81 |
82 | ```elm
83 | type List
84 | = Empty
85 | | Node Int List
86 | ```
87 |
88 | No matter what, the type of `Node n xs` is going to be `List`. There is no expansion to be done. This means you can represent recursive structures with types that do not explode into infinity.
89 |
90 | So let's return to wanting to represent a `Comment` that may have responses. There are a couple ways to do this:
91 |
92 |
93 | ### Obvious, but kind of annoying
94 |
95 | ```elm
96 | type Comment =
97 | Comment
98 | { message : String
99 | , upvotes : Int
100 | , downvotes : Int
101 | , responses : List Comment
102 | }
103 | ```
104 |
105 | Now let's say you want to register an upvote on a comment:
106 |
107 | ```elm
108 | upvote : Comment -> Comment
109 | upvote (Comment comment) =
110 | Comment { comment | upvotes = 1 + comment.upvotes }
111 | ```
112 |
113 | It is kind of annoying that we now have to unwrap and wrap the record to do anything with it.
114 |
115 |
116 | ### Less obvious, but nicer
117 |
118 | ```elm
119 | type alias Comment =
120 | { message : String
121 | , upvotes : Int
122 | , downvotes : Int
123 | , responses : Responses
124 | }
125 |
126 | type Responses = Responses (List Comment)
127 | ```
128 |
129 | In this world, we introduce the `Responses` type to capture the recursion, but `Comment` is still an alias for a record. This means the `upvote` function looks nice again:
130 |
131 | ```elm
132 | upvote : Comment -> Comment
133 | upvote comment =
134 | { comment | upvotes = 1 + comment.upvotes }
135 | ```
136 |
137 | So rather than having to unwrap a `Comment` to do *anything* to it, you only have to do some unwrapping in the cases where you are doing something recursive. In practice, this means you will do less unwrapping which is nice.
138 |
139 |
140 | ## Mutually recursive type aliases
141 |
142 | It is also possible to build type aliases that are *mutually* recursive. That might be something like this:
143 |
144 | ```elm
145 | type alias Comment =
146 | { message : String
147 | , upvotes : Int
148 | , downvotes : Int
149 | , responses : Responses
150 | }
151 |
152 | type alias Responses =
153 | { sortBy : SortBy
154 | , responses : List Comment
155 | }
156 |
157 | type SortBy = Time | Score | MostResponses
158 | ```
159 |
160 | When you try to expand `Comment` you have to expand `Responses` which needs to expand `Comment` which needs to expand `Responses`, etc.
161 |
162 | So this is just a fancy case of a self-recursive type alias. The solution is the same. Somewhere in that cycle, you need to define an actual `type` to end the infinite expansion.
163 |
--------------------------------------------------------------------------------