Skip to content

Nim for Haskell Programmers

dlesnoff edited this page Nov 26, 2023 · 10 revisions
DISCLAIMER!

This page is unofficial and incomplete. Contributions are appreciated.

official doc

Language Features

Feature Haskell Nim
Comments -- single line, {- multiline -}(nestable) # single line, #[multiline]# (nestable)
Blocks Uses space and tab or C-like Uses indents like Python, another option is statement list expression
Operators operator is function (use (+) a b , or a + b), Operator has precedence, infix by default, Unicode syntax command call syntax unicode operators
Operator overloading None Operators are user defined except = and ., can overload: subscripts, curly subscripts a{b}, experimental call and dot operators
If/else statement None if a: foo() else: bar()
If/else expression if test then a else b if test: a else: b
case expression case t of m1 -> a
otherwise -> b
case t
of m1: a
else: b
Exception many ways, use Control.Monad, try: foo() except Exception as ex: bar() finally: bar() - can omit as ex or Exception as ex
Procedure definition id a = a, id::a->a for declare proc foo(a: U, b: S): T = discard in module
Method definition None method foo(obj: Obj, a: U, b: S): T = discard in module
Calling procedure func a b, or a `func` b foo(a, b), foo a, b, a.foo(b), a.foo b
Calling method None foo(obj, a, b), foo obj, a, b, obj.foo(a, b), obj.foo a, b
Method/procedure declarations are order-agnostic Yes No, can use forward declarations, experimental code reordering
String literals "str" "str", """str""", foo"str" (raw string literals)
Collection literals list comprehension [(a, b) | b <- ['a'..'z'], a <- [1..50], even a], Overloaded string and list array [1, 2, 3], seq @[1, 2, 3], set {1, 2, 3}, tuple (1, 2, 3), table constructor
compiler output hi interface file, native assembly or llvm translate to C, C++, Objective C , JavaScript
major compiler ghc nim
major REPL ghci inim
stability old young
meta-programming template-haskell macro keyword
template use {-# LANGUAGE CPP #-} template keyword
pure function IO Monad func keyword or noSideEffect pragma

Data Types

Haskell

Haskell is a pure functional language, variables are immutable.

However, you can use State Monad or IORef to get a mutable-like behaviour.

let behaves like create new immutable variable

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
  let smaller = quicksort [a | a<-xs, a<=x]
      bigger  = quicksort [a | a<-xs, a>x]
  in  smaller ++ [x] ++ bigger

In addition, where is the same

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) = smaller ++ [x] ++ bigger
    where
       smaller = quicksort [a | a<-xs, a<=x]
       bigger  = quicksort [a | a<-xs, a>x]

Nim

var for mutable

let for immutable

const for compile-time symbol

var mutable = "some"
mutable &= " string"
let shadow_copy = mutable
const flags = ["--run", "--hints:off"]

Basic types present in Haskell and Nim:

Haskell Nim
Int8 int8
Int16 int16
Int32 int32
Int64 int64
Word8 uint8
Word16 uint16
Word32 uint32
Word64 uint64
Double float or float64 (same meaning)
Float float32
Ptr pointer
Bool bool

Available only in Haskell:

Haskell meaning
Integer Arbitrary precision integers
Ratio a numerator and denominator in type a
Rational aka Ratio Integer
Char represent Unicode code points
Complex a real and image number in type a
[a] List with element in type a
String aka [Char]

Only in Nim:

Nim meaning
char 1 byte character
string mutable chars
cstring pointer to memory, const char*
ptr[T] untraced pointer
ref[T] traced pointer
byte aka uint8

Nim has many additional types that enable the programmer to interface with C.

for Example: csize_t, cint, cshort

type inference

Both Haskell and Nim is static typed

Haskell's most widely used compiler (GHC) has strong type inference to determine what expression's type is

For example

Prelude> :t 25
25:: Num a => a
Prelude> add a b = a+b
Prelude> :t add
add :: Num a => a -> a -> a
Prelude> add 1 2
3
Prelude> add 1.0 2.0
3.0
Prelude> (add 1 2)::Int
3
Prelude> (add 1 2)::Integer
3
Prelude> (add 1 2)::Float
3.0
Prelude> (add 1 2)::Double
3.0

The + function' signature is Num a => a -> a -> a, so add's parameter a and b must instance of Num. Int, Integer, Float, Double are instances of Num type classes. Use ::Type to tell explicitly the compiler the type of an expression.

Prelude> :{
Prelude| repr::(Show a)=>a->String
Prelude| repr a = "repr " ++ (show a)
Prelude| :}
Prelude> repr 23
"repr 23"
Prelude> repr [1..5]
"repr [1,2,3,4,5]"
Prelude> repr (return ()::IO ())

<interactive>:7:1: error:
    * No instance for (Show (IO ())) arising from a use of `repr'
    * In the expression: repr (return () :: IO ())
      In an equation for `it': it = repr (return () :: IO ())

show's signature is Show a->String, we can say repr's parameter a must be an instance of the Show typeclass.

Prelude> a = []
Prelude> :t a
a :: [a]
Prelude> a ++ ['a'..'d']
"abcd"
Prelude> a ++ [1..5]
[1,2,3,4,5]

a :: [a] means a can be any type

Nim doesn't accept any type,

var arr: seq[int]
for i in 0..5:
  arr.add i
echo $arr

Lazyness

In Haskell, you can pass an expression to a function, and it's un-evaluated.

If you have an infinite list, you can take the first five elements, and calculate the result.

main = do
    let a = [0..]::[Int]
    print $ take 5 a

In Nim, an expression will be evaluated and passed to a function, so to pass large data to a function, you can pass by a pointer.

List example

type List[T] = object
   data: T
   next: ref List[T]

func fromArray[T](a: openarray[T]): ref List[T] = 
   new(result)
   var tmp = result
   tmp[].data = a[0]
   for i in 1..<len(a):
      new(tmp[].next)
      tmp = tmp.next
      tmp[].data = a[i]
   tmp.next = nil
   return result

proc `$`[T](a: ref List[T]): string = 
   result = "["
   var tmp = a
   while true:
     result.add($ tmp.data)
     if tmp.next==nil:
        break
     result.add ", "
     tmp = tmp.next
   result.add "]"

let a = fromArray [12, 45, 27, 64, 1024, 4096]

echo a

Documentation, libraries & popular tooling

Feature Haskell Nim
IDE support use Haskell Language Server, see VS Code, see editor support
Package manager cabal, stack, ghc-pkg Nimble
Library format hs source, hi interface, o object, see Source code, unused code is not included in binary (dead code elimination), can also compile to a shared library or static library
Style guide see NEP-1
Doc generator haddock nim doc, nim rst2html, nim tex, nim jsondoc, nim doc2tex
Unit testing HTF QuickCheck HUnit Standard library unittest module

Examples

Hello world

Haskell

main = do
  print "enter your name"
  a <- getLine
  print $ "hello " ++ a

compile

ghc Main.hs
./Main 
# or runghc Main.hs

Nim

echo "enter your name"
let a = stdin.readLine()
echo "hello " & a

compile

nim c Main.nim
./Main
# or nim c --run Main.nim

case

Haskell

case expression

case (parse "<string>" number "45") of (Right x) -> x
                                       (Left err) -> print err >> fail "parse error"

pattern matching

fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
fibs::[Integer]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
main = print $ take 10 fibs

Nim

var 
  a = 0
  b = 0
  res: int
let s = "+"

case s:
of "+": res = a + b 
of "-": res = a - b
else: res = -1

Algebraic Data Type

Haskell

data Value = IVal Integer | FVal Double deriving (Show, Eq)

data Expr
  = Var String
  | Lit Value
  | App Expr Expr
  | Lam String Expr
  deriving (Eq, Show)

Nim

type 
  ExprKind = enum
    Var, Lit, Lam, App
  Expr = ref object
    case kind: ExprKind
    of Var:
      name: string
    of Lit:
      val: float
    of App:
      a, b: Expr
    of Lam:
      n: string
      e: Expr

func eval(e: Expr): Expr = 
   case e.kind:
   of Var: ...

importing modules

{-# LANGUAGE Unsafe #-} -- since Unsafe.Coerce is unsafe, use LANGUAGE pragma to mark this module unsafe
module Main (hello) where -- expose hello function

import Unsafe.Coerce -- expose all symbols
import qualified Control.Monad.Writer as W -- W is the new name
import Data.ByteString hiding (putStrLn) -- readFile is not visible
import Data.Semigroup ((<>), Semigroup) -- import (<>) function and Semigroup typeclass

hello::String -- hello is type String, aka [Char]
{-# INLINABLE hello #-} -- tell ghc hello is inlinable
hello = "Hello "<>"world" -- the function body

main::IO () -- main is the entry point of the program, main is an empty tuple inside the IO Monad
main = putStrLn hello -- call putStrLn
{-
  {-
    multi-line comment
  -}
-}

Nim

import System # exposes all symbols
import System as S # S W is the new name
from System import create # import create
import System except int # import all symbols except int
import system as S except int # S is the new name, int is not imported

# hello is a procedure, return some string
proc hello(): string {.inline.} = "hello " & "world"

# three ways to call
# they are the same
echo hello()
hello().echo
echo(hello())

#[
  #[
       multi-line comment
  ]#
]#
var mutable = "Win32"
mutable &= "API"

let immutable = ["Nim", "PlayGround", "C"]

const compileTime = 2 shl 1024

echo $mutable, $immutable, $compileTime

templates

Haskell

{-# LANGUAGE CPP #-}

#define bind(a, f) a <- f
#define COUNT 5

main::IO ()

main = do
  putStrLn "enter a number->"
  bind(s, getLine)
  bind(num, readIO s)::IO Integer
  print $ scanl (+) 0 (take COUNT $ repeat num)

nim

template curry(a, b, c) = 
  a (b, c)

curry(echo, 1, 2)

macros

Haskell

Macro.hs

{-# LANGUAGE TemplateHaskell #-}
module Macro
  ( duplicate )
where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

duplicate :: String -> Q Exp
duplicate s = do
  [| fromString s++s |]

Main.hs

{-# LANGUAGE TemplateHaskell #-}

module Main where

import Macro
main :: IO ()
main = putStrLn $(duplicate "<>")

Nim

import macros

macro readContent(path: static[string]): untyped = 
  let c = staticRead path
  result = newLit c

stdout.write readContent("Main.nim")

FFI

Haskell

module Main where
import Prelude hiding (sin)
import Foreign.C.Types

-- stdcall in windows
foreign import ccall "math.h sin"
    sin::CDouble->CDouble

main = print $ map sin (take 50 (scanl (+) 0 (repeat 0.001)))

Nim

proc sin(a: cdouble): cdouble {.importc, header: "math.h", nodecl.}

var i = 0.cdouble
while i < 0.05:
  echo sin(i)
  i += 0.001
Clone this wiki locally