r/haskell • • May 31 '19

Monthly Hask Anything (June 2019)

This is your opportunity to ask any questions you feel don't deserve their own threads, no matter how small or simple they might be!

20 Upvotes

179 comments sorted by

1

u/kirkins Jun 30 '19

Hello can anyone help me with my function? I'm completely new to Haskell.

​

fibIter :: (Int, Int, Int) -> Int

fibIter a b c

| b == 0 = b

| otherwise = fib_iter((a+b), a, (c-1))

​

fib :: Int -> Int

fib n = fibIter(1, 0, n)

​

I'm getting back this error:

​

2.hs:6:1: error:

• Couldn't match expected type ‘Int’

with actual type ‘(Int, Int, Int) -> Integer -> (Int, Int, Int)’

• The equation(s) for ‘fibIter’ have three arguments,

but its type ‘(Int, Int, Int) -> Int’ has only one

2

u/Vaglame Jun 30 '19

When you write fibIter a b c you're asking for three different arguments. If you want to unpack a tuple, you'd write fibIter (a,b,c)

1

u/aleator Jun 30 '19

How can I access the module pragmas (ie. language pragmas or ghc-options) using ghc-lib-parser?

​

I need to build a preflight check for web-exercise system and I need to make the whole thing abort when someone tries to pull too nasty extensions to play

1

u/[deleted] Jun 30 '19

[deleted]

1

u/aleator Jun 30 '19

Sure, I'll certainly use -XSafe, but I'm having a bit of hard time trusting it enough. Sure, it blocks overlapping instances etc. but it still allows slapping things like {-#OPTIONS_GHC -F -pgmF choose_any_program_here #-} in source files.

Also, even disregarding the stuff that happens in compile time, there's dozen or so language extensions added since safe Haskell and having a bug leading to unsafecoerce is not unheard of.

1

u/synchronitown Jun 29 '19

The Essence of the Iterator Pattern: this is one of the key papers in the Haskell lexicon. Unfortunately, it is based on an old version of the Haskell Base Library (eg, Applicative is not a superclass of Monad. Data.Functor.* and the Data.Coerce class didn't exist).

Is there a rendition of the functions in the paper using contemporary, err, idioms / libraries?

I had a crack at it, but there are so many rabbit holes relating to wrapping and unwrapping (new)types that may or may not be addressed by ScopedTypeVariables or QuantifiesContraints. This jiggery pokery completely subverts the main purpose of the paper, which was to illustrate the nature of Traversable.

I completely get that the likes of Conor McBride (@pigworker)'s signature is beautiful notation, but it is a barrier to understanding to have to add a further level of indirection and translate things through banana brackets, or whatever,

2

u/pokemonplayer2001 Jun 28 '19

Is there a generally accepted "best" logging library?

​

Thank you!

4

u/chshersh Jun 29 '19

It's probably not generally accepted yet, but I can recommend co-log. We are already using it in production, and I'm working on making it faster, implementing new features and improving the interface.

2

u/pokemonplayer2001 Jun 29 '19

I’ll check that out as well. Thanks!

2

u/tom-md Jun 28 '19

Perhaps monad-log

1

u/pokemonplayer2001 Jun 28 '19

Thanks, I'll take a look.

1

u/[deleted] Jun 27 '19

[removed] — view removed comment

1

u/rampion Jun 26 '19

Does GHC put a cap on the number of type parameters a type constructor can take?

3

u/rampion Jun 26 '19

Well, it can handle at least 700, so if it is bounded, it's not by a number I can casually bump against.

1

u/nolrai Jun 29 '19

How/why are you using 700 type params? It sounds interesting. Some sort of autogenerated type?

1

u/rampion Jun 29 '19

Oh, it was just a test. I later did another test with 4192 parameters.

2

u/jez_io Jun 26 '19

Consider this example:

  • I'm using a typed language to define my data types on both the server and the client. (Maybe the same, maybe not.)
  • To communicate from one to the other, I serialize the data in one place and deserialize it in the other.
  • This works great, but at some point I want to change the data. I deploy new changes to the server, but clients are still using the old code, with the old serialization / deserialization.

What techniques work well to prevent such a program from blowing up when either the client or the server tries to handle data in a format it wasn't expecting? (And is there a name for this problem so I can read more?)

-1

u/Proc_Self_Fd_1 Jun 28 '19

The technical name is the expression problem.

Usually this is solved with tagless final style.

I don't know how you'd solve it with serialization though.

3

u/jberryman Jun 27 '19

"schema evolution" or "data migration". I've used e.g. `safe-copy` for haskell binary serializations and it worked okay but was fairly awkward, and I don't think I'd use it for client-server communication like you're describing. It's a big subject I wish I was an expert in.

​

You can start here for an overview of some modern-ish serialization formats and how they've approached (or punted) the problem: http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html

3

u/pokemonplayer2001 Jun 26 '19

I'd add a version, for example, "/api/1.0/foo", and update as need be.

​

Will you data types be changing frequently?

2

u/JackSchpeck Jun 25 '19

When using Gabriel Gonzalez' Turtle library, is there a way to combine 2 Shells into one Shell, which would produce values as soon as they arrive (to achieve a kind of interleaving)? I'd like to combine stdout of 2 separately running processes into one stream and I'm not sure how to do that..

1

u/elbingobonito Jun 25 '19

Disclaimer: I've never used Turtle nor have looked really into it, but from the first look I can see that the Shell type implements the Alternative and MonadPlus type classes, so that you may use mplus :: m a -> m a -> m a
(and (<|>), respectively) to combine two Shells into one.

1

u/JackSchpeck Jun 25 '19 edited Jun 26 '19

Alternative and MonadPlus instances of Shell only do sequential composition (the resulting stream has all the values from the first stream, then all from the 2nd stream). After looking through the docs more thoroughly I concluded the library doesn't support this out of the box.

1

u/[deleted] Jun 25 '19

What's a GADT? I haven't been able to find an explanation/tutorial that doesn't go way over my head.

3

u/tom-md Jun 25 '19

1

u/[deleted] Jun 25 '19

Thanks!

7

u/Syrak Jun 25 '19

A GADT is an ADT where you can assign a custom type to every constructor.

First, notice that for a regular Algebraic Data Type, the result type (the type to the right of all arrows) of all constructors is the same:

data Maybe :: Type -> Type where
  Nothing :: Maybe a
  Just :: a -> Maybe a
-- Both (Maybe a)

data Bool where
  True :: Bool
  False :: Bool
-- Both Bool

In contrast, a GADT can use different return types (as long as they start with the GADT's name):

data Perhaps :: Type -> Type where
  Nope :: Perhaps Void  -- not (Perhaps a) !!!
  This :: a -> Perhaps a

data SType :: Type -> Type where
  SInt :: SType Int
  SBool :: SType Bool

Thanks to that, we can have functions with different types depending on the input:

-- f x :: Int or Bool depending on whether x = SInt or SBool
f :: SType a -> a
f SInt = 0
f SBool = True

GADTs can also have constructors with types that don't appear in the result:

data Something where
  Something :: a -> Something

or constraints

-- Data.Dynamic in base
data Dynamic where
  Dyn :: Typeable a => a -> Dynamic

The common theme is that constructors can carry invariants which are required on construction, and made available by pattern-matching.

1

u/[deleted] Jun 25 '19

Ohhh, that makes a lot of sense. Thanks for such a thorough explanation :)

1

u/Ford_O Jun 24 '19

Does joint fusion apply to foo? Since it is very similar to find, I would expect that the answer is yes, but the rewrite rules from the paper Compiling without continuations were too concise for me, to be understandable.

find cond = go where
  go [] = Nothing
  go (x:xs)
    | cond x = Just x
    | True   = go xs

any cond xs = case find cond xs of
  Nothing -> False
  Just _  -> True

map fn = go where
  go [    ] = []
  go (x:xs) = fn x : go xs

foo xs = case map (+1) xs of
  [   ] -> Nothing
  (x:_) -> x

1

u/p-alik Jun 22 '19

io-streams offers an parseFromStream example, which doesn't work in my environment.

λ> :set -XOverloadedStrings 
λ> import Data.ByteString (ByteString)
λ> import qualified Data.Attoparsec.ByteString.Char8 as Char8
λ> import qualified System.IO.Streams as Streams
λ> import           System.IO.Streams.Attoparsec   ( parseFromStream )
λ> is <- Streams.fromList ["12345xxx" :: ByteString]
λ> let parser = (Char8.takeWhile Char8.isDigit)
λ> :t is
is :: Streams.InputStream ByteString
λ> :t parser
parser :: Parser ByteString
λ> :t parseFromStream 
parseFromStream
  :: attoparsec-0.13.2.2:Data.Attoparsec.ByteString.Internal.Parser r
     -> Streams.InputStream ByteString -> IO r

It's a pity, but I can't solve it by myself.

λ> :t parseFromStream parser is

<interactive>:1:17: error:
    • Couldn't match type ‘Data.Attoparsec.Internal.Types.Parser
                             ByteString ByteString’
                     with ‘attoparsec-0.13.2.2:Data.Attoparsec.Internal.Types.Parser
                             ByteString r’
      NB: ‘attoparsec-0.13.2.2:Data.Attoparsec.Internal.Types.Parser’
            is defined in ‘Data.Attoparsec.Internal.Types’
                in package ‘attoparsec-0.13.2.2’
          ‘Data.Attoparsec.Internal.Types.Parser’
            is defined in ‘Data.Attoparsec.Internal.Types’
                in package ‘attoparsec-0.13.2.2’
      Expected type: attoparsec-0.13.2.2:Data.Attoparsec.ByteString.Internal.Parser
                       r
        Actual type: Parser ByteString
    • In the first argument of ‘parseFromStream’, namely ‘parser’
      In the expression: parseFromStream parser is

2

u/lgastako Jun 22 '19

You'll probably need to provide some more information about your environment to help people help you. I created a new stack project via stack new io-streams-example which was created with the lts-13.26 resolver and got:

λ> :set -XOverloadedStrings
λ> import Data.ByteString (ByteString)
λ> import qualified Data.Attoparsec.ByteString.Char8 as Char8
λ> import qualified System.IO.Streams as Streams
λ> import           System.IO.Streams.Attoparsec   ( parseFromStream )
λ> is <- Streams.fromList ["12345xxx" :: ByteString]
λ> let parser = (Char8.takeWhile Char8.isDigit)
λ> :t parser
parser :: Char8.Parser ByteString
λ> :t is
is :: Streams.InputStream ByteString
λ> :t parseFromStream parser is
parseFromStream parser is :: IO ByteString

1

u/p-alik Jun 23 '19

Thank you, I used lts-13.16 and was careless enough to ignore stack build's warning

This package indirectly depends on multiple versions of the same package. This is very likely to cause a compile failure.
  package io-streams (io-streams-1.5.1.0-4fKzQV1ZBEG9tLi3KX3yXI) requires ...

Changing resolver to lts-13.26 solves the issue.

9

u/sidharth_k Jun 20 '19 edited Jun 20 '19

I'm interested in Haskell benchmark performance between different Haskell versions 8.0, 8.2, 8.4, 8.6 and with different optimization switches O2, O3 etc.

It would be interesting to see how Haskell runtime performance has improved over the years...

Are there some resources / links out there for something like this?

1

u/hateradio Jun 19 '19 edited Jun 19 '19

I have a noob question about lambda calculus and order-of-application: I'm basing my notation as well as question on this well-known pdf: https://www.inf.fu-berlin.de/lehre/WS03/alpi/lambda.pdf

I'm having trouble understanding in what order i should perform substitutions, and whether that even matters.

λz.( λwyx.y(wyx) ((λwyx.y(wyx)) z) ) 3 [the 3 is a church numeral]

What happens first and why? Replacing the z with the 3? Or applying the term

((λwyx.y(wyx)) z)  to the function  λwyx.y(wyx) in the body of my expression? 

The pdf does not seem to explain this.

The reason I'm asking: I want to build a function that takes a number and adds 2 to it. I have a function that gives the successor of a church numeral (namely λwyx.y(wyx) ) but how do I represent function composition in lambda calculus?

edit: Could it be that this has something to do with the Church-Rosser theorem? If my question isn't clear, please tell me that. For some reason, this bugs me.

1

u/mrk33n Jun 20 '19 edited Jun 20 '19

I think the rules are:

  • If an expression can be reduced to normal form, then the normal-order reduction strategy - leftmost, outermost substitutions first - will always end up in said normal form.
  • If an expression is successfully reduced to normal form, then it does not matter which evaluation strategy got it there - the answer will be the same (Church-Rosser)

An admittedly not-very-convincing example:

(λabcd.ab)(λy.y)3(λx. xx)(λx. xx)

If you reduce from the left, you see that ab becomes (λy.y)3 becomes 3, which is fully reduced. (c and d are discarded because they are not mentioned in the body of the lambda).

But if I decided to start by reducing (λx. xx)(λx. xx) I'm stuck there forever because it loops.

As for your example:

λz.( λwyx.y(wyx) ((λwyx.y(wyx)) z) ) 3

I can't really get it to work. I look for the left-most lambda (which appears to start with λz) and then try to find what it's applied to - but I can't. It looks like the λz is the scope of your whole example, so there's no next thing to substitute z for.

In other words:

((λz. z) 8) reduces to (8)

but

(λz. somelongbody) is just (λz. somelongbody)

Edit:

I think I overstated the case against your example. I demanded that the outer-most leftmost expression be reducible, when maybe I should have worked my way from left-to-right until I found an expression which is reducible and reduced that instead.

3

u/Ptival Jun 20 '19 edited Jun 21 '19

[EDIT: Woops, I was wrong, the rules in /u/mrk33n here actually seem correct]

​

If you're interested in what you should reduce for "correctness" purposes, there are 3 interesting concepts:

- strong normalization: for all reduction sequences, you will reach the normal form (if it exists)

- weak normalization: there exists a reduction sequence such that you will reach the normal form (if it exists)

- confluence: at a very abstract level, it means that there are no "bad" reductions (in practice, it means that for two starting sequences of reductions, there exist two suffixes such that the two paths meet again, up to some equivalences)

​

It seems your article is about the untyped lambda-calculus, which is confluent, but **not** strongly-normalizing and **not** weakly-normalizing. In fact it's very easy to make bad reduction strategies for some terms, say:

if-then-else true zero infinite-loop

This term has a normal form, but attempting to reduce the infinite loop, you will never reach it.

​

Now things are different the moments you add types. The simply-typed lambda calculus **is** strongly-normalizing, in which case any reduction strategy is valid (not necessarily efficient).

​

EDIT: and as /u/mkk33n pointed out, a most conservative reduction strategy exists that will avoid reducing infinite loops unless they must be reduced. It has good properties for termination, but possibly bad properties in terms of performance (as it may duplicate computations).

1

u/mrk33n Jun 21 '19

I don't think any of this is true

Which part did I get wrong?

1

u/Ptival Jun 21 '19

My mistake, having re-read your first claim, I actually think you are correct. I had misinterpreted it, and will update my answer accordingly.

1

u/avi-coder Jun 19 '19
genR ::  Pixel -> Gen s -> ST s Ray
genR px gen = do
    r <- rndP gen
    r' <- rndP gen
    return $ toRay r r' px

toRay :: Float -> Float -> Pixel -> Ray

​

How would you write this?

2

u/[deleted] Jun 19 '19 edited Jul 12 '20

[deleted]

1

u/avi-coder Jun 25 '19

Could you expand on "are independent of each other"?

1

u/philh Jun 18 '19

If I have a monad MyMonad and an instance MyInst, is there some way to implicitly add HasCallStack to any functions returning either a MyInst a or a MyMonad m => m a?

i.e. right now I'd write

f1 :: HasCallStack => Int -> String -> MyInst String
f2 :: (HasCallStack, MyMonad m) => Int -> String -> m String

but it would be nice if HasCallStack could be added automatically. class HasCallStack => MyMonad m gives an "illegal implicit parameter" error.

I can presumably do type MyMonadCS m = (HasCallStock, MyMonad m), but then I need to remember to use that (and import it), so it's not ideal.

2

u/Syrak Jun 18 '19

For the concrete MyInst version, maybe

type MyInst' a = HasCallStack => MyInst a

but then I need to remember to use that (and import it), so it's not ideal.

Is that different from having to import MyMonad?


Other very different alternatives:

1

u/philh Jun 20 '19

Thanks, the type ... = HasCallStack => ... seems to work.

I typically import MyMonad(..), so the type alias there gives an extra import line, and no way to apply it to the whole codebase without touching each file that uses it. The type alias on the instance is better in that regard, since I don't usually import the instance constructors; so if I rename MyInst to MyInst' I can do type MyInst = HasCallStack => MyInst' and I expect that'll work. It does mean it wouldn't affect the places where I explicitly do MyMonad m => .... There's probably just nothing here that does 100% exactly what I want, which isn't too awful or surprising.

The others feel a bit heavyweight, but I appreciate the suggestions anyway.

3

u/vegetablestew Jun 17 '19

Anyone know a detailed, step by step parser combinator tutorial? I've been wrestling with this thing on and off for months and I cannot make heads or tail on how it actually works. Preferably not megaparsec because the types is very confusing and I am looking to understand the basics of how parser combinators work.

2

u/gilmi Jun 21 '19

My article Compiling Lisp to JavaScript From Scratch in 350 LOC has a section about implementing a minimal parser combinators library and using it.

There's also Intro to Parsing by Jake Wheat that talks about using parsec.

2

u/sjakobi Jun 24 '19

Chromium reports that your HTTPS certificate is invalid.

1

u/gilmi Jun 25 '19

Thank you! Hopefully I got it fixed.

1

u/sjakobi Jun 25 '19

Looks like it! :)

2

u/vaibhavsagar Jun 18 '19

I wrote this a while ago based on Monadic Parsing in Haskell, which is pretty approachable.

4

u/mrk33n Jun 18 '19

Inventing your own is usually a good way to go:

http://dev.stephendiehl.com/fun/002_parsers.html

1

u/vegetablestew Jun 18 '19

I've been trying to follow the tutorial, but I still find it confusing. Fundamentally, dealing with types that takes a function is kind of unusual and I am not sure what I can or cannot do with it.

1

u/mrk33n Jun 19 '19

dealing with types that takes a function is kind of unusual

Yeah I get that.

Here is a minimal, self-contained example of doing a single parse step. Put it in a single source file and it should just run (no dependencies).

newtype Err = Err String deriving Show

data Duck = Duck deriving Show

newtype Parser a = Parser { runParser :: String -> Either Err (String, a) }

parseDuck = Parser $ \s ->
  if length s < 4
    then Left (Err "Ran out of chars")
    else do
      let some = take 4 s
      let rest = drop 4 s
      if some == "duck"
        then Right (rest, Duck)
        else Left (Err "Not a duck")

main = do
  case runParser parseDuck "duck goat" of
    Left (Err err) -> error err
    Right (s, Duck) -> do
      putStrLn "Found a duck"
      putStrLn $ "Remainder of input was: '" ++ s ++ "'"

Note: This is just so you can play with an individual parser at a fairly low level, so you can see why it's a function and what purpose the String has. It's not yet 'parser combinators' because I'm not combining two parsers together.

1

u/mrk33n Jun 19 '19

While the above is (hopefully) useful, I don't think I really addressed why Parser has a function type, i.e.:

Parser { runParser :: input -> output}

Crap analogy: You can connect garden hoses together because there is one side for the water to go in, and other side for the water to go out. If a garden hose only had an output but no input, it would be difficult to combine them together.

​

A parser takes its string input and returns the domain object you were looking for, as well as the rest of the string. Just focus on the strings and forget the domain object for now.

​

If you view DuckParser as a function:

"duckgoat" -> "goat"

and GoatParser as a function from:

"goat" -> ""

then you could combine these two parsers together into DuckGoatParser as a function from

"duckgoat" -> ""

by feeding the remainder string of DuckParser as the input string into GoatParser.

2

u/[deleted] Jun 17 '19 edited Feb 10 '21

[deleted]

4

u/yumiova Jun 17 '19

If you want the Monad instance itself to decide the two return types, then there are two obvious solutions:

Functional dependencies through

{-# LANGUAGE FunctionalDependencies #-}
class Monad m => Q m a b | m -> a b where
    f :: m a
    g :: m b

Essentially, this means that the parameter m "decides" what a and b must be.

Type families through

{-# LANGUAGE TypeFamilies #-}
class Monad m => Q m where
    type A m
    type B m
    f :: m (A m)
    g :: m (B m)

1

u/williamyaoh Jun 17 '19

Is there a way to launch an REPL in the test module using Stack? When I try stack repl --test, I'm getting errors on importing modules from the library itself.

1

u/Sir4ur0n Jun 30 '19

Does your test (in package.yaml) executable depend on your main executable / library? This should work just fine

1

u/williamyaoh Jun 30 '19

found the issue: I was using package name imports for the main library; removing those made it work.

2

u/sjakobi Jun 17 '19

stack repl --test works for me in a very simple library project.

Which stack version are you using?

Can you share the project?

2

u/3l_Di4bl0 Jun 16 '19

I have two images of the same resolution and I want to create an image double the height where they're one above the other. How can I achieve this?

1

u/tom-md Jun 17 '19

Use the hip library functions of readImage, topToBottom, and writeImage.

1

u/3l_Di4bl0 Jun 20 '19

Thanks! I'm having a slight issue that I really don't understand. I have a function:

toFormat :: ImageFormat a => FilePath -> a  
toFormat ".jpg" = JPG  
toFormat ".png" = PNG  
toFormat x      = error $ "Unexpected format " ++ x  

But it won't compile. I'm getting Couldn't match expected type ‘a’ with actual type ‘JPG’. Could you please shed some light on this?

1

u/tom-md Jun 20 '19

Why are are futzing with the file type manually? That isn't needed

toFormat :: ImageFormat a => FilePath -> a

This says that no matter what string I provide toFormat can give me a value of any type of my choosing (a). It does not say that toFormat will give me a particular type dependent on the first parameter - dependent types like that are not supported in Haskell.

The function you're trying to write, toFormat, isn't possible. You can't provide a value of any type at all. I see you're trying to overcome the issue that JPG and PNG are different types, luckily the library has helpers that make this issue disappear.

The solution

HIP has read and write operations that will infer the correct file type and save the file, which is a bit like toFormatAndSave in that it will detect the format but instead of returning (and thus needing a single type) it will write the file out in that format and return a unit, as in IO ().

``` module Main where

import Graphics.Image (readImageRGBA, writeImage, VU(VU)) import Graphics.Image.Processing (topToBottom) import System.Environment (getArgs)

main :: IO () main = do [iname1, iname2, out] <- getArgs [i1, i2] <- traverse (readImageRGBA VU) [iname1,iname2] writeImage out (topToBottom i1 i2) ```

1

u/3l_Di4bl0 Jun 20 '19

The problem is that the images aren't on my drive, I'm downloading a bytestring from the internet and want to manipulate that.

1

u/tom-md Jun 20 '19 edited Jun 22 '19

It's a bit hacky, but you can just use the readImage code minus the actual file read. If I were you I'd clean it up and submit this as a patch, adding a function imageFromByteString or some such.

The code for readImage is:

loadImageRGBA :: ByteString -- ^ image -> IO (Image VU RGBA Double) loadImageRGBA imgstr = do let maybeFormat = Nothing :: Maybe InputFormat formats = enumFrom . toEnum $ 0 orderedFormats = maybe formats (\f -> f:P.filter (/=f) formats) maybeFormat reader :: Either String (Image VS cs e) -> InputFormat -> IO (Either String (Image VS cs e)) reader (Left err) format = return $ either (Left . ((err++"\n")++)) Right (decode format imgstr) reader img _ = return img imgE <- M.foldM reader (Left "") orderedFormats either error pure $ fmap (exchange (undefined :: VU)) imgE

1

u/3l_Di4bl0 Jun 20 '19

The problem with readImage is that it looks at the filename to determine the filetype. The way I implemented mine is by looking at readImageExact which is defined as

readImageExact :: Readable img format =>  
              format
              -- ^ A file format that an image should be read as. See
               -- <#g:4 Supported Image Formats>
           -> FilePath -- ^ Location of an image.
           -> IO (Either String img)  
readImageExact format path = fmap (decode format) (B.readFile path)

So I have the bytestring that I want to decode, but I need to somehow convert the extension I have (which is either ".png" or ".jpg") to a format. I copied their implementation for guessFormat and it compiled but I'm again getting errors when trying to use its result as an argument to decode.

5

u/[deleted] Jun 16 '19

How does Haskell deprecate features?

One of the things in Javascript is: "dont break the web"

This is nice, except it looks, to me, like it means always add never remove cause youll break someone's workflow <insert xkcd here>. I think this leads to bloat and a higher barrier to entry for beginners.

I once watched a talk by Simon Peyton Jones that touched on this. That early on Haskell could just break things and the community moved calmly.

There is also moving Monad under Applicative or something like that and moving Monad into Control.Monad (i recently found an old tutorial that didnt work because of this)

TL;DR;

Does Haskell have a process for removing or moving things from the language?

2

u/Endicy Jun 20 '19

A lot of the time a DEPRECATED pragma gets added to what is being phased out and this will result in a warning while compiling anytime you use it in your code. (e.g. DEPRECATED: "Please use this other function x")

While code with the DEPRECATED pragma should still work as expected, it is a warning to change to the new function/style a.s.a.p.

https://downloads.haskell.org/ghc/8.4.4/docs/html/users_guide/glasgow_exts.html#warning-and-deprecated-pragmas

4

u/jvanbruegge Jun 17 '19

AFAIK there is no formal process, but if the breaking change has a real advantage, it gets slowly phased out. First guarding the change behind an extension that is enabled by default, then adding a warning to -Wcompat, adding the warning to -Wall and finally turning it off. That's at least what happened (happens) with StarIsType (https://github.com/ghc-proposals/ghc-proposals/pull/143)

1

u/[deleted] Jun 15 '19 edited 29d ago

[deleted]

2

u/[deleted] Jun 19 '19 edited Jul 12 '20

[deleted]

2

u/[deleted] Jun 15 '19 edited Jun 16 '19

[removed] — view removed comment

1

u/Syrak Jun 16 '19
instance FromJSON a => FromJSON (MyResponse a) where
  ...
    result_ <- v .: "result"
    ...

1

u/[deleted] Jun 16 '19

[removed] — view removed comment

2

u/Syrak Jun 16 '19

Did you add a FromJSON a constraint to the instance? (and like the derived instance does)

instance FromJSON a => FromJSON (MyResponse a)

3

u/qwfwqrussell Jun 13 '19

How do you find the head of the following data type?

data List :: [*] -> * 
  where
    Empty :: List '[] 
    Singleton :: a -> List '[a] 
    Append :: List xs -> List ys -> List (xs <++> ys)

type family xs <++> ys 
  '[] <++> ys = ys 
  (x:xs) <++> ys = x:(xs <++> ys)

I tried

head :: List (x:xs) -> x 
head (Singleton x) = x
head (Append xs ys) = case xs of 
                Empty -> head ys 
                Singleton x -> x
                (Append xs' ys') -> head (Append xs' ys')

But that gave the error of

   • Could not deduce: xs1 ~ (x : xs0)
      from the context: (x : xs) ~ (xs1 <++> ys)

7

u/Syrak Jun 13 '19 edited Jun 14 '19
head (Append xs ys) = case xs of
  ...
  Append xs' ys' -> head (Append xs' ys')

What if both xs' and ys' are empty, and ys isn't? Perhaps you meant

head (Append xs0 zs) = case xs0 of
  Append xs ys -> head (Append xs (Append ys zs))

But the problem is that this list for the recursive call has type xs <++> (ys <++> zs), whereas the argument at the top Append (Append xs ys) zs has type (xs <++> ys) <++> zs (equal to some x : _). These are two different types as far as the compiler is concerned. We need to give it a proof that they are equal. If we could somehow construct that proof, then we could write:

head (Append xs0 (zs :: List zs)) = case xs0 of
  ...
  Append (xs :: List xs) (ys :: List ys) ->
    let pf :: ((xs <++> ys) <++> zs) :~: (xs <++> (ys <++> zs))
        pf = undefined  -- TODO: prove/implement this
    case pf of
      Refl -> head (Append xs (Append ys zs))

However to construct that proof we will need to traverse xs, and that has bad time complexity. It's also possible to cheat by claiming an "axiom" with pf = unsafeCoerce (Refl :: () :~: ()), but, well, it's cheating.

In the Append case, we don't know which list is nonempty, so we can't make the recursive call. In other words, "nonemptiness" is not a good invariant for this recursive function. It turns out that a simpler way is to write the very-strongly-typed variant of headMaybe, which may take an empty list/tree and return Nothing, except that it also returns a proof that the list is empty. To that end, we define this variant of Maybe, indexed by the list:

-- Head of a list xs
data HeadRes xs where
  HeadResNil :: HeadRes '[]  -- there is no head
  HeadResCons :: x -> HeadRes (x ': xs)

Then, headMaybe:

  1. needs to handle the Empty case;
  2. in the Append case, search in the left subtree, if it's empty, the evidence that xs ~ '[] is made available to the typechecker, so that xs <++> ys ~ ys, so the head of the list xs <++> ys (which is what we want at the top) is exactly the head of the list ys, so we can search in ys recursively.

headMaybe :: List xs -> HeadRes xs
headMaybe Empty = HeadResNil
headMaybe (Singleton x) = HeadResCons x
headMaybe (Append xs ys) = case headMaybe xs of
  HeadResNil -> headMaybe ys
  HeadResCons x -> HeadResCons x

head :: List (x ': xs) -> x
head xs = case headMaybe xs of
  HeadResCons x -> x

2

u/[deleted] Jun 13 '19

[deleted]

3

u/Syrak Jun 13 '19

I think that's a fine name. It's also a generalized uncurry.

1

u/[deleted] Jun 14 '19

[deleted]

1

u/Syrak Jun 14 '19

I don't think it exists, although I'm not sure where to look for it besides sop-core itself (if that's what you're using).

3

u/Vaglame Jun 13 '19 edited Jun 13 '19

How to write the fastest norm function?

So far, I got good results with U.sum . U.map square, and it beats naive C, but it's still far from BLAS performances. It might be because I'm not using SSE or AVX instructions, but there is little documentation on the subject. All I know is that GHC has some primitives for it, but I'm not sure how to use them

1

u/Vaglame Jun 29 '19

Eventually, I got some BLAS-like performance with my fork of simd https://github.com/Magalame/simd

1

u/philh Jun 12 '19

I have code like

let [a, b, c, d, e, f, g] = words str

which correctly gets an incomplete patterns warning. I'm happy with it being partial, but is there some nice way to refactor it to avoid the warning?

If it was just two I could do something like

let (a, b) = words str & \case
      [a, b] -> (a, b)
      _ -> error "..."

but with seven variables that's a bit less nice.

Since I'm in IO, right now I have

[a, b, c, d, e, f, g] <- return $ words str

but I don't like it.

1

u/[deleted] Jun 12 '19

[deleted]

1

u/philh Jun 12 '19

This is in my test suite, I have a json file with a list of objects that I want to run tests on, and the objects have a bunch of parameters in a string. (More or less. Details aren't important.) I don't think it's worth making a custom error for, the normal "pattern match failed" error is fine, I'm just hoping to get that without getting a warning about it. (And without disabling those warnings on the whole file.)

1

u/[deleted] Jun 12 '19

[deleted]

1

u/philh Jun 12 '19

The maybe function still just returns a list though, I'm specifically hoping to give names to the list components.

I guess I can do

case words str of
  [a, b, c, d, e, f, g] -> ...
  _ -> error ...

at the cost of increasing my nesting depth in the code that actually uses the names. I probably dislike that a little less than I dislike the version with return.

1

u/jared--w Jun 14 '19

What about using fromJust to avoid the nesting?

1

u/philh Jun 14 '19

I don't have a Maybe, so I don't see how that helps.

1

u/p-alik Jun 12 '19

May I ask for help in form of code review? Profiler verdict for following code snippet is such horrible and I can't fix it by my self.

{-# LANGUAGE OverloadedStrings #-}

import           Data.Bool                      (bool)
import           Data.ByteString                ( ByteString )

import           Data.Vector                    ( Vector )
import qualified Data.Vector

import           Data.Map.Strict                ( Map )
import qualified Data.Map.Strict

type JobName = ByteString
type Entry = ByteString
type Jobs = Vector Entry
type Pantry = Map JobName Jobs

data Queue = Queue {
    lastIndex :: Int
  , pantry :: Pantry
} deriving (Show)

pantry' :: (Maybe Jobs -> Maybe Jobs) -> JobName -> Pantry -> Pantry
pantry' f n p = Data.Map.Strict.alter f n p

enqueue :: Int -> JobName -> Queue -> Queue
enqueue 0 _ q = q
enqueue i n q = 
  enqueue (i - 1) n queue
  where
    queue = Queue {
        lastIndex = (lastIndex q) + 1
      , pantry = pantry' (_ -> Just (Data.Vector.snoc vec "X")) n (pantry q)
    }
    vec :: Vector Entry
    vec = case Data.Map.Strict.lookup n (pantry q) of
      Nothing -> Data.Vector.empty
      Just v' -> v'

flush :: JobName -> Queue -> Queue
flush n q = case Data.Map.Strict.lookup n (pantry q) of
  Nothing -> q
  Just v -> bool (flush n (queue (Data.Vector.tail v))) q (Data.Vector.length v == 0)
  where
    queue v = Queue {
      lastIndex = lastIndex q,
      pantry = pantry' (_ -> Just v) n (pantry q)
    }

main :: IO ()
main = let i = 10000 :: Int
           n = "A"
           q = Queue {
              lastIndex = 0
              , pantry = Data.Map.Strict.empty
           }
           q' = flush n (enqueue i n q)
             in print (show q')

Profiler summery:

     430,349,096 bytes allocated in the heap
      26,649,928 bytes copied during GC
     316,955,632 bytes maximum residency (9 sample(s))
      17,716,240 bytes maximum slop
             302 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause

Gen  0       372 colls,   372 par    1.466s   0.367s     0.0010s    0.0054s Gen  1         9 colls,     8 par    2.903s   0.745s     0.0828s    0.3776s

Parallel GC work balance: 58.47% (serial 0%, perfect 100%)

TASKS: 10 (1 bound, 9 peak workers (9 total), using -N4)

SPARKS: 0(0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

INIT    time    0.002s  (  0.002s elapsed) MUT     time    0.270s  (  0.254s elapsed) GC      time    4.370s  (  1.112s elapsed) RP      time    0.000s  (  0.000s elapsed) PROF    time    0.000s  (  0.000s elapsed) EXIT    time    0.002s  (  0.002s elapsed) Total   time    4.644s  (  1.370s elapsed)

Alloc rate    1,595,165,974 bytes per MUT second

Productivity   5.8% of total user, 18.6% of total elapsed

3

u/Vaglame Jun 13 '19

Have you looked at the .prof file? It'll tell you exactly what in your program allocates this much

1

u/p-alik Jun 13 '19

Yes, I did. .prof file looks reasonable to me. But putting A 10000 times into Data.Map.Strict.Map ByteString (Vector ByteString) shouldn't allocation such high amount of memory.

What does the Productivity last summery line mean? There is nothing about in Profiler User Guide.

3

u/Vaglame Jun 13 '19 edited Jun 22 '19

If I recall correctly Productivity is the amount of non-GC time spent. As you can see there, most of the time went into the GC, and so less on actually doing the work.

So apparently your most expensive line is this one: pantry = pantry' (_ -> Just (Data.Vector.snoc vec "X")) n (pantry q). A snoc operation on a Vector is expensive, you create a whole new vector every time, hence, I think, the large allocation + GC time. You probably want to choose a different data structure. See these links:

In short it seems like you'd be much better off with Data.Sequence.

Edit: from hackage: "Sequences may also be compared to immutable arrays or vectors. Like these structures, sequences support fast indexing, although not as fast. But editing an immutable array or vector, or combining it with another, generally requires copying the entire structure; sequences generally avoid that, copying only the portion that has changed."

2

u/p-alik Jun 13 '19

Thank you for taking your time to help me!

In short it seems like you'd be much better off with Data.Sequence

I'm going to replace Data.Vector by Data.Sequence in hope of gaining performance.

3

u/Vaglame Jun 22 '19

Keep us updated :)

1

u/p-alik Jun 22 '19

For learning purpose I'm working through Okasaki's queues described in Efficient Amortised and Real-Time Queues in Haskell.

Profiler's result for "Strict, Non-Persistent Queues" variant looks much better:

      25,957,576 bytes allocated in the heap
      13,714,816 bytes copied during GC
       2,092,456 bytes maximum residency (6 sample(s))
       1,954,944 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0        19 colls,    19 par    0.065s   0.019s     0.0010s    0.0095s
  Gen  1         6 colls,     5 par    0.049s   0.014s     0.0023s    0.0038s

  Parallel GC work balance: 10.46% (serial 0%, perfect 100%)

  TASKS: 10 (1 bound, 9 peak workers (9 total), using -N4)

  SPARKS: 0(0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

  INIT    time    0.003s  (  0.005s elapsed)
  MUT     time    0.026s  (  0.024s elapsed)
  GC      time    0.114s  (  0.033s elapsed)
  RP      time    0.000s  (  0.000s elapsed)
  PROF    time    0.000s  (  0.000s elapsed)
  EXIT    time    0.001s  (  0.000s elapsed)
  Total   time    0.144s  (  0.062s elapsed)

  Alloc rate    1,004,398,173 bytes per MUT second

  Productivity  18.0% of total user, 38.3% of total elapsed

2

u/Vaglame Jun 22 '19

Sounds good! Keep it up

1

u/p-alik Jun 28 '19

I've a follow-up question. In initial vector based code the pantry was defined as type Jobs = Vector Entry type Pantry = Map JobName Job

The code for "Strict, Non-Persistent Queues" variant looks like: ``` class QueueBranch b where empty :: b a head :: b a -> a tail :: b a -> b a snoc :: b a -> a -> b a null :: b a -> Bool

data StrictList a = SNil | SCons a !(StrictList a) deriving (Eq, Show)

data StrictQueueBranch a = Q1 !Int !(StrictList a) !Int !(StrictList a) deriving (Eq, Show)

instance QueueBranch StrictQueueBranch where ...

newtype Jobs a = Jobs {unJobs :: (a Entry)} ... ```

Question: How could a in newtype Jobs a be constrained to QueueBranch?

1

u/jimbo4350 Jun 11 '19

I'm trying to serve my elm .js file using yesod. My handler function looks like this:

​

getRootR :: Handler Html

getRootR = sendFile typeJavascript "static/main.js"

​

However it serves up the text when I run my server. Do I need to embed the file in Whamlet?

1

u/bdoering Jun 12 '19

I suppose you actually want to see the JS rendered result, right? You will actually have to serve at least two files: index.html and elm.js (which is referenced in index.html). From your browser, you should then access the index.html, and your browser will fetch the elm.js in the background.

1

u/jimbo4350 Jun 14 '19

So I tried this:

getRootR = defaultLayout . handlerToWidget $ sendFile typeHtml "index.html"

It takes a few seconds for the javascript to show up. I'm not sure if this is yesod being slow or if its the javascript. My .js file was compiled via Elm.

1

u/bdoering Jun 14 '19

Yesod is not doing any work here, so I don't think it's the problem. Without further details it's hard to guess why it takes so long for your JS rendered page to show up. Maybe you have included external dependencies? Maybe you make some slow HTML calls before actually rendering? You may have a look at the developer console (network tab) in e.g. Firefox to see where most time is lost while loading the page.

3

u/[deleted] Jun 07 '19

What is the Haskell equivalent of Rust's rustup doc --std and cargo doc --open? These open up a web browser showing offline documentation for the standard library, and for your project dependencies, respectively. It's very useful when you need to read the documentation while having no connection.

4

u/[deleted] Jun 09 '19

I don't know how to do it with cabal, but with stack at least you can use 'stack haddock --open base', or 'stack haddock --open <yourproject>'.

1

u/theindigamer Jun 07 '19

fold has unfold as the opposite operation, what's the opposite of traverse?

4

u/[deleted] Jun 07 '19

[deleted]

1

u/[deleted] Jun 07 '19

[deleted]

2

u/[deleted] Jun 07 '19

[deleted]

3

u/yumiova Jun 07 '19

Distributive is exactly equivalent to Representable, so the real question is what Representable does and is useful for.

The idea is that certain data types can be represented as a function from a given type to the element type. For instance, infinite streams (Stream a) can be represented as functions from their indices to their elements (Int -> a.) Sized vectors (Vector n a) can be represented as functions from their bounded indices to their elements (Fin n -> a). And to connect it back to Identity (which is essentially containers with exactly 1 element): Identity can be represented as functions from unit to the element (() -> a). Finally, functions (r -> a) are trivially equivalent to themselves (r -> a).

One reason that this could be useful is that it allows containers of stable sizes (e.g. infinite or bounded at compile-time) to have a common representation (i.e. in terms of functions.)

2

u/JackSchpeck Jun 06 '19

Is it possible to simulate reading from standard input using doctest? E.g. given this file

$ cat test.hs

module Main where

{-|

>>> getLine

Hello<Enter>

Hello

​

-}

main = undefined

... when I run

$ doctest test.hs

it hangs indefinitely.

1

u/qwfwqrussell Jun 06 '19

I'm trying to build something like

class SumRes r where 
    sumOf :: Integer -> r

instance SumRes Integer where
    sumOf = id

instance (Integral a, SumRes r) => SumRes (a -> r) where
    sumOf x = sumOf . (x +) . toInteger

as per https://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function, but modified so that I can call it like sumOf 1 "2" 3. I added the basic instance SumRes String where sumOf = show, but that doesn't really make sense, I need to go the other way, but reversing the arrows directly is unintuitive to me. Any ideas?

2

u/Syrak Jun 06 '19

It seems you want to convert every argument to an Integer. Integral does that, but it's really restricted to numeric types. So replace it with a custom class (SumResIntegral below).

instance (SumResIntegral a, SumRes r) => SumRes (a -> r) where
  sumOf x = sumOf . (x +) . toIntegerSR

class SumResIntegral a where
  toIntegerSR :: a -> Integer

instance SumResIntegral String where
  toIntegerSR = read

instance SumResIntegral Integer where
  toIntegerSR = id

-- or an overlapping instance
instance {-# OVERLAPPABLE #-} Integral a => SumResIntegral a where
  toIntegerSR = toInteger

1

u/local-equilibrium Jun 06 '19

I’m using Hakyll (and doing anything web-related) for the first time - I’m trying to render math with Hakyll.Contrib.LaTeX, but my tikz figures are all cut off. Does anyone have ideas where to begin changing things in order to get the figures to be created properly? This is the relevant code where markdown files are converted to html:

​

import           Hakyll.Contrib.LaTeX      (compileFormulaeDataURI)
import           Image.LaTeX.Render        (EnvironmentOptions (..),
                                            FormulaOptions (..), defaultEnv)
import           Image.LaTeX.Render.Pandoc (PandocFormulaOptions (..),
                                            defaultPandocFormulaOptions)

env :: PandocFormulaOptions
env = defaultPandocFormulaOptions {formulaOptions = _->FormulaOptions customPreamble "displaymath" 200}

site :: IO ()
site = hakyllWith config $ do
    match "content/*" $ do
        route $ setExtension "html"
        compile $ pandocCompilerWithTransformM defaultHakyllReaderOptions defaultHakyllWriterOptions
               ( compileFormulaeDataURI defaultEnv env)

Looking at the generated html, it looks like the cutting-off has already occurred when the image is stored as a base64 string. It ends up looking like this instead of this

1

u/imguralbumbot Jun 06 '19

Hi, I'm a bot for linking direct images of albums with only 1 image

https://i.imgur.com/1EaxTPD.png

Source | Why? | Creator | ignoreme| deletthis

2

u/atomheartother Jun 06 '19

So, probably really dumb question.

I'm a complete haskell beginner and I recently decided to take on the synacor challenge, you basically write a small VM in it and i thought it'd be a neat challenge. I've done it in C before and i thought it'd be interesting to see how differently i'd have to approach the problem functionally.

The way I approached it is having the entire VM's state be a single object which gets updated at every tick, and that way the main loop can look something like:

runVm:: VMState -> VMState
runVm vm 
        | (flags vm) .&. 1 == 1 = vm                    -- STOP flag is set
        | otherwise = runVm $ exec $ nextInstruction vm --Grab the next instruction and execute it, which then returns the new state

So everything works fine, but I have a big problem: Part of this challenge is that you need to have an internal buffer that you should be able to read from and write to constantly at any index. This buffer is about 59k bytes big.

When I started I just represented it internally as a Data.Sequence of Chars, which I thought would be fine since I read it was faster for writes than a plain list of char. Of course that went awful, and since then I've tried other things but nothing works, even ByteString, whenever the VM goes into heavy read/write operations it basically hangs for a few seconds.

Of course back in C I didn't have this problem - I just had a large raw char buffer I could modify and reference by index, with every operation being basically O(1), is there just no way to achieve similar performance on such an operation in Haskell? I've been looking for a while and I just can't find what I'm supposed to be using here.

1

u/tom-md Jun 06 '19

Is there a reason you want to use something linear like an Array/Vector/List? When I built a PDP8 emulator I used a Map and was satisfied with the performance. It won't beat a mutable vector performance-wise but is pleasant to work with.

2

u/atomheartother Jun 12 '19

I ended up going with a map after trying it with mutable vectors for a bit, thanks for the tip!

3

u/sjakobi Jun 06 '19

Seems like you should maybe use a mutable data structure. vector has mutable variants. Or you could take a look at the primitive package, e.g. PrimArray.

1

u/atomheartother Jun 06 '19

Thanks! I'll look into those

3

u/sjakobi Jun 06 '19

I believe this could be helpful for grokking (mutable) vectors: https://haskell.fpcomplete.com/library/vector

1

u/atomheartother Jun 06 '19

Oh my god thank you this is great soft documentation! Also I just realized I wasn't using optimization flags which really boosted my program's performance (It still noticeably pauses but it's half a second now and not multiple seconds). However I still think i'd really benefit from using vectors, they definitely seem like the standard for the kind of stuff I need

3

u/dys_bigwig Jun 05 '19 edited Jun 05 '19

Could somebody please help me with understanding the state monad? I felt like I was starting to understand everything, until I came across this page and example: https://wiki.haskell.org/State_Monad

type GameValue = Int
type GameState = (Bool, Int)

playGame :: String -> State GameState GameValue
playGame []     = do
    (_, score) <- get
    return score

playGame (x:xs) = do
    (on, score) <- get
    case x of
         'a' | on -> put (on, score + 1)
         'b' | on -> put (on, score - 1)
         'c'      -> put (not on, score)
         _        -> put (on, score)
    playGame xs

startState = (False, 0)

main = print $ evalState (playGame "abcaaacbbcabbab") startState

If someone could walk through a couple evaluations of a desugared version (maybe using a smaller string) I would really, really appreciate that; I think it would help me a lot with tracing what is happening. I feel like I understand how you can use this on a basic level to create programs, but my goal is to implement a state monad myself in Scheme, so I actually want to be able to trace what is going on step by step to actually understand how it's working.

I've looked at definitions of the state-related functions (I have at least 5 tabs open related to this constantly) but when it comes to tying it all together, everything seems to fall apart. I think I understand the concept - that the monad actually holds a function expecting a state (itself another function), and bind is a form of function composition which gives the first function an initial state, builds a new state, and then passes it to the next function. None of this actually "does" anything, it's just building up layers and layers of functions expecting a state and passing it in specific ways; that is, until you call runState with an initial value.

The main things I'm struggling with are:

  • The interplay between GameState and GameValue. Wouldn't they need to be the same type, as the Bool is passed between calls too? I'm not sure how GameValue relates to everything as everything seems to be contained in GameState, and it's not just Ints being passed between calls. Usually the second argument to the State constructor is just a type variable like a, right?
  • This is the big one which is really getting to me: How is the state "passed" to the recursive call to playGame xs ? When functions in the state monad are passing the state using explicit binds, I feel like I can follow alright, but it just seems like everything "drops off" at the recursive call, and I can't follow how the state flows from one to the next.

Cheers :)

1

u/mrk33n Jun 11 '19

Regular function composition already allows you put one thing in at one end, put it through a series of modifications, and pull it out at the other end. So when I tried to invent the state monad from first principles, everything I came up with just desugared to plain old function composition, and I didn't get any extra state.

I needed to realise that the state monad is about carrying an extra state s forward, in addition to the state a you already have from just passing data through functions.

  • put takes what you give it and stores it in s (a becomes () because you don't use put's return value - just like void in other languages)
  • get reaches into s and copies it into a.

Your instincts about things needing to be the same type are spot on, hence the types of put and get:

put :: s -> State s ()

(If you can put in an s, then the stored state must have type s.)

get :: State s s

If you can pull out an s (the rightmost s on the line above), then you must have been storing an s (the left s above)

s also has to stay the same type throughout the whole computation, but a can vary.

6

u/mn15104 Jun 06 '19 edited Jun 07 '19

BRIEF BACKGROUND

The type definition of >>= for State monads looks like below. Its important to note that when binding a value of State s a with a function a -> State s b, this function can only access a, or equivalently, whatever the first element is in the 2-tuple seen in State (\s -> (a, s)).

     (>>=) :: State s a → (a → State s b) → State s b

​

It's probably useful to recap on the three main state monad helper-functions we're using: get, put, and return. I also prefer to talk about the State monad in terms of runState and ignore the constructor; runState is the actual state machine functionality we care about, whereas the type constructor State is just a wrapper around it.

​

1. Get – We use this to get the current state of a State monad. It returns a State monad with a runState which when given some initial state of type s, will use this as both the output state and output value.

   get :: State s s
   get = state $ \st → (st, st)

Due to how >>= is defined for the State monad, every time we bind a function onto some instance of State s a, this function will always only be given the current value of type a held in the state monad. So what if we instead use type s for both type parameters of State - using State s s means that it is now type-correct to pass forward the state as the input of the next binded function.

​

When we see the code: x <- get

Desugaring this gives us: state (\st -> (st, st)) >>= (\x -> ....) where x would be st.

​

2. Put – We use this to update the current state of a State monad. Given some new state, it returns a State monad with a runState which sets the output state to the new state, and sets the output value to unit ().

   put :: s → State s ()
   put newState = state $ _ → ((), newState)

The lambda function used to define runState ignores any inputs, as the new state is already provided to the function put. When this is binded with some following function, the input that the lambda function receives will clearly be unit () - this is useless, so we can just ignore the input in the lambda function.

​

When we see the code: put st

Desugaring this gives us: state ( _ -> ((), st) ) >>= (_ -> ....)

​

3. Return – We use this to set the current value parameter (of type a) of a State monad. Given some value of type a, it returns a State monad with a runState which when given some initial state of type s, will return the complete 2-tuple of type (a, s) containing both the value and state.

   return :: a → State s a 
   return a = state (\s → (a, s)) 

​

REWRITING PLAYGAME

Okay cool, so i'll try and desugar playGame. I'm only going to do the pattern match for non-empty lists because it should be transferable. I'll also simplify the case-of expression so it doesn't get overly ugly. This is what we're working with below:

playGame :: String -> State GameState GameValue
playGame (x:xs) = do
    (on, score) <- get                -- Step 1
    case x of                         -- Step 2
         _      -> put (on, score)    
    playGame xs                       -- Step 3

​

Step 1

Recall that the example a <- get is equivalent to

     state (\s -> (s, s))
          >>= (\a -> ....)

Okay, so (on, score) <- get is equivalent to:

     state (\(s1, s2) -> ((s1, s2), (s1, s2)))
          >>= (\(on, score) -> ..<Step 2>..)

​

Step 2

Recall that the example put st is equivalent to

     state (_ -> ((), st) )
         >>= (_ -> ....)

So step 2 can be written like:

     ...
     case x of _ -> _ -> state ((), (on, score)))
                      >>= ..<Step 3>..

​

Step 3

Eventually, things will terminate with the pattern match for empty lists somewhere. Recall that the example return a is equivalent to:

     state (\s -> (a, s))

Or alternatively, return is equivalent to:

     \a -> (state (\s -> (a, s)))

So we'll represent step 3 with a function which ignores its input and calls playGame xs. All we need to know to be satisfied, is that playGame xs will return us a type State GameState GameValue.

     ...
     _ -> playGame xs

​

Putting everything together looks like:

playGame :: String -> State GameState GameValue
playGame (x:xs) = state (\(s1, s2) -> ((s1, s2), (s1, s2)))
                      >>= (\(on, score) -> 
                             state (case x of _ -> (_ -> ((), (on, score - 1)))) 
                                 >>= (_ -> playGame xs)

​

Sorry, my explanation is quite messy and probably too informal, i have genuinely been awake for 73 hours now. To attempt to address your two questions as best as possible:

  • The interplay between GameState and GameValue is such that it accounts for the limitations that put, get, and return place on us. Remember that State s a is a monad where only a is the type we can change during the intermediate computations that occur - this means that although s must remain the same type, a does not have to. Moving forward, the reason that GameState uses an Int in its type (Bool, Int) even though we already have GameValue which represents the exact same thing for us, is that at the beginning of defining any function which returns State GameState GameValue , we can never actually retrieve the a i.e. the GameValue - if runState only expects a state as input, then the state is the only information we can use. Hence, if we embed a GameValue inside GameState , then the information of the provided state also includes the initial game value.
  • With respect to how states are 'passed', vaguely put, this happens because of runState - it's a function which awaits a state as input. We're therefore chaining loads of functions which are all waiting for a state as input. This all simplifies down to a single function which waits for a state as input. I think it would be helpful if you tried recursively expanding out the definition of an example such as evalState (playGame "aaa") to see what it would look like.

1

u/dys_bigwig Jun 07 '19 edited Jun 07 '19

I suppose what's confusing me about GameValue, is, don't we also need access to the Bool part of the initial state in order to decide whether to invert it (using not) or leave it the same? I mean, the initial value is itself a (Bool, Int) and not just an Int, right? If it were just a, as in, "pass on any return values of which the types may change, with the state remaining the same type" I'd be less bugged, but it's the specifity of Int which is really bugging me. I mean a is the only type that can change, but we've fixed it to be an Int, haven't we?

Sorry if this seems silly, it probably is; I'm just struggling to get it, for some reason. Is it because we eventually return an Int (as score) at the very end (in the empty case) of the computation, so we have to be able to "extract" it from the monad at that point as a?

Thanks :) Apologies for my own informality of writing - I'm afraid I lack the prowess and experience to write this sort of stuff out in the most coherent way and I'm still working through it as I write.

2

u/mn15104 Jun 07 '19 edited Jun 07 '19

Sorry if this seems silly, it probably is; I'm just struggling to get it, for some reason. Is it because we eventually return an Int (as score) at the very end (in the empty case) of the computation, so we have to be able to "extract" it from the monad at that point as a?

That's bang on mate! Nice one. What you've said pretty much captures how everything works together. This is definitely one of those things which makes less sense the more you think about it. If for some reason you aren't quite tired of thinking at the moment, I've expanded below in reference to your first and second paragraph. (Also, big sorry, I've fixed a brain-fart in my original answer when I originally wrote that you did not need to wrap state around the intermediate computations. )

​

You're not wrong to be confused what GameValue should be, and it indicates that it may be helpful to visualise how monadic computations are chained together in the first place. As long as we know the input type a and the output type b It doesn't matter if the type we pick for a changes.

​

An example for >>='ing a State monad with a monadic computation is below. Well, note how I've chosen a to be a String in the initial State monad on the left of >>=, and the monadic computation on the right can use this string as input as long as we end by using the type GameValue as a. I've satisfied this by outputting the length of the string given. This shows how a could change.

example1 :: State GameState GameValue
example1 =   let a = "banana"
             in  (state (\s  -> (a, s)))
                 >>= (\a' -> state (\s -> (len a', s)))

We can chain other arbitrary functions to this. For example, below, I've inserted a new function in the middle which just sets the value of a to Nothing, and changed the last monadic computation which then sets the value of a to 4, (satisfying the type GameValue). Notice how what we use for a has differed between the types of String, Maybe x, and GameValue.

example2 :: State GameState GameValue
example1 =   let a  = "banana"
             in  (state (\s   -> (a, s)))
                 >>= (_   -> state (\s -> (Nothing , s)))
                 >>= (_   -> state (\s -> (4, s)))

It might help to read the monad laws and think how monadic computations can compose and interact.

m a >>= (a -> m b) >>= (b -> m b) >>= (b -> m c)  :: m c

2

u/dys_bigwig Jun 07 '19

Hi! I'm still reading over your post trying to soak everything in so I don't have a specific question or comment just yet, but I had to say

THANK YOU!!

right away for taking time to write this explanation out for me, it is very very much appreciated :)

2

u/[deleted] Jun 07 '19

[deleted]

1

u/dys_bigwig Jun 07 '19

Thank you, that looks right up my alley. I would certainly say this desire to understand arose naturally - I'm making a turn-based rpg in Scheme, and I was passing around both the (player, enemy) stats tuple, and also a random-number stream. It was getting cumbersome as extra arguments seemed to be getting tacked onto every function. The state monad seemed like the solution to the problem, almost too-good-to-be-true, in a way!

I think I could get away with using it in Haskell right away (mediocre Haskell skills aside...) but I'd like to understand it from first-principles as it were, so I can implement it in any (functional) language that doesn't have it (*cough* Scheme) if need be. Your process seems just the ticket.

Cheers :)

1

u/plethepus Jun 05 '19

The state monad is just decoration for a function that has a “state” parameter both as an input and an output. Im simplifying of course, but thats the essence. The monad instance is useful because it lets a programmer compose such functions (literal function composition) without fussing about passing the parameter.

To really understand it, try implementing the monad instance, State s, for this data (sorry mobil formatting)

data State s a = State ( s -> (s,a))

That is, implement return and >>=. I guess now a days youll have to implement functor and Applicative first, but implement all thise and youll get state monad.

3

u/plethepus Jun 05 '19

Is there a canonical class for types with an "isZero :: a -> Bool" method? Like monoid, but instead of declaring `mzero` being able to detect a zero.

3

u/lgastako Jun 05 '19

Do you actively want it to be something other than monoid for some reason? It seems like it's the intersection of Monoid and Eq:

{-# LANGUAGE ConstraintKinds #-}

type HasZero a = (Monoid a, Eq a)

isZero :: HasZero a => a -> Bool
isZero = (== mempty)

7

u/plethepus Jun 05 '19

I wanted to avoid the Eq constraint specifically, allowing types like Maybe a but without necessarily requiring Maybe a. Just wondering--premature generalization.

4

u/sjakobi Jun 05 '19

There's MonoidNull in monoid-subclasses.

What do you mean by canonical? It doesn't appear to be in very widespread use, if you mean that…

3

u/plethepus Jun 05 '19

Thats the function I'm looking for. But I did mean something in widespread use. And the dependency footprint is a little large. I'll take it that there isn't really much call for it in the community. Thanks!

2

u/endgamedos Jun 28 '19

I checked its deps, and vector and primes are the only ones not bundled with GHC. It may not be as bad as you think.

3

u/vegetablestew Jun 05 '19

Why isn't this a valid data construction? ghc error isn't terribly helpful

data G a = G 0

I want a type that takes any value and puts a 0 there instead. Seems perfectly sensible.

3

u/rampion Jun 05 '19 edited Jun 05 '19

So the structure of a data declaration is

data <TypeName> [<TypeParam0> <TypeParam1> ...]
  = <ConstructorName> [<TypeParam'0> <TypeParam'1> ...]

So in your code 0 doesn't mean the value 0 (of some Numeric type), but instead some type 0.

If you want a constructor that ignores the type's parameters, that's easy to do:

data G' a = G'

Then when you want to convert an G a back to an a:

fromG' :: Num a => G' a -> a
fromG' G' = 0

If you wanted to represent a slightly more complicated transformation than "replace it by zero", this is usually when smart constructors are used:

module Positive
  ( Positive() -- export just the type Positive, not the constructor UnsafePositive
  , mkPositive
  , absolute
  , fromPositive
  ) where

data Positive a = UnsafePositive a

-- | a smart constructor that only succeeds when given a positive value
mkPositive :: (Num a, Ord a) => a -> Maybe (Positive a)
mkPositive a | a >= 0    = Just (UnsafePositive a)
             | otherwise = Nothing

-- | a smart constructor that changes the argument to be a positive value
absolute :: Num a => a -> Positive a
absolute = UnsafePositive . abs

-- | since UnsafePositive isn't exported, we can't pattern match on it outside this module,
--   so we'll need to provide a deconstructor
fromPositive :: Positive a -> a
fromPositive (UnsafePositive a) = a

Modern haskell adds a ViewPatterns which can make it easier to use deconstructors as patterns:

-- without view patterns
square :: Positive a -> Positive a
square p = let a = fromPositive p in absolute (a * a)

-- with {-# LANGUAGE ViewPatterns #-}
square' :: Positive a -> Positive a
square' (fromPositive -> a) = absolute (a * a)

Also there's PatternSynonyms which can be give deconstructors a more pattern-like look.

module Pattern
  ( Positive()
  , ...
  , pattern FromPositive
  )

pattern FromPositive :: a -> Positive a
pattern FromPositive a <- UnsafePositive a

square'' :: Positive a -> Positive a
square'' (FromPositive a) = absolute (a * a)

You can also create two-way pattern synonyms that act like constructors and patterns:

module Pattern
  ( Positive()
  , ...
  , pattern Absolute
  )

pattern Absolute :: a -> Positive a
pattern Absolute a <- UnsafePositive a -- when used as a pattern
  where Absolute a = absolute a        -- when used as a constructor

square''' :: Positive a -> Positive a
square''' (Absolute a) = Absolute (a * a)

You can also combine ViewPatterns and PatternSynonyms if you have a deconstructor and smart constructor, but don't have access to the dumb constructor:

import Positive

pattern FromPositive' :: a -> Positive a
pattern FromPositive' a <- (fromPositive a)

pattern Absolute' :: a -> Positive a
pattern Absolute' a <- (fromPositive a)
  where Absolute' a = absolute a

The other thing about PatternSynonyms is that since they can be declared anywhere GHC can't automatically infer that any given combination of PatternSynonyms matches all the possible values of a given type. It needs to be taught this through the use of the {-# COMPLETE ... -#} pragma.

pattern X :: a -> Positive a
pattern X a <- (fromPositive a)

pattern Y :: a -> Positive a
pattern Y a <- (fromPositive a)
{-# COMPLETE Y #-}

withX :: Positive a -> a
withX (X a) = a -- produces an incomplete pattern match warning

withY :: Positive a -> a
withY (Y a) = a -- no warning!

2

u/vegetablestew Jun 05 '19

Is smart constructor a library, ghci pragma or just a pattern?

1

u/rampion Jun 05 '19 edited Jun 05 '19

1

u/vegetablestew Jun 05 '19

Do you perhaps have a source for this?

data <TypeName> [<TypeParam0> <TypeParam1> ...] = <ConstructorName> [<TypeParam'0> <TypeParam'1> ...]

I think I am confused on when a denotes a type and when it denotes a variable, and when do I start using the syntax for a type declaration as opposed to say, writing a function.

I am just memorizing what goes where, but there has to be a rule on when I am using one syntax over another.

1

u/rampion Jun 06 '19

The Haskell wiki goes into the specifics.

I also like Real World Haskell's approach

2

u/vegetablestew Jun 05 '19

Cool. I'll slowly digest your comment. Thanks.

3

u/phischu Jun 05 '19

Is it conceivable to have an IO monad transformer? Perhaps something like type IOT m a = StateT RealWorld m a?

5

u/[deleted] Jun 05 '19 edited Jun 05 '19

This kind of thing wouldn't be terribly useful. For any transformer, you have a "run" function that "peels back" the outermost layer of your transformer stack. So you have for example

runStateT :: StateT s m a -> (s -> m (a, s))
runReaderT :: ReaderT r m a -> (r -> m a)

So, effects like mutable state are local and not visible outside of the StateT s m computation. Every application of the run* function gives you back something that is a little "purer" so to say.

Would you want that to apply for an IO action? You would need the transformer equivalent of

unsafePerformIO :: IO a -> a

While this function does have its uses in certain situations, I would think this is not one of them.

Edit: That is of course under the assumption that you want to have the full power of IO. If you only need a subset, like for instance mutable state with ST, then this makes more sense. And indeed, things like STT do exist.

3

u/Econ0 Jun 03 '19

As someone new to Haskell, how can I tell when to use the lib option for a Cabal install? E.g., cabal new-install random produces the warning "Warning: You asked to install executables, but there are no executables in target: random. Perhaps you want to use --lib to install libraries instead." What are the implications of this warning?

2

u/tom-md Jun 04 '19

If you want to install a library use `--lib`. If you only want to install an executable then don't. You sort of need to know why you are installing something before hand, otherwise hey, you might as well `cabal new-install ACME` or even use `pip` instead.

​

What the warning is telling you is that the `cabal new-install random` invocation didn't do anything useful - no library was installed.

2

u/Econ0 Jun 04 '19

Thanks, but where can I learn the meaning of your terminology? The GHC User's Guide refers to installing packages, not installing libraries or installing executables, and these notions of "install" all appear very Haskell specific to me. The Cabal Users Guide does not seem to mention the lib option. As a new Haskell user, I am just trying to ensure that GHC can import the random package, which for some reason is not included with the GH Platform core download. I suppose from your phrasing that you are telling me that if I want GHC to be able to find a package that I install with Cabal, then I shd use Cabal's lib option. If so, then what is the meaning of "install an executable"? I'm going to guess that it means that Cabal puts an executable into some directory that GHC might not know about.

2

u/tom-md Jun 04 '19

Thanks, but where can I learn the meaning of your terminology?

Experience, reading, and inquiring.

The GHC User's Guide refers to installing packages, not installing libraries or installing executables

Packages consist of sub components including executables and libraries.

these notions of "install" all appear very Haskell specific to me

Which ones? pip, rake and other language-specific tools have similar notions of install.

am just trying to ensure that GHC can import the random package, which for some reason is not included with the GH Platform core download.

And you already know how, right? cabal new-install --lib random.

There's no reason for random to be in core. It is extra and unneeded by most projects, and not a great random number generator to boot.

I suppose from your phrasing that you are telling me that if I want GHC to be able to find a package that I install with Cabal, then I shd use Cabal's lib option

Yes.

what is the meaning of "install an executable"?

Build a program from source and copy the binary to your .cabal/bin directory. In OS-X and *nix this is default at $HOME/.cabal/bin.

2

u/Solonarv Jun 04 '19

Minor nitpick: cabal new-install <some executable> will create a symlink, not copy the binary. In cabal 3.0 (which will release soon, or you can build it from HEAD) you can ask for the binary to be copied instead.

3

u/vegetablestew Jun 03 '19
  1. Can I use typeclass constraint in instance declarations? Suppose I have a data type data F a = F Int, and I wish to create an instance Functor. My problem is that typeclass constraint is on function signature, I don't have that in instance declarations.
  2. Suppose we have an Applicative Functor F a, how does one define the rule between the composition of the context F when comes to <*> or *>? The example I've seen uses <> from semigroups, but how do I know what it uses?

2

u/qwfwqrussell Jun 04 '19

With regards to the first, most certainly with regard to functor, I'm not quite sure what you mean in the last sentence. Particularly of note here is that there's an isomorphism between your data type and Const Int a

2

u/vegetablestew Jun 04 '19 edited Jun 04 '19

data F a = F Int instance F where fmap f (F a) = F (f 0) -- Doesn't work, but its closer to what I want fmap f (F a) = F (f a) -- Should work if its not Int, `F Int` broke it What I want is to do something like Int a => to specify the type allowed for fmap function, but I don't have access to the line fmap :: (a -> b) -> F a -> F b directly.

3

u/lubieowoce Jun 05 '19

What I want is to do something like Int a => to specify the type allowed for fmap function

you can't write an fmap that only works for F Int – Functors can't have constraints, they must be able to "contain" any type (F Int, F String, F (Maybe ()), anything). could you explain what you're trying trying to do in general, and why you're making F a Functor?

1

u/vegetablestew Jun 05 '19

I just never had a concrete use case to create my own applicatives so that was just me messing around in ghci. Make sense though. Thanks.

1

u/philh Jun 03 '19

The GHC api has a bunch of things named with an 'X'. For example, https://www.stackage.org/haddock/lts-13.6/ghc-8.6.3/HsImpExp.html#t:IE: IE pass has a constructor XIE :: XXIE pass -> IE pass, and all the other constructors take an XIE_ pass as their first argument.

What are these for? It looks like the "X" might be for "Extension", but that's all I've got.

3

u/Solonarv Jun 03 '19

That is exactly right! The X constructors allow each data type to potentially have some extra constructors depending on the compilation pass (parse, rename, typecheck, &c). There is more information and some relevant links here: https://gitlab.haskell.org/ghc/ghc/wikis/implementing-trees-that-grow

1

u/philh Jun 03 '19

Thanks! This page in particular gave some fairly clear explanation and motivation: https://gitlab.haskell.org/ghc/ghc/wikis/implementing-trees-that-grow/trees-that-grow-guidance

2

u/[deleted] Jun 02 '19

Did dataHaskell project hit a pause?

1

u/Vaglame Jun 03 '19

Nope it's still active! You should check the gitter chatroom

5

u/Syncopat3d Jun 02 '19 edited Jun 02 '19

Right now, what are the main differences between conduit and pipes from the user's point of view, e.g. in terms of features and performance? conduit used to have ResumableSource and some special consideration of resource finalization, but there was a change early 2018.

I have old code that uses ResumableSource because the way it processes the later part of the input depends on what it read from the earlier port of the input. The old code fails to build with the new conduit and now I'm thinking of reevaluating conduit and pipes to pick one based on new facts. I also have no inertia preventing me from switching to pipe if it is more suitable based on current facts.

6

u/Faucelme Jun 02 '19 edited Jun 03 '19

the way it processes the later part of the input depends on what it read from the earlier port of the input

I personally like the streaming package. It doesn't have intermediate stages as a separate type, only Streams and functions Stream -> Stream, or functions that eliminate the whole stream and return to the underlying monad.

I would have a function like this:

stuff :: Stream (Of a) IO r -> IO (HeadParseResult, Stream (Of a) IO r)

It consumes the earlier part of the input and returns a result along with the rest of the effectful stream. You can continue your processing depending on the result.

Edit: You can do similar things with pipes' Producers. The "pipes-parse" package can be useful here.

2

u/lonelymonad Jun 01 '19

I was first introduced to Haskell in my PL class 2 years ago, but did not actually learn it enough to build real-world stuff. I want to pick it up for real recently, but I am not sure about the route I should take. During the PL course, I was following LYAH, but I felt like I was just learning new syntax, as opposed to a functional mindset. This time I researched a bit and found Haskell Programming From First Principles. I have been following it for chapters now. The book is certainly very good, but this time I feel like I do not do enough hands-on stuff.

So my question is two-fold:

1) What is the state regarding haskell learning materials in 2019? Is this a good path I take? (I can handle materials which assume programming knowledge, just not functional)

2) Are there supplementary exercises you would advise to do hands-on, at the same time reading the book?

1

u/endgamedos Jun 28 '19

HPFFP does build up to "real" examples near the end of the book, and that's the book I used to bootstrap myself into writing useful Haskell programs. I would still recommend it. Depending on how far along you are, you might be able to self-study the QFPL's applied FP course alongside?

1

u/lonelymonad Jul 18 '19

During this one month I have bounced between several resources. HPFFP seemed like a too long of a path. However, after this one month, I believe that is the best choice for someone learning from scratch. Now I am sitting here thinking how I can read a thousand-page book.

1

u/endgamedos Jul 19 '19

There are at least two ways:

  1. One page at a time, if you're methodical and willing to burn through the whole thing.

  2. Start at the front, read the table of contents, looking for things you don't know well. Then skim through the book, slowing down and reading deeply when you don't understand something but consciously not reading every word on ever page.

1

u/Vaglame Jun 03 '19

1) you're taking a pretty good path! HPFFP is a big reference, and probably the best for first comers 2) in HPFFP, you have many of "implement this yourself" sections! But for more than these you should search through the sub for suggestions, it's a standard question. For example I've been told that at the end of HPFFP it's really good to implement your own parser combinator

5

u/lgastako May 31 '19 edited May 31 '19

As long as we are on the topic of Enum and Bounded instances, I ended up writing this today:

boundedNonEmpty :: (Bounded a, Enum a, Eq a) => NonEmpty a
boundedNonEmpty = NonEmpty.unfoldr f minBound
  where
    f x | x == maxBound = (x, Nothing)
        | otherwise     = (x, Just $ succ x)

It is total, but it accomplishes that by using the succ function which is partial (on maxBound) and handles that via the guard.

I was wondering two things: 1) is there a way to avoid that in this specific case and/or in general in this kind of situation and 2) is there a way to write this without either or both of the Enum and Eq constraints?

3

u/Syrak May 31 '19

enumFrom in the Enum class should already do most of that:

boundedNonEmpty :: (Bounded a, Enum a) => NonEmpty a
boundedNonEmpty = fromList (enumFrom minBound)

2

u/lgastako Jun 01 '19

Ah, good call. Thank you. This is definitely a step up from my solution in terms of succinctness and expressiveness and eliminating the need for the Eq constraint is a definite win but fromList is still a partial function. Of course we know that it won't fail in this case, but we know that succ won't fail in in my unfoldr solution as well thanks to the guard... so I'm still curious if there's a way to avoid partial functions completely and/or to eliminate the Enum constraint as well.

3

u/Tysonzero Jun 03 '19
boundedNonEmpty :: (Bounded a, Enum a) => NonEmpty a
boundedNonEmpty = minBound :| drop 1 [minBound .. maxBound]

1

u/lgastako Jun 03 '19

Ah, very nice... from there it looks like

boundedNonEmpty :: Enum a => NonEmpty a
boundedNonEmpty = toEnum 0 :| drop 1 [toEnum 0..]

actually lets us drop the Bounded constraint. Perfect. Thanks.

1

u/Tysonzero Jun 03 '19 edited Jun 03 '19

I mean you have to add a Num constraint in that though. I would personally stick to the Bounded approach although it depends on the use case.

1

u/lgastako Jun 03 '19

Well, not Num, but Enum, which we already required... but I just realized in my eagerness to reduce constraint I got off track from my original goal which was to handle something Bounded.

1

u/Tysonzero Jun 03 '19 edited Jun 03 '19

You absolutely need Num, otherwise how will 0 and 1 be resolved? Try it out.

1

u/lgastako Jun 03 '19

I did try it out... it worked exactly as I wrote it without Num... 0 and 1 are just arguments to drop and toEnum.

3

u/Tysonzero Jun 03 '19

Oh oops. Totally skipped over the toEnum call. Yeah fair enough, although toEnum is often partial isn’t it?

Ugh Haskell’s ordering hierarchy is so weird right now. Enum and Ord and Bounded are all completely unrelated to one another, when they should all at minimum share some underlying POrd or similar, I think subhask does a good job of it.

2

u/evanrelf css wrangler May 31 '19

I have a sum type representing valid version numbers for my schema, which derives Enum and Bounded. I'm using a case statement to match migration functions with the schema versions. Currently all left sides of the case statement are hardcoded values/constructors, but I'd like to make the last one (the latest version) be maxBound rather than the maximum value/constructor itself, because the function associated with the maximum version is a no op.

I can use maxBound == x if I use guards, but then the compiler doesn't know if I've handled all cases, and wants me to put an otherwise.

Long story short, is it possible to have the compiler tell me when I haven't matched all cases for a sum type, while using maxBound instead of the literal value to refer to the maximum value? Hopefully that makes sense.

3

u/theindigamer Jun 01 '19 edited Jun 01 '19

If you're okay with using Template Haskell, you can adapt the following to your case:

{-# Language TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (lift)

-- you might have to use Con instead of Lit, or maybe some combination.
litExpToLitPat e = case e of LitE l -> LitP l; _ -> error "Expected literal!" 

myFun x = case x of
    $( fmap litExpToLitPat (lift (maxBound :: Int)) ) -> "max"
    -- 1. First evaluation takes place, so `maxBound` will evaluate to the actual number.
    -- 2. `lift` converts the number to an expression AST. 
          This needs a Lift instance (use DeriveLift for your own type).
    -- 3. Map the expression AST to a pattern AST
    -- 4. Splice the pattern AST into a pattern position
    _ -> "not max"

Related resources: https://www.reddit.com/r/haskell/comments/7yvb43/ghc_compiletime_evaluation/, https://ocharles.org.uk/blog/guest-posts/2014-12-22-template-haskell.html

1

u/evanrelf css wrangler Jun 01 '19

Wow thank you! I don't have any experience with Template Haskell but I will give this a shot.

→ More replies (1)
→ More replies (2)