1. 2

    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?

    1. 5

      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
      
      1. 1

        Yes, the include specifically made me think so. Is that actually the CPP? Anyways, reassuring to see there’s other functionality.

        1. 3

          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...
          %}
          
          1. 1

            Interesting! Seeing as the #include syntax is reused, is it textual substitution or something more complex?

            1. 3

              #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.

    1. 10

      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.

      1. 5

        I agree. I have always thought that for and loop don’t belong in Rust, because they necessarily return unit and therefore are necessarily side-effecting.

        1. 15

          Lots of things are side-effecting in Rust; that’s not really an axis by which we design the language.

          1. 2

            I know, I just disagree that that’s a good thing. My impression of Rust is that it has some great FP concepts but it has bolted on some C misfeatures to make it more appealing to “systems developers.” Loops in syntax are one of them.

            1. 4

              Until we get something like the becomes syntax for guaranteed TCO, we need real loops to keep from blowing out the stack in Rust.

              1. [Comment removed by author]

                1. 2

                  Yeah! I’ve heard noise about it on the IRC before. Pretty sure there’s an RFC but I can’t find it at the moment…

              2. 3

                Why do you prefer recursion to loops?

          2. 1

            I remember wanting this feature well before 1.0.

          1. 5

            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.

            1. 12

              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?

              1. 14

                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.

                1. 3

                  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
                  
                  1. 6

                    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
                    
                    1. 2

                      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?

                      1. 7

                        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:

                        https://bitbucket.org/snippets/puffnfresh/gndog

                        1. 2

                          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")
                          
                          1. 4

                            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.

                2. 4

                  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.

                  1. 1

                    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.

                  1. 6

                    Author here, I’m happy to receive feedback, comments and corrections (content, grammar, typos, …). Thanks!

                    1. 2

                      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.

                      1. 3

                        True, thanks! Fixed!

                      2. 1

                        Looks like a typo here: “In response, it was tried to put band-aid around it.”

                        1. 2

                          You are right, the grammar of that sentence sounds weird … do you have a suggestion on how to improve it?

                          1. 2

                            How about, “The response was a band-aid.” or “In response, a band-aid was applied.”

                            1. 3

                              Thanks, fixed!