2

I have some variables a and b. I want to create the map Data.Map.fromList [("a",a),("b",b)] rapidly, by typing something like magic [a,b]. I want to do this live in GHCI, not from within a module.

I spent some time learning Template Haskell for this, but I still can't even tell if it's possible. Is it?

Jeffrey Benjamin Brown
  • 3,427
  • 2
  • 28
  • 40
  • [tag:nameof] seems to be C# specific. Tag carefully please. (by looking at the tag info page) (**edit** Be careful and don't make higher-rep users busy) – user202729 Feb 27 '18 at 03:10
  • @user202729, sorry about `nameof`. I saw that it was C#, but it also seemed like a perfect fit conceptually. Didn't realize I would be wasting someone's time; I was hoping to *save* someone's time, by giving them a precise idea of what's in the post before reading it. – Jeffrey Benjamin Brown Feb 27 '18 at 03:36
  • 1
    Someone not knowing C# may skip your question because of the tag. For that purpose you have the title. – user202729 Feb 27 '18 at 03:37

1 Answers1

5

Ok, here's a very quick and dirty implementation of magic:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import qualified Data.Map as M

magic :: [String] -> ExpQ
magic xs = [| M.fromList $(listE (map toPair xs)) |]
  where
    toPair nameStr = do
        Just name <- lookupValueName nameStr
        [| (nameStr, $(varE name)) |]

Here is using it in ghci:

$ ghci -XTemplateHaskell thmagic.hs
GHCi, version 8.2.2: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( thmagic.hs, interpreted )
Ok, one module loaded.
*Main> let x = 1 ; y = 2 ; z = "hello"
*Main> $(magic ["x", "y"])
fromList [("x",1),("y",2)]
*Main> $(magic ["x", "y", "z"])

<interactive>:3:3: error:
    • No instance for (Num [Char]) arising from a use of ‘x’
    • In the expression: x
      In the expression: ("x", x)
      In the first argument of ‘M.fromList’, namely
        ‘[("x", x), ("y", y), ("z", z)]’

Note that template haskell is enabled in ghci, and the $() splice syntax is used to tell it to actually splice the generated expression in. Also note the compile error in the case where not every list entry would have the same type.

This code is quick and dirty, but the happy path is correct. Error cases result in code that fails to splice, but not necessarily with the friendliest error messages. All in all... it's a starting point.

Edit - Version with minimal input keystrokes, as described in the comments below:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import qualified Data.Map as M

magic :: String -> ExpQ
magic names = [| M.fromList $(listE (map toPair (words names))) |]
  where
    toPair nameStr = do
        Just name <- lookupValueName nameStr
        [| (nameStr, $(varE name)) |]
Carl
  • 26,500
  • 4
  • 65
  • 86
  • It's beautiful. Is there a way to avoid writing the quotation marks in `$(magic ["x", "y"])`? – Jeffrey Benjamin Brown Feb 27 '18 at 04:21
  • @JeffreyBenjaminBrown it could be modified to take a list of `Name` values instead. Let me try that modification out. – Carl Feb 27 '18 at 04:23
  • My goal is to minimize the amount of typing involved. If it's easier for magic to take a single string containing the terms separated by spaces, I think that would be character-minimal too. – Jeffrey Benjamin Brown Feb 27 '18 at 04:26
  • 1
    @JeffreyBenjaminBrown Ah. Yes, that's shorter than taking names as arguments. Each `name` still needs a single quote, and the list still needs to be comma-separated on top of that. Using a single space-separated string is just a matter of a quick change to the type and using the `words` function. – Carl Feb 27 '18 at 04:30