How to fix this Purescript error: Could not match {...} with (...)? - records

I have a state for a halogen component including a lens like this:
import Optic.Core (Lens', lens)
type State =
{ userName :: String
, password :: String
, formError :: String
}
_userName :: Lens' State String
_userName = lens _.userName (\r str -> r { userName = str })
And I want to modify the state in the eval function of the same component like this:
eval :: forall eff.
Query ~> ParentDSL State Query UserNameField.Query Slot Void (Aff (console :: CONSOLE , ajax :: AJAX | eff))
eval = case _ of
HandleInput userName next -> do
-- this code causes trouble:
_userName .= userName
-- while this code works:
-- modify (set _userName userName)
pure next
However, I get the error message:
Could not match type
{ userName :: String
, password :: String
, formError :: String
}
with type
( userName :: String
, password :: String
, formError :: String
)
while trying to match type t3
{ userName :: String
, password :: String
, formError :: String
}
with type t2
while checking that expression _userName
has type (t0 -> t1) -> t2 -> t2
in value declaration eval
where t1 is an unknown type
t0 is an unknown type
t2 is an unknown type
t3 is an unknown type
[TypesDoNotUnify]
Note the difference between { and ( (took me a while). I don't even know what the latter type actually means and I have no clue why this error is introduced by a MonadState-based lense.

Mystery solved: I unintentionally mixed two packages
purescript-lens and purescript-profunctor-lenses. My lenses came from the former and the assign function (.=) is only present in the latter, which was installed apparently as some implicit subdependency.

Related

EscapedSkolem error implementing websocket reconnection in PureScript and Halogen

I'm trying to implement Websocket reconnection in PureScript and am at a complete loss at how to proceed. I've added the reconnection code at the top level due to the use of Aff; I think this is the correct place but I'm not sure.
I've tried to implement it as I might in Haskell but I can't make it typecheck due to an EscapedSkolem error in runWs. I get the impression I can fix this by adding a type signature but I can't for the life of me work out what the signature might be!
So I have three questions:
Is this the correct way of implementing reconnection?
What is the type of runWs (any hints on how I might work this out for myself would be fantastic)?
If adding a type signature doesn't fix the EscapedSkolem error how would I go about fixing it?
And finally, I'm a complete newb when it comes to PureScript so if anything's unclear please point that out and I'll try and clarify.
EDIT: Added the error compiler output and changed the title slightly.
module Main where
import Prelude
import Control.Coroutine (Producer, Consumer, runProcess, consumer, ($$))
import Control.Coroutine.Aff (produce)
import Control.Monad.Aff (Aff, delay)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Eff.Exception (EXCEPTION)
import Control.Monad.Eff.Ref (REF)
import Control.Monad.Eff.Var (($=), get)
import DOM (DOM)
import DOM.Websocket.Event.CloseEvent (reason)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Data.Time.Duration (Milliseconds(..))
import Halogen as H
import Halogen.Aff (HalogenEffects, awaitBody, runHalogenAff)
import Halogen.VDom.Driver (runUI)
import Log (Query(..), component)
import WebSocket (Connection(..), URL(..), WEBSOCKET, newWebSocket, runMessage, runURL, runMessageEvent)
wsURI :: URL
wsURI = URL "ws://localhost:6385"
reconnectionDelay :: Milliseconds
reconnectionDelay = Milliseconds 10000.0
main :: forall eff. Eff (HalogenEffects (console :: CONSOLE, err :: EXCEPTION , avar :: AVAR , dom :: DOM , exception :: EXCEPTION , ref :: REF , ws :: WEBSOCKET | eff)) Unit
main = do
runHalogenAff do
body <- awaitBody
driver <- runUI component unit body
---> Replace this: <---
runProcess (wsProducer $$ wsConsumer driver.query)
---> with this: <---
-- runWs driver
-- -------------------------------------------------
-- -------------------------------------------------
--
-- Reconnection function
-- runWs :: ????????
runWs p = go
where
go = do
runProcess (wsProducer $$ wsConsumer p)
delay reconnectionDelay
go
-- -------------------------------------------------
-- -------------------------------------------------
wsProducer :: forall eff. Producer String (Aff (console :: CONSOLE, err :: EXCEPTION , ws :: WEBSOCKET , avar :: AVAR | eff)) Unit
wsProducer = produce \emit -> do
Connection socket <- newWebSocket wsURI []
socket.onopen $= \event -> do
log "onopen: Connection opened"
log <<< runURL =<< get socket.url
socket.onmessage $= \event -> do
emit $ Left $ runMessage (runMessageEvent event)
socket.onclose $= \event -> do
log $ "Socket Closed, returning to runHalogenAff: " <> reason event
emit $ Right unit
socket.onerror $= \event -> do
log "Error."
emit $ Right unit
wsConsumer :: forall eff . (Query ~> Aff (HalogenEffects eff)) -> Consumer String (Aff (HalogenEffects eff)) Unit
wsConsumer driver = consumer \msg -> do
driver $ H.action $ AddMessage msg
pure Nothing
And the compiler output is:
Compiling Main
[1/1 MissingTypeDeclaration] src/Main.purs:54:1
v
54 runWs p = go
55 where
56 go = do
57 runProcess (wsProducer $$ wsConsumer p)
58 delay reconnectionDelay
59 go
^
No type declaration was provided for the top-level declaration of runWs.
It is good practice to provide type declarations as a form of documentation.
The inferred type of runWs was:
forall t110 t120.
(Query a0
-> Aff
( avar :: AVAR
, ref :: REF
, exception :: EXCEPTION
, dom :: DOM
, console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| t120
)
a0
)
-> Aff
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
, avar :: AVAR
, dom :: DOM
, exception :: EXCEPTION
, ref :: REF
| t120
)
t110
where a0 is a rigid type variable
bound at line 57, column 44 - line 57, column 45
[1/1 EscapedSkolem] src/Main.purs:54:1
v
54 runWs p = go
55 where
56 go = do
57 runProcess (wsProducer $$ wsConsumer p)
58 delay reconnectionDelay
59 go
^
The type variable a, bound at
/home/rgh/dev/purescript/translate/sidebar/src/Main.purs line 57, column 44 - line 57, column 45
has escaped its scope, appearing in the type
(Query a2
-> Aff
( avar :: AVAR
, ref :: REF
, exception :: EXCEPTION
, dom :: DOM
, console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| t120
)
a2
)
-> Aff
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
, avar :: AVAR
, dom :: DOM
, exception :: EXCEPTION
, ref :: REF
| t120
)
t110
in the expression \p ->
let
go = ...
in go
in value declaration runWs
Src Lib All
Warnings 1 0 1
Errors 1 0 1
* Failed to rebuild; try to fix the compile errors
Compiler error messages may be hard to decrypt sometimes, but in this case it turns out to be the answer you're looking for. Let's look at your do block here:
do
runHalogenAff do
body <- awaitBody
driver <- runUI component unit body
runWs driver.query -- < assuming you made a small mistake here
I usually start by desugaring, I find it makes it easier to follow the types, but ymmv:
runHalogenAff $
awaitBody >>= \body ->
runUI component unit body >>= \driver ->
runWs driver.query
Looking at the signature of runHalogenAff, we can see that it accepts an argument of type Aff (HalogenEffects eff) x, meaning the following term must evaluate to a value of that type. It must be then that runWs returns a value of that type.
Now let's turn to runWs. Its argument is a natural transformation f ~> m which in your example takes your query algebra into the Aff monad. We can write this down and ask the compiler to figure out the rest for us:
runWs :: (Query ~> Aff _) -> Aff _ Unit
That will build successfully and give you what you can fill these holes with. Here is the final signature:
runWs :: forall eff.
(Query ~> Aff (HalogenEffects
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| eff
)))
-> Aff (HalogenEffects
( console :: CONSOLE
, err :: EXCEPTION
, ws :: WEBSOCKET
| eff
)) Unit
Indeed that is exactly what the compiler output gives you. I am assuming the error message "the type variable a has escaped its scope" is because of the universal quantifier in the definition of a natural transformation.

What does the double colon ( :: ) mean in Elm?

I'm new to Elm and I just came across this:
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
UrlChange location ->
( { model | history = location :: model.history }
, Cmd.none
)
Could someone tell me what the double colon does in line 5?
That's the cons operator. It adds an item to the front of a list.
1 :: [2,3] == [1,2,3]
1 :: [] == [1]
Documentation:
https://package.elm-lang.org/packages/elm/core/latest/List#::

Haskell - Validate integer input from string using readMaybe

I am trying to validate integer input from a string, I just need a boolean result if the input string correctly changes types to an integer. I tried this method from another question:
https://stackoverflow.com/a/30030649/3339668
This is the relevant code along with my imports:
import Data.Char
import Data.List
import Text.Read
checkValidInt :: String -> Bool
checkValidInt strInt
| readMaybe strInt :: Maybe Int == Nothing = False
| readMaybe strInt :: Maybe Int /= Nothing = True
However, I receive the following errors on loading the script:
Illegal operator ‘==’ in type ‘Maybe Int == Nothing’
Use TypeOperators to allow operators in types
main.hs:350:38:
Not in scope: type constructor or class ‘Nothing’
A data constructor of that name is in scope; did you mean DataKinds?
main.hs:351:35:
Illegal operator ‘/=’ in type ‘Maybe Int /= Nothing’
Use TypeOperators to allow operators in types
main.hs:351:38:
Not in scope: type constructor or class ‘Nothing’
A data constructor of that name is in scope; did you mean DataKinds?
So what kind of data type is Nothing? How do I check if Nothing is the result of readMaybe properly?
This is due to the fact that Haskell interprets your
readMaybe strInt :: (Maybe Int == Nothing = False)
as:
readMaybe strInt :: (Maybe Int == Nothing = False)
It can make no sense out of this. So you can help Haskell by using some brackets:
(readMaybe strInt :: Maybe Int) == Nothing = False
You also better do not repeat the condition, but use otherwise, since if you repeat it, the program will - unless optimized - do the parsing twice, so:
checkValidInt :: String -> Bool
checkValidInt strInt
| (readMaybe strInt :: Maybe Int) == Nothing = False
| otherwise = True
Since you check for a condition such that the result is True if the condition is False, and vice versa, it is no use to use guards, we can write it as:
checkValidInt :: String -> Bool
checkValidInt strInt = Nothing /= (readMaybe strInt :: Maybe Int)
Or we can use a pattern guard, this can be used in case we can not perform equality checks on the type of value that is wrapped in the Maybe, so:
checkValidInt :: String -> Bool
checkValidInt strInt | Just _ <- (readMaybe strInt :: Maybe Int) = True
| otherwise = False
Or we can use the isJust :: Maybe a -> Bool function:
checkValidInt :: String -> Bool
checkValidInt strInt = isJust (readMaybe strInt :: Maybe Int)
I just need a boolean result
Probably not. You need a Maybe Int to pattern match on, and readMaybe already gives you that without any further processing.
Instead of this
if checkValidInt s -- try reading an Int, but throw it away
then foo (read s) -- now really read an Int and use it
else bar -- fall back
you do this
case readMaybe s of -- try reading an Int
Just i -> foo i -- use it
Nothing -> bar -- fall back
Normally an explicit type annotation should not be needed, if foo is of the right type; but see below.
If you, for some unfathomable reason, really need checkValidInt, you base it on the above pattern
case (readMaybe s) :: Maybe Int of
Just _ -> True
Nothing -> False
As noted in another answer, the maybe function abstracts this pattern match away, but I would recommend using explicit pattern matches whenever you can as an exercise, to get the hang of it.
You could rewrite this as
import Data.Char
import Data.List
import Text.Read
checkValidInt :: String -> Bool
checkValidInt strInt =
case (readMaybe strInt :: Maybe Int) of
Nothing -> False
Just _ -> True
The algorithm you need here has already been abstracted behind the maybe function:
checkValidInt :: String -> Bool
checkValidInt = maybe False (const True) . (readMaybe :: String -> Maybe Int)
If readMaybe returns Nothing, then maybe returns False. Otherwise, it just applies const True to the resulting Just value, which returns True without caring about just what is wrapped by Just. Note that you are specializing the type of readMaybe itself, not the type of its return value.
Or, even simpler with an import,
import Data.Maybe
checkValidInt :: String -> Bool
checkValidInt = isJust . (readMaybe :: String -> Maybe Int)

How to use Value constructors to create records in PureScript

I am attempting to create a record based on an array of data, the function looks like this:
type Address = {
street :: String,
city :: String,
state :: String
}
convertToAddress :: Array String -> Maybe Address
convertToAddress [street, city, state] = Just (Address { street: street, city: city, state: state })
convertToAddress _ = Nothing
Here, I am trying to create a Record of the type Address using the Address value constructor but it throws an error when compiling:
Unknown data constructor Address
type only defines a type alias, so Address and
{
street :: String,
city :: String,
state :: String
}
Are actually the same type. If you want to generate a constructor you will have to use newtype:
newtype Address = Address {
street :: String,
city :: String,
state :: String
}
Or alternatively, you can just get rid of the constructor in your code and just use the record type:
convertToAddress :: Array String -> Maybe Address
convertToAddress [street, city, state] = Just { street: street, city: city, state: state }
convertToAddress _ = Nothing

Records in PureScript

I don't quite understand why this works:
module Records where
type Element e = { element :: String, label :: String | e }
type Sel = ( value :: Number, values :: [Number] )
type Select = Element Sel
while this says Cannot unify # * with *.
module Records where
type Element e = { element :: String, label :: String | e }
type Sel = { value :: Number, values :: [Number] }
type Select = Element Sel
(Note the '()' around the right hand side of Sel instead of the '{}'.)
I've read here https://leanpub.com/purescript/read#leanpub-auto-objects-and-rows that forall r. { firstName :: String, lastName :: String | r } desugars to
forall r. Object (firstName :: String, lastName :: String | r)
I'm still a bit confused, why you can't use the record-sugar for extending records.
The Object type constructor is parameterized by a row of types. In kind notation, Object has kind # * -> *. That is, it takes a row of types to a type.
( value :: Number, values :: [Number] ) denotes a row of types (something of kind # *), so it can be passed to Object to construct a type, namely
Object ( value :: Number, values :: [Number] )
Note that { ... } is just syntactic sugar for the Object type constructor, so this is the same as
{ value :: Number, values :: [Number] }
Both have kind *, so it doesn't make sense to pass this thing as an argument to Element, since the type variable e in Element has kind # *.
Put another way, Element Sel in your second example unrolls to
{ element :: String, label :: String | { value :: Number, values :: [Number] } }
which desugars to
Object (element :: String, label :: String | Object (value :: Number, values :: [Number]) )
which fails to kind-check due to the thing of kind * in the tail of the outer row.

Resources