You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Elm has predefined type variable categories that have implicit functionality. You specify a type var. category by means of a prefix. Check the compiler function categorizeVar
type variables with "number" as prefix form a category that corresponds to Haskell's Num typeclass (a ring structure), defines (+), (-), (*), abs, negate, adding (^) and clamp to the pack. Check the link documentation for available instances.
"comparable" type var category corresponds to Haskell's Ord functionality
"appendable" type var category corresponds to Haskell's Semigroup with (++) as right associative infix operator for append
To extend the functionality with new functions based on the predefined ones for the category, you only have to categorize the type variable of the parameter using the category prefix.
Type differences
Elm's Int has the integer division as double slash (//) and mod as (%), and rem as rem. (/) is only for Floats
But there is a catch with (//). It does not return the integer part when dividing negatives. It behaves like haskell's quot truncating towards zero !! So it does not correspond to the euclidean division definition because it doesn't comply the rule stating that the remainder should be non‑negative.
importHtmlasHimportListasL-- (//) behaves like `quot`, not like `div`
intDblSlashIsQuot :Int->Int->Bool
intDblSlashIsQuot denom num =abs (num // denom) ==abs (-num // denom)
checkProperty = intDblSlashIsQuot 3
main =H.text <| toString <|L.map checkProperty [7, -2, 4, -8]
Elm's Float (JavaScript's IEEE754 Number) corresponds to Haskell's Double
Parameters with the type category number allow Int and Float as actual parameter types.
Elm's data types use the clause keyword type. Type aliases are defined with "type alias".
Elm has a special uninhabited type Never to be used as a Task error type parameter for tasks that cannot fail.
Elm's mapping Dict structure (dictionary) requires comparable key types (ordered domains). This includes Int, Float, Time, Char, String, and tuples or lists of comparable types. Its corresponding in Haskell is Data.Map.Strict where stored values are forcedly evaluated.
Elm's Set structure as a special case of Dict where the elements are the keys, has the same restriction.
Elm's Task structure is for effect actions that may fail. It implements Haskell's MonadError throwing errors with fail that skips the subsequent actions until the onError monadic catcher is evaluated, and its type (Task err a) would correspond to IO lifted to a MonadError compliant transformer, like (ExceptT err IO a).
Haskell range syntax is not supported in Elm. Specific functions are used: List.range. But you can define a dot-dot operator (..)
importHtmlasHimportList
(..) =List.range
infix9..
main =H.text <| toString (1..7)
A Haskell style stepped range for Ints defining enumFromThenTo.
importHtmlasHimportListasLimportDebugasDimportCharasCh-- tailrec
intEnumFromThenToTR :ListInt->Int->Int->Int->ListInt
intEnumFromThenToTR acc ini nxt top =if ini == nxt
thenD.log"next must be different than initial"[]elseif{- ini beyond top -} ini /= top &&compare ini top /=compare ini nxt
thenL.reverse acc
else intEnumFromThenToTR (ini ::acc) nxt (nxt + nxt - ini) top
intEnumFromThenTo = intEnumFromThenToTR []-- using this seems to slow result yield
v0 = intEnumFromThenTo 971-- [9,7,5,3,1]
v1 = intEnumFromThenToTR []971-- not as elegant but maybe faster-- refactored
intEnumFromThenTo2 :Int->Int->Int->ListInt
intEnumFromThenTo2 ini0 nxt0 top =if ini0 == nxt0
thenD.log"next must be different than initial"[]elselet
step = nxt0 - ini0
go acc ini nxt =if{- ini beyond top -} ini /= top &&compare ini top /=compare ini nxt
thenL.reverse acc
else go (ini ::acc) nxt (nxt + step)
in go [] ini0 nxt0
v2 = intEnumFromThenTo2 971
charEnumFromThenTo:Char->Char->Char->ListChar
charEnumFromThenTo ini0 nxt0 top =if ini0 == nxt0
thenD.log"next must be different than initial"[]elseL.mapCh.fromCode <|
intEnumFromThenTo2 (Ch.toCode ini0) (Ch.toCode nxt0) (Ch.toCode top)
v3 = charEnumFromThenTo 'Y''W''A'
main =H.text <| toString (v2, v3)
Using partial application seems to yield result later in Elm's try-online vs. specifying all function arguments at once. Check it also with the program at the page bottom.
Functor: Haskell Data.Functor.fmap corresponds to Elm structures' map function
Monad: Haskell's bind (>>=) corresponds to Elm structures' andThen flipped.
Applicative: With Elm structures' (map2, .., map5) you have the functionality that corresponds to Haskell's Control.Applicative (liftA2, ...) to combine the results of a string of actions as subsequent parameters.
For Task's map{N}, action side effects are serialized with andThen (Haskell's bind) so they rather correspond to Control.Monad.liftM{N} instead of Control.Applicative.liftA{N}.
Reductions, Traversals and Tail call optimization
The Elm compiler is able to do tail-call elimination on a function when any of the branches are a direct self-recursive call. See Tail-call elimination.
Lists
-- from Elm's core library Listfoldl: (a -> b -> b) -> b ->List a -> b
foldl func acc list =case list of[]-> acc
x::xs->foldlfunc (funcxacc) xs-- tail recursivefoldr: (a -> b -> b) -> b ->List a -> b
foldr=Native.List.foldr-- via JS list conversion toArray(xs)-- map is based on foldr, so elements are traversed right to leftmap: (a -> b) ->List a ->List b
map f xs =foldr (\x acc -> f x ::acc) [] xs
-- Task.sequence traverses left-to-right a list of Tasks giving a Task that returns the List of results. It uses map2 (like Applicative.liftA2 but serializing the side effects: Control.Monad.liftM2) to concatenate its results.
Desugaring Haskell Do blocks
Simulation of a Haskell do block, nesting subsequent monadic functions to have all lambda argument variables in scope. Try it in Elm's try online:
import Html as H exposing (Html)
import Maybe as M
-- Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
-- simulation of a Haskell "do" block
monadic : Maybe Int
monadic = Just 1
|> M.andThen (\x -> Just 2
|> M.andThen (\y -> let y2 = y * y -- a `do block` let
in Just (x + y2)
|> M.andThen (\z -> Just (x + y2 + z) -- y2 still in scope
)))
-- rewriting it with (>>=)
(>>=) m f = M.andThen f m
infixl 1 >>=
monadic2 : Maybe Int
monadic2 = Just 1
>>= (\x -> Just 2
>>= (\y -> let y2 = y * y -- a `do block` let
in Just (x + y2)
>>= (\z -> Just (x + y2 + z) -- y2 still in scope
)))
main : Html String
main = H.text <| toString (monadic, monadic2)
Deferring computations - Lazy parameters
See also elm-lang/lazy. Lazyness memoisation is advised against. Have a look at the link's "Pitfalls" section regarding memory usage for memoization of lazy expressions and parameters.
importHtmlasH-- lazyness without memoizationtypeLazya=Lazy (()->a)
force :Lazy a -> a
force (Lazy f) = f ()---------------- use:
maybeOrElse :Maybe a ->Lazy a -> a
maybeOrElse mbX lzY =case mbX ofJust x -> x
Nothing-> force lzY
v = maybeOrElse Nothing<|Lazy (\_ ->sqrt2)
main =H.text <| toString v
importHtmlasH exposing (Html)
-- appendables (Semigroup) have (++) as infixr for append (See Basics)
main :Html msg
main =H.text <|"Hello "++"cruel world!"
Pages without effects
Web programs without effects follow the so called beginnerProgram structure as explained here.
Html.beginnerProgram : { model : model,
view : model ->Html msg,
update : msg -> model -> model }
->ProgramNever model msg
main =Html.beginnerProgram { model = initialModel, view = view, update = update }
Pages with effects
Html.program : { init: (model, Cmd msg),
update : msg -> model -> (model, Cmd msg),
subscriptions : model ->Sub msg,
view : model ->Html msg }
->ProgramNever model msg
Effect actions (tasks) are not evaluated directly but by sending a command to an Effect manager that will be handled differently based on their protocol processing state.
Effects, Effects managers and Message routing
Effect managers support protocol processing by handling its own message queue (selfMsg queue), and may send messages to the app main loop through the appMsg queue parameter of a Router object assigned to them.
You can interact with them sending commands and subscriptions specific to the effect manager which finally will send messages routed to your app main loop, and handled by the update function.
-- from the Platform module:{-| Task: Represents asynchronous effects that may fail. It is useful for stuff like HTTP.-}typeTaskerrok=Task{-| Router: An effect manager has access to a “router” that routes messages between the main app and your individual effect manager.-}typeRouterappMsgselfMsg=Router{-| sendToApp: The effects manager module will be able to send the router a message for the main loop of your app. This message will be handled by the overall `update` function, just like events from `Html`.-}
sendToApp :Router appMsg a -> appMsg ->Task err (){-| sendToSelf: The effects manager module will be able to send the router a message for its own message queue. This message willbe routed to the `onSelfMsg` function, where you can update the state of your effect manager as necessary.-}
sendToSelf :Router a selfMsg -> selfMsg ->Task err ()
The (Cmd msg) and (Sub msg) types are the types of requests for commands or subscriptions sent to the effect managers (see below) that may send app messages to be processed by the update function.
Each effect manager exposes user entry points to build such requests.
-- from Platform.Cmd
{-| Cmd msg: the type of commands to Elm's effects
Every Cmd specifies (1) which effects you need access to and (2) the type of messages that will come back into your application.
-}
type Cmd msg
-- from Platform.Sub
{-| Sub msg: the type of commands to Elm's effects
Every Sub specifies (1) which effects you need access to and (2) the type of messages that will come back into your application.
-}
Elm has modules qualified by the effect keyword as effect managers. They handle Msgs from/to the run time system.
As example, the Websocket Effect Manager skeleton, expliciting which queue the message types are related:
effect module WebSocketwhere { command =MyCmd, subscription =MySub } exposing
( send
, listen
, keepAlive
)
...-- MyCmd and MySub are parameterized by an appMsg type to encode app. message constructors.typeMyCmdappMsg=SendStringStringtypeMySubappMsg=ListenString (String->appMsg)
|KeepAliveString--| user entry calls to build command or subscription requests
send :String->String->Cmd appMsg
send url txt = command (Send url txt)
listen :String-> (String-> appMsg) ->Sub appMsg
listen url tagger = subscription (Listen url tagger)
--^ tagger: appMsg constructor to wrap the received String with
keepAlive :String->Sub appMsg
keepAlive url = subscription (KeepAlive url)
--| request processing automaton (input -> state -> state) callback
onEffects
:Platform.Router appMsg SelfMsg->List (MyCmd appMsg) -- cmd requests->List (MySub appMsg) -- subscription requests->State appMsg
->TaskNever (State appMsg)
onEffects router cmds subs state =...--| State: The msg param in the State type is the appMsg of the type of the current subscriptionstypealiasStatemsg=
{ sockets:SocketsDict
, queues:QueuesDict
, subs:SubsDictmsg
}
--| selfMsgs (type changed from Msg to SelfMsg to avoid confusion)typeSelfMsg=GoodOpenStringWS.WebSocket|BadOpenString|ReceiveStringString|DieString--| selfMsgs processing automaton callback
onSelfMsg
:Platform.Router appMsg SelfMsg->SelfMsg->State appMsg
->TaskNever (State appMsg)
onSelfMsg router selfMsg state =...--| cmdMap called by Platform.Cmd.map
cmdMap : (a -> b) ->MyCmd a ->MyCmd b
--| subMap called by Platform.Sub.map
subMap : (a -> b) ->MySub a ->MySub b
The type (Task err a) represents an action that may fail.
You may run a Task with the commands perform or attempt, that send a message to the appMsg queue upon finalisation, through the Task effect manager (See the module's source).
Within Effect managers, automaton callbacks are Tasks. You can also run tasks asynchronously through Process.spawn and abort them with Process.kill.
The structure Task implements Haskell's Monad and MonadError functionality as well, for computations that can throw errors whose type is determined by the type of the monad, here (Task err), where throwError bypasses subsequent actions until catchError is found.
succeed -> Haskell Monad's return implementation
andThen -> Haskell Monad's flipped bind impl.
fail -> Haskell MonadError's throwError
onError -> Haskell MonadError's catchError
map -> Haskell Functor's fmap
map2 -> Haskell Control.Monad's liftM2
map3 -> Haskell Control.Monad's liftM3
sequence -> Haskell Control.Monad sequence for a List container
effect module Taskwhere { command =MyCmd } exposing
( Task-- the type
, perform, attempt -- cmds to run tasks
, succeed, andThen -- return, bind
, map, map2, map3, map4, map5 -- functor, liftM{N}
, sequence-- sequence a list of tasks
, fail, onError, mapError -- throwError, catchError
)
{-| Command to perform asynchronously a Task that cannot fail (err ~ Never) and send a msg to the appMsg queue-}
perform : (a -> msg) ->TaskNever a ->Cmd msg
perform toMessage task =
command (Perform (map toMessage task))
{-| Command to attempt asynchronously a task that might fail and send a msg chosen by the resultToMessage function-}
attempt : (Result err a -> msg) ->Task err a ->Cmd msg
attempt resultToMessage task =
command (Perform (
task
|> andThen (succeed << resultToMessage <<Ok) -- Ok is a constructor of type Result|> onError (succeed << resultToMessage <<Err) -- Err is a constructor of type Result
))
{-| Simple task generator that succeeds immediately when run. A Haskell's Monad `return` implementation.-}
succeed : a ->Task err a
{-| Tasks chaining. A flipped version of Haskell's Monad `bind`-}
andThen : (a ->Task x b) ->Task x a ->Task x b
{-| fail gives a task that fails immediately when run. fail "file not found" : Task String a fail: Monadic exception thrower, MonadError's throwError-}fail: err ->Task err a
{- onError: Monadic exceptions catcher, MonadError's catchError-}
onError : (x ->Task y a) ->Task x a ->Task y a
{- mapError: to wrap the error type into a wider union one with a constructor-}
mapError : (x -> y) ->Task x a ->Task y a
{- Functor map-}map: (a -> b) ->Task x a ->Task x b
map f taskA =
taskA |> andThen (\a -> succeed (f a))
{- Results combination of serialized tasks: Haskell's liftM2-}
map2 : (a -> b -> c) ->Task x a ->Task x b ->Task x c
map2 f2 taskA taskB =
taskA |> andThen (\a -> taskB
|> andThen (\b -> succeed (f2 a b)
))
...{-| form a Task from a list of tasks returning the list of results.-}sequence:List (Task x a) ->Task x (List a)
sequence tasks =case tasks of[]-> succeed []task::remainingTasks->map2 (::) task (sequenceremainingTasks)
Adding tipical control functions for the type "Task"
moduleTask_Extraexposing (..)
importTask exposing (Task, succeed, andThen, map2)
-- lazyness without memoizationtypeLazya=Lazy (()->a)
force :Lazy a -> a
force (Lazy f) = f ()-- let's call traverse what in Haskell would be mapM or traverseMtraverse: (a ->Task err b) ->List a ->Task err (List b)
traverse f li =case li of[]-> succeed []x::xs->map2 (::) (fx) (traversefxs)
-- side effects only
traverse_ : (a ->Task err ()) ->List a ->Task err ()
traverse_ f li =case li of[]-> succeed ()x::xs->fx|>andThen (\_->traverse_fxs)
-- Haskell's monadic forM
for =fliptraverse
for_ =flip traverse_
when :Bool->Lazy (Task err ()) ->Task err ()
when cond lzTask =if cond then force lzTask
else succeed ()
whenM :Lazy (Task err Bool) ->Lazy (Task err ()) ->Task err ()
whenM lzBoolTask lzTask =
force lzBoolTask |> andThen (\cond -> when cond lzTask)
-- replicate with Indexreplicate:Int-> (Int->Task err a) ->Task err (List a)
replicate n f =if n <=0then succeed []elselet go i =-- i: 0..nif (i < n) then map2 (::) (f i) <| go (i+1)
else succeed []in go 0-- side effects only
replicate_ :Int-> (Int->Task err ()) ->Task err ()
replicate_ n f =if n <=0then succeed ()elselet go i action =-- i: 0..nif (i < n) then go (i+1) (f i) -- tail recursiveelse succeed ()in go 0 (succeed ())
Elm timing example: Comparing elapsed times of expressions with partial application vs complete number of arguments.
Other client side Model-View-Controller frameworks
PureScript offers alternatives for designing Web User Interfaces much nearer to the language power of Haskell and much lighter weight than GHCJS (no GHC RTS emulation).