├── .gitignore ├── .travis.yml ├── Data └── NullPointer.hs ├── LICENSE ├── README.md ├── Setup.hs ├── acme-nullpointer.cabal ├── stack.yaml └── test └── Main.hs /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This Travis job script has been generated by a script via 2 | # 3 | # make_travis_yml_2.hs 'acme-nullpointer.cabal' 4 | # 5 | # For more information, see https://github.com/hvr/multi-ghc-travis 6 | # 7 | language: c 8 | sudo: false 9 | 10 | git: 11 | submodules: false # whether to recursively clone submodules 12 | 13 | cache: 14 | directories: 15 | - $HOME/.cabal/packages 16 | - $HOME/.cabal/store 17 | 18 | before_cache: 19 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log 20 | # remove files that are regenerated by 'cabal update' 21 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.* 22 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json 23 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache 24 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar 25 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx 26 | 27 | matrix: 28 | include: 29 | - compiler: "ghc-7.8.4" 30 | # env: TEST=--disable-tests BENCH=--disable-benchmarks 31 | addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-7.8.4], sources: [hvr-ghc]}} 32 | - compiler: "ghc-7.10.3" 33 | # env: TEST=--disable-tests BENCH=--disable-benchmarks 34 | addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-7.10.3], sources: [hvr-ghc]}} 35 | - compiler: "ghc-8.0.2" 36 | # env: TEST=--disable-tests BENCH=--disable-benchmarks 37 | addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.0.2], sources: [hvr-ghc]}} 38 | - compiler: "ghc-8.2.1" 39 | # env: TEST=--disable-tests BENCH=--disable-benchmarks 40 | addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.2.1], sources: [hvr-ghc]}} 41 | 42 | before_install: 43 | - HC=${CC} 44 | - unset CC 45 | - PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$PATH 46 | - PKGNAME='acme-nullpointer' 47 | 48 | install: 49 | - cabal --version 50 | - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]" 51 | - BENCH=${BENCH---enable-benchmarks} 52 | - TEST=${TEST---enable-tests} 53 | - travis_retry cabal update -v 54 | - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config 55 | - rm -fv cabal.project.local 56 | - "echo 'packages: .' > cabal.project" 57 | - rm -f cabal.project.freeze 58 | - cabal new-build -w ${HC} ${TEST} ${BENCH} --dep -j2 all 59 | - cabal new-build -w ${HC} --disable-tests --disable-benchmarks --dep -j2 all 60 | 61 | # Here starts the actual work to be performed for the package under test; 62 | # any command which exits with a non-zero exit code causes the build to fail. 63 | script: 64 | - if [ -f configure.ac ]; then autoreconf -i; fi 65 | - rm -rf .ghc.environment.* dist/ 66 | - cabal sdist # test that a source-distribution can be generated 67 | - cd dist/ 68 | - SRCTAR=(${PKGNAME}-*.tar.gz) 69 | - SRC_BASENAME="${SRCTAR/%.tar.gz}" 70 | - tar -xvf "./$SRC_BASENAME.tar.gz" 71 | - cd "$SRC_BASENAME/" 72 | ## from here on, CWD is inside the extracted source-tarball 73 | - rm -fv cabal.project.local 74 | - "echo 'packages: .' > cabal.project" 75 | # this builds all libraries and executables (without tests/benchmarks) 76 | - rm -f cabal.project.freeze 77 | - cabal new-build -w ${HC} --disable-tests --disable-benchmarks all 78 | # this builds all libraries and executables (including tests/benchmarks) 79 | # - rm -rf ./dist-newstyle 80 | 81 | # build & run tests 82 | - cabal new-build -w ${HC} ${TEST} ${BENCH} all 83 | - if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} all; fi 84 | 85 | # EOF 86 | -------------------------------------------------------------------------------- /Data/NullPointer.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MagicHash #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | 4 | -- | Adds @null@ value to any Haskell data type. 5 | module Data.NullPointer (null, isNull, NullPointerException(..)) where 6 | 7 | import Prelude hiding (null) 8 | import Data.Typeable 9 | import Control.Exception (Exception, throw) 10 | import GHC.Prim (reallyUnsafePtrEquality#) 11 | 12 | -- | The null value. 13 | -- When forced, a NullPointerException will be thrown. 14 | null :: a 15 | null = throw NullPointerException 16 | {-# NOINLINE null #-} 17 | 18 | -- | Check whether the given value is 'null'. 19 | isNull :: a -> Bool 20 | isNull x = 21 | case reallyUnsafePtrEquality# x null of 22 | 0# -> x `seq` False -- isNull should be strict in all values except null 23 | _ -> True 24 | 25 | -- | Thrown on attempt to use 'null'. 26 | data NullPointerException = NullPointerException deriving (Eq, Show, Typeable) 27 | 28 | instance Exception NullPointerException 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Maciej Bielecki 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Maciej Bielecki nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Data.NullPointer` ![build status](https://travis-ci.org/zyla/haskell-nullpointer.svg?branch=master) 2 | 3 | Tired of using `Maybe`? Wrapping values in `Just` makes you sick? 4 | Missing your `NullPointerException`s? 5 | Add null value to any data type using `Data.NullPointer`! 6 | 7 | ## Example 8 | 9 | ```haskell 10 | import Prelude hiding (null) 11 | import System.Environment (getArgs) 12 | import Data.NullPointer 13 | 14 | getFirstArg :: [String] -> String 15 | getFirstArg (x:_) = x 16 | getFirstArg [] = null 17 | 18 | main = do 19 | arg <- getFirstArg <$> getArgs 20 | 21 | if isNull arg 22 | then putStrLn "No argument passed" 23 | else putStrLn arg 24 | ``` 25 | 26 | ## What if I forget to check if a value is null? 27 | 28 | You'll get a `NullPointerException`, just like in Java! Brillant! 29 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /acme-nullpointer.cabal: -------------------------------------------------------------------------------- 1 | name: acme-nullpointer 2 | version: 0.1.0.0 3 | synopsis: Null pointers in Haskell 4 | 5 | description: Tired of using Maybe? Wrapping values in Just makes you sick? 6 | Missing your NullPointerExceptions? 7 | Add null value to any data type using Data.NullPointer! 8 | 9 | license: BSD3 10 | license-file: LICENSE 11 | author: Maciej Bielecki 12 | maintainer: zyla@prati.pl 13 | category: Data 14 | build-type: Simple 15 | cabal-version: >=1.10 16 | extra-source-files: README.md 17 | tested-with: 18 | GHC==7.10.3 19 | , GHC==7.8.4 20 | , GHC==8.0.2 21 | , GHC==8.2.1 22 | 23 | library 24 | exposed-modules: Data.NullPointer 25 | build-depends: base >=4.7 && <4.12, ghc-prim 26 | default-language: Haskell2010 27 | 28 | test-suite tests 29 | type: exitcode-stdio-1.0 30 | main-is: Main.hs 31 | hs-source-dirs: test 32 | build-depends: base, hspec, acme-nullpointer 33 | default-language: Haskell2010 34 | 35 | source-repository head 36 | type: git 37 | location: https://github.com/zyla/haskell-nullpointer 38 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-8.22 2 | packages: 3 | - '.' 4 | -------------------------------------------------------------------------------- /test/Main.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -O0 #-} 2 | module Main where 3 | 4 | import Prelude hiding (null) 5 | import Test.Hspec 6 | import Control.Exception 7 | import Data.NullPointer 8 | 9 | main = hspec $ do 10 | it "null is null" $ do 11 | isNull null `shouldBe` True 12 | 13 | it "other values are not null" $ do 14 | isNull "foo" `shouldBe` False 15 | isNull 42 `shouldBe` False 16 | 17 | it "using it throws NullPointerException" $ do 18 | evaluate null `shouldThrow` (== NullPointerException) 19 | 20 | it "undefined is not null" $ do 21 | evaluate (isNull undefined) `shouldThrow` anyException 22 | 23 | it "values using null are not null" $ do 24 | evaluate (isNull (1 + null)) `shouldThrow` (== NullPointerException) 25 | --------------------------------------------------------------------------------