Skip to content

Instantly share code, notes, and snippets.

@tfausak
Last active August 14, 2016 23:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tfausak/d6441efdd7e4938fb8807379ae7f3021 to your computer and use it in GitHub Desktop.
Save tfausak/d6441efdd7e4938fb8807379ae7f3021 to your computer and use it in GitHub Desktop.
-- This module explores an alternative function application syntax for
-- PureScript. It grew out of my Neon library, which uses some similar ideas.
-- <https://github.com/tfausak/purescript-neon>
module Main where
import Control.Monad.Eff as Eff
import Control.Monad.Eff.Console as Console
import Prelude (bind)
import Prelude as Prelude
-- Let's start by defining a function that does backwards function application.
-- We won't be using this directly, but we need it to define an operator.
_operator_apply :: forall a b. a -> (a -> b) -> b
_operator_apply x f = f x
-- And now let's define that operator. PureScript's prelude calls this `#`. In
-- Haskell it's `&`. In Elm it's `|>`. We're going with `..` to make things
-- look similar to OOP languages. (PureScript doesn't let us define an operator
-- called `.`, which is probably a good thing.)
infixl 8 _operator_apply as ..
-- That operator encourages us to write functions with signatures like this:
-- method :: ... -> a -> b
-- Where `a` is the "object" we're working with, `...` are the arguments to
-- this "method", and `b` is the return value.
-- What should the arguments to one of these "methods" look like? We could use
-- normal function arguments like this:
-- method :: c -> d -> a -> b
-- Where `c` and `d` are arguments. But it may not always be clear what `c` and
-- `d` are supposed to be. PureScript usually solves this problem with
-- newtypes, documentation, or both. But some languages, like Python and Swift,
-- use named arguments. We can emulate that in PureScript like so:
-- method :: { foo :: c, bar :: d } -> a -> b
-- Now it's clear what the arguments are supposed to be.
-- Let's convert one of the prelude's functions to this style. The `clamp`
-- function is a good candidate. Its signature looks like this:
-- clamp :: Ord a => a -> a -> a -> a
clamp :: forall a. Prelude.Ord a => { min :: a, max :: a } -> a -> a
clamp { min: l, max: h } x = Prelude.clamp l h x
-- So what does actually using a "method" like this look like? Let's see.
main :: Eff.Eff (console :: Console.CONSOLE) Prelude.Unit
main = do
Console.log "https://gist.github.com/tfausak/d6441efdd7e4938fb8807379ae7f3021"
-- We can of course call it the "normal" way.
Console.logShow (clamp { min: 1, max: 9 } 5) -- 5
-- But I think it's a little better to use our `..` operator.
Console.logShow (5..clamp{ min: 1, max: 9 }) -- 5
-- We can even go all in and use `..` for everything. This forms a single
-- expression that reads left to right.
5..clamp{ min: 1, max: 9 }..Console.logShow -- 5
-- There are a few downsides to this way of doing things:
-- 1. The PureScript community doesn't do things this way, so it's weird.
-- 2. You don't get curried (partially applied) functions for free. I don't
-- mind this so much because I rarely use curried functions, but it may be a
-- bigger deal for some people.
-- 3. Optional arguments still have to be wrapped in `Maybe`. You can't just
-- leave them out like you can in other languages.
-- 4. It's not clear if the record holding all of a function's arguments should
-- be "open" (like `{ | r }`) or not.
@tfausak
Copy link
Author

tfausak commented Aug 14, 2016

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