Skip to content

Instantly share code, notes, and snippets.

@dktn
Last active March 25, 2021 11:10
Show Gist options
  • Select an option

  • Save dktn/f426fba662bb68ae268df07ce0544215 to your computer and use it in GitHub Desktop.

Select an option

Save dktn/f426fba662bb68ae268df07ce0544215 to your computer and use it in GitHub Desktop.

PureScript

strict by default

Links

Comparisons (used in this presentation)

Vs Haskell

  1. Prelude:
  • like {-# LANGUAGE NoImplicitPrelude #-}

  • Prelude also isn't distributed with PureScript compiler

  • Explicit dependency purescript-prelude and import it:

    import Prelude
  1. forall:
  • like {-# LANGUAGE ExplicitForAll #-}
  1. unicode:
  • unicode is allowed by default

  • basic unicode symbols included , , , , ,

  • we used

    infixr 9 compose asinfixr 9 composeFlipped as-- gave up at some point
    infixr 9 composeFlipped as-- in favour for this one (nicer symetry)
    
    compose2 :: forall a b c d g. Semigroupoid g => g b a -> (d -> g c b) -> d -> g c a
    compose2 f g = (f ∘ _) ∘ g
    
    infixr 9 compose2 as ∘∘
    
    infixl 9 applyFlipped as |>
  • also

    (^|>)
    (^|>^)
    (|>^)
    ($^)
    (^$)
    (^$^)
    (^#)
    (#^)
    (^#^)
    (^⟡)
    (⟡^)
    (⟡^⟡)
    (^⟡^⟡)
    (⟡^⟡^)
    (^⟡^⟡^)
    (^∘)
    (∘^)
    (∘^∘)
    (^∘^∘)
    (∘^∘^)
    (^∘^∘^)
    (^<$>)
    (<#>^)
    
  1. Basic operators:

    Haskell PureScript Restaum
    (.) (<<<) (∘)
    flip (.) (>>>) (⟡)
    (&) (#)
    (<&>) (<#>)
    a >> b a *> b
    b << a b <* a
  • Haskell:

    • (&), (<&>) - from Data.Function
  • PureScript:

    • (<>) - Semigroup type class - always instead of haskelish (++)
    • (*>), (<*) - Apply type class (granurality)
  • Haskell

    f $ x = f x
  • PureScript

    apply f x = f x
    infixr 0 apply as $
  1. Basic functions / data types:

    Haskell PureScript
    () Unit for type, unit for value
    fmap map (Functor type class)
    error unsafeThrow
    from Control.Monad.Eff.Exception.Unsafe
    from purescript-exceptions
    forkIO forkAff
    from Control.Monad.Aff
    from purescript-aff
    Control.Concurrent.MVar Control.Monad.Aff.AVar from purescript-aff
    undefined ?
    mapM traverse
    forM for
    _foo ?foo
    _ n/a
    [1 .. 3] 1..3
  • btw 3..1 is [3,2,1] (empty in Haskell)
  1. point-free (and sections):

    Obligatory ghost place for a value in PS:

    Haskell PureScript
    (+ 2) (_ + 2)
    (2 +) (2 + _)

    PureScript:

    case _ of
      Just x  -> 34
      Nothing -> 42

    Haskell (with {-# LANGUAGE LambdaCase #-} ):

    \case
      Just x  -> 34
      Nothing -> 42

    Update a record - include ghost place marker:

    _ { foo = 42 }

    Nested updates:

    _ { foo { bar { baz = 42 } } }

    Fill record:

    { foo: _, bar: _ }

    equivalent to:

    \foo bar -> { foo: foo, bar: bar }

    which can be simplified to:

    \foo bar -> { foo, bar }
  2. About IO:

  • Effect

    Haskell PureScript
    IO () Effect Unit
  • Eff (legacy)

    Haskell PureScript legacy
    (Member Console effs) => Eff effs a `forall eff. Eff (console :: CONSOLE
  • Haskell - freer.

  • Aff

    launchAff_ :: forall a. Aff a -> Effect Unit forkAff :: forall a. Aff a -> Aff (Fiber a)

  1. Booleans

    PureScript Haskell
    Type Boolean Type Bool
    Value true Value True
    Value false Value False
  2. Tuples

    Package purescript-tuples

    PureScript Haskell
    Type Tuple Bool Int Type (Bool, Int)
    Value Tuple true 42 Value (True, 42)
    Pattern (Tuple x y) Pattern (x, y)
  3. Lists

  • PureScript has built-in Arrays

  • functional Lists are provided by purescript-lists package

    • same with purescript-maybe
  • [1,2,3] - a value of Array Int

  • no sugar [Int] for array types

  • no sugar for list (array) comprehensions (do notation used)

    Array patterns

      f []     = -1
      f [x]    = x
      f [x, y] = x * y
      f _      = 0

    List patterns

    PureScript Haskell
    (Cons x xs) (x : xs)
  1. Records:

    foo :: { foo :: String, bar :: Int } -> Int
    foo x = x.bar

    Type of foo is equivalent to:

    foo :: Record (foo :: String, bar :: Int) -> Int

    any record that have bar :: Int

    foo :: forall r. { bar :: Int | r } -> Int
    bar = { foo: "Foo", bar: 42, baz: true }

    for update - use = instead of :

    bar { bar = 34 }
    foo { bar { baz { bzz = 42 } } }

    destructuring

    foo x = log x.bar
    foo { bar } = log bar
    foo { bar: baz } = log baz
  2. Deriving type class instance:

    • separate lines
    derive instance eqLocation :: Eq Location
    derive instance genericLocation :: Generic Location
    instance showLocation :: Show Location where show = gShow
    • names required
    • no orphans btw
  3. Imports:

    no qualified keyword

    as keyword at the end

    Haskell PureScript
    import qualified Data.Foo as Foo import Data.Foo as Foo
    import qualified Data.Foo as Foo (foo) import Data.Foo (foo) as Foo
  4. Type classes

  • arrow direction

    class (Eq a) <= Ord a where
      ...
    
  • granurality

    Category has a superclass Semigroupoid which provides (<<<), and does not require an identity. Applicative has a superclass Apply, which provides (<*>) and does not require an implementation for pure.

  1. Newtypes
import Data.Newtype (class Newtype)

newtype Route =
  Route
  { name :: String
  , queryParams :: QueryParams
  }

derive instance newtypeRoute :: Newtype Route _
  • PureScript: wrap/unwrap, Haskell: pack/unpack
r = Route { "Ala", params }
v1 = case r of
        Route rr -> rr.name
v2 = (unwrap r).name
v3 = r # unwrap # _.name
v4 = _.name $ unwrap $ r
v5 = r ^|> _.name
v6 = r ^# _.name
v7 = r.name -- in zyla PureScript fork
data Bool = True | False

newtype Year = Year Int

newtype Person = Person { name: String, surname: String }

type PersonRecord = { name: String, surname: String }

newtype Person2 = Person2 PersonRecord
---------- apply ----------

x |> f
x # f
f x

x ^|> f
unwrap x # f

x ^|>^ f
unwrap x # f # unwrap

---------- fmaps ----------

f ^<$> x
unwrap ⟡ f <$> x

x <#>^ f
x <#> unwrap ⟡ f

---------- compose ----------

f ^∘ g
unwrap ∘ f ∘ g

f ∘^ g
f ∘ g ∘ unwrap

f ∘^∘ g
f ∘ unwrap ∘ g

f ^∘^∘ g
unwrap ∘ f ∘ unwrap ∘ g

f ∘^∘^ g
f ∘ unwrap ∘ g ∘ unwrap

f ^∘^∘^ g
unwrap ∘ f ∘ unwrap ∘ g ∘ unwrap

---------- compose flipped ----------

f ^⟡ g
unwrap ⟡ f ⟡ g

f ⟡^ g
f ⟡ g ⟡ unwrap

f ⟡^⟡ g
f ⟡ unwrap ⟡ g

f ^⟡^⟡ g
unwrap ⟡ f ⟡ unwrap ⟡ g

f ⟡^⟡^ g
f ⟡ unwrap ⟡ g ⟡ unwrap

f ^⟡^⟡^ g
unwrap ⟡ f ⟡ unwrap ⟡ g ⟡ unwrap
  1. Full code example

Haskell

sourceList :: [Int]
sourceList = [1..100]

allTriples :: [(Int, Int, Int)]
allTriples =
  [ (a, b, c)
  | a <- sourceList
  , b <- sourceList
  , c <- sourceList
  ]

isPythagorean :: (Int, Int, Int) -> Bool
isPythagorean (a, b, c) = a ^ 2 + b ^ 2 == c ^ 2

isSmallEnough :: (Int, Int, Int) -> Bool
isSmallEnough (a, b, c) = a + b + c < 100

finalAnswer :: [(Int, Int, Int)]
finalAnswer = filter
  (\t -> isPythagorean t && isSmallEnough t)
  allTriples

PureScript

import Data.List (List, range, filter)
import Data.Int (pow)
import Prelude

sourceList :: List Int
sourceList = range 1 100

newtype Triple = Triple
  { a :: Int
  , b :: Int
  , c :: Int
  }

allTriples :: List Triple
allTriples = do
  a <- sourceList
  b <- sourceList
  c <- sourceList
  pure $ Triple { a, b, c }

isPythagorean :: Triple -> Boolean
isPythagorean (Triple triple) = pow triple.a 2 + pow triple.b 2 == pow triple.c 2

isSmallEnough :: Triple -> Boolean
isSmallEnough (Triple triple) = triple.a + triple.b + triple.c < 100

finalAnswer :: List Triple
finalAnswer = filter
  (\triple -> isPythagorean triple && isSmallEnough triple)
  allTriples
  1. My former project
  • ~80 KLOC PS, ~80 KLOC Haskell

  • anonymized examples of unwrap hell (fun?)

    let resultDataD = map (_.report ∘ unwrap) ∘ join ∘ map hush ∘ fromLoaded <$> requestResultD
    
    flip F.singleOption choiceId <$>
      liftMaybe (Map.lookup itemId (unwrap (unwrap env.menu).items) <#>
          unwrap >>> _.name >>> getLocalization locale
    
    merge (toUnfoldable (Map.keys (unwrap (unwrap items).items))) form.itemsList
    
    tableStyled (\x -> mwhen (Just (unwrap x).itemId == ((_.itemId ∘ unwrap) <$> env.highlightedItem)) ("class" := "success"))
  • In Haskell indentation, in PureScript not (kind of accidentally)

  • PureScript more responsive IDE tooling (when works)

  • In PS a lot of FRP code (Specular by zyla)

  • lots of in house machinery

    • more layers on top of Specular
      • widgets
      • forms
      • validations
    • rpc handling layers (json-rpc)
      • interface
      • error handling
    • generate-ps for data types (domain and rpc requests/responses)
      • including robust tests of correctness of generated files
  • evolving conventions

  • performance issues (Specular engine rewritten into inremental style)

    • tracing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment