The fact that ATS makes heavy use of the C preprocessor bothers me. Does it have a true module system? Is there a particular reason it doesn’t?
What use of the C preprocessor are you seeing? Is it the #include
? That does work like C’s #include
but there are more module like things. See staload. These respect namespacing and local definitions so you can do things like:
local
staload FOO = "somefile.sats"
in
$FOO.dosomething()
end
Yes, the include specifically made me think so. Is that actually the CPP? Anyways, reassuring to see there’s other functionality.
No, the #include
isn’t handled by the CPP. The C preprocessor is only used if you use #include/#define inside embedded C code blocks:
%{
#include <foo.h>
#define X
...C Code here...
%}
Interesting! Seeing as the #include
syntax is reused, is it textual substitution or something more complex?
#include
in ATS code is textual substitution in ATS code and the code processed by the ATS compiler. Anything in C code blocks is directly inserted into the generated C code (ATS compiles to C which is then compiled by a C compiler) so is handled by the C compiler. ATS doesn’t parse that code. So there are two languages at play there, both using #include
for similar functionality.
I’m really excited about break values in loops. Before then, my way of setting a value at the end of a loop was to use this funky “let” syntax that doesn’t assign a value, e.g.
let x;
loop {
if foo() {
x = 5;
break;
}
}
I much more prefer the new method.
As a violinist, it’s important to me that I’m able to do as much as possible on my laptop without a trackpad. Small, precise movements - scrolling, selections - put a lot of tension in my bow hand’s index finger especially. I actually switched to vim recently in an attempt to wean myself off of trackpad dependency and I want to say I can feel an improvement…
I realize that a trackpad isn’t exactly a mouse but many people don’t carry mice around with them which makes it relevant here, I think.
The example given is
do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
How do I tell whether that is a maybe monad, a list monad, a continuation monad, or a state monad? Or is that generally not a problem encountered with monads?
Those are really great questions.
… is that generally not a problem encountered with monads?
That’s the point of monads, code reuse. I personally write a lot of code which works over many monads or all monads. One example I have from about 2 weeks ago:
whileM_ :: (Monad m) => m Bool -> m a -> m ()
whileM_ cond ma = do
b <- cond
when b $ ma *> whileM_ cond ma
This function allows looping over any monadic value. Works for values which represents state, configuration, IO, etc.
The example you copied uses print
, I don’t know the imports but I’m guessing it’s the one from Prelude
which has type (Show a) => a -> IO ()
. My manual type inference in my head says the example is using IO
:
program :: IO ()
program = do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
But let’s say you use some other print
function, maybe one which changes the specific IO
type into a constraint, so I can use it over any monad which supports IO:
program :: (MonadIO m) => m ()
program = do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
But let’s also say that getData
now has a different constraint. It does some stuff with configuration:
program :: (MonadIO m, MonadReader MyConfig m) => m ()
program = do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
And now getEvenMoreData
actually does some stuff with state:
program :: (MonadIO m, MonadReader MyConfig m, MonadState MyState m) => m ()
program = do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
But this program is polymorphic. It can be instantiated as a monadic value, as long as it supports IO, Reader and State. A common use of this is to have one instantiation for production and one instantiation for tests. Yay, code reuse!!!
So the answer to your question:
How do I tell whether that is a maybe monad, a list monad, a continuation monad, or a state monad?
Is that you read the types.
Please show an example where the following table is satisfied:
getData getMoreData getEvenMoreData
promise yes yes no
maybe yes no yes
list no yes yes
That’s the last example, just with renamed constraints.
program :: (MonadPromise m, MonadMaybe m, MonadList m) => m ()
program = do
a <- getData
b <- getMoreData a
c <- getMoreData b
d <- getEvenMoreData a c
print d
Forgive me if my understanding is incorrect here, but wouldn’t that only be true if you wrote all three of those functions yourself and a priori defined them in terms of such a triple-constrained monad? What if the three functions were provided by three authors and had types defined only in terms of a subset of the constraints?
Yeah, your understanding is incorrect. You don’t have to have all constraints on all values used. It’s the constraints on how to instantiate your program.
Here’s a full, executable example:
Thanks for the example. I’m impressed by the relative lack of lifting operations.
How would I get something like this to work?
runProd = flip evalStateT (MyState 0) . flip evalStateT (MyOtherState 5) . flip runReaderT (MyConfig "production-config")
That’s exactly one of the limitations of MTL (the type of constraints that I’m using) - you can read more about it here:
https://ro-che.info/articles/2014-06-11-problem-with-mtl
I use something like the lens version (see the “Merging transformer layers” section) for other reasons but the point about StateT s1 (MaybeT (StateT s2 Identity))
definitely demonstrates a problem with MTL.
Writing a specific class for your state is a workaround.
By type signatures. Or, if you’ve got monad-specific functions mixed in like State
‘s get
and put
, then it becomes fairly obvious what sort of monad you’re dealing with. The code shown in the article could even be polymorphic w.r.t. the monad - assuming getData
and co. are too.
I tend to use monads for “secondary” concerns - the kind of thing that I would consider leaving completely invisible if I were working in a language without monads. E.g. if working with state is the whole point of a given function then I’d probably have it accept and return the state in question rather than passing it via a state monad. So the <-
just indicates “a secondary effect is going on here”, and if I wanted to know the specifics I’d mouse over one of the functions and see what the type signature is.
But yeah in code that mixes multiple monads there are cases where it would be nicer to have a way to distinguish. I wonder about an IDE using e.g. colour or underlines to show what monads were in play.
Author here, I’m happy to receive feedback, comments and corrections (content, grammar, typos, …). Thanks!
I think there is a typo in the “The Bad: Type Inference” section. As written, it is:
val nums4: List[Double] = List(1, 2, 3) // compiles
val nums5a = List(1, 2, 3)
val nums5b: List[Double] = nums4 // fails to compile
I believe it probably should have been
val nums4: List[Double] = List(1, 2, 3) // compiles
val nums5a = List(1, 2, 3)
val nums5b: List[Double] = nums5a // fails to compile
or else I don’t know what nums5a is supposed to be there for.
You are right, the grammar of that sentence sounds weird … do you have a suggestion on how to improve it?
Wouldn’t the performance of HashMap Lookup be
O(1)*
, notO(log n)
? It states the key value must implement hash and eq, ideally this means you eventually end up with a hash table, which would mean faster lookups.It’s implemented underneath with an HAMT (hash array mapped trie) iirc. So although it compares on hashes it’s an ordered map over those hashes w/ O(log n) for most ops.