Создание функции с помощью шаблона Haskell

можно ли определить функцию с помощью шаблона Haskell? Например

convertStringToValue :: String -> Int
convertStringToValue "three" = 3
convertStringToValue "four" = 4

у меня тоже Map [Char] Int.

fromList [("five",5),("six",6)]

как я могу добавить функции

convertStringToValue "six" = 6
convertStringToValue "five" = 5

во время компиляции с использованием шаблона Haskell и Map? Кажется довольно глупым использовать шаблон Haskell для этой цели, но я хотел бы знать, тем не менее.

2 ответов


вы можете сделать это, используя два файла:

файл" maker":Maker.hs:

module Maker where

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH

maker items = do
    x <- newName "x"
    lamE [varP x] (caseE (varE x) (map (\(a,b) -> match (litP $ stringL a) (normalB $ litE $ integerL b) []) items))

и главный файл:Main.hs:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Maker

function = $(maker [("five",5),("six",6)])

в этом случае function будет типа [Char] -> Int и будет составлен так:

\x -> case x of
    "five" -> 5
    "six" -> 6

это так, как если бы вы написали:

function = \x -> case x of
    "five" -> 5
    "six" -> 6

себя. Очевидно, что это не окупится за два или три случая, но, как вы написали в спросите себя, когда вы хотите использовать тысячи случаев или список элементов, генерируемых пониманием списка, это начинает окупаться.

создание шаблона Haskell самостоятельно

этот раздел призван кратко описать, как написать шаблон Haskell самостоятельно. Этот учебник не "полное введение...": для этого есть другие методы.

чтобы написать шаблон Haskell, вы можете сначала попробовать несколько выражений, а затем попробуйте обобщение их с помощью map, fold, etc.

проанализируйте дерево AST

сначала вам лучше взглянуть на то, как Хаскелл будет анализировать определенное выражение. Вы можете сделать это с помощью runQ и скобки [| ... |] с ... выражение, которое вы хотите проанализировать. Так, например:

$ ghci -XTemplateHaskell
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m Language.Haskell.TH
Prelude Language.Haskell.TH> runQ [| \x -> case x of "five" -> 5; "six" -> 6 |]
Loading package array-0.4.0.1 ... linking ... done.
Loading package deepseq-1.3.0.1 ... linking ... done.
Loading package containers-0.5.0.0 ... linking ... done.
Loading package pretty-1.1.1.0 ... linking ... done.
Loading package template-haskell ... linking ... done.
LamE [VarP x_0] (CaseE (VarE x_0) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

АСТ таков:

LamE [VarP x_0] (CaseE (VarE x_0) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

Итак, теперь у нас есть производные the Аннотация Синтаксическое дерево (AST) из этого выражения. Подсказка состоит в том, чтобы сделать выражения достаточно общими. Например, используйте несколько случаев в case блок, так как использование одного случая не говорит вам, как вы должны добавить второй к вашему выражению. Теперь мы хотим сами создать такое абстрактное синтаксическое дерево.

создать имя переменной

первый аспект-это переменные, как VarP x_0 и VarE x_0. Вы не можете просто копировать-вставить них. Здесь x_0 - это имя. Чтобы убедиться, что вы не используете имя, которое уже существует, вы можете использовать newName. Теперь вы можете построить следующее выражение для его полной репликации:

maker = do
    x <- newName "x"
    return $ LamE [VarP x] (CaseE (VarE x) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

обобщить функции

очевидно, мы не заинтересованы в построении фиксированного абстрактного синтаксического дерева, иначе мы могли бы написать его сами. Теперь дело в том, что вы вводите одну или несколько переменных и рассуждаете об этих переменных. Для каждого кортежа!--27-->, etc. мы вводим Match о себе:

Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) []

теперь мы можем легко обобщить с \(a,b):

\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []

а затем используйте map для перебора всех элементов:

map (\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []) items

С items список кортежей, для которых мы хотим создать случаях. Теперь мы закончили:

maker items = do
    x <- newName "x"
    return $ LamE [VarP x] (CaseE (VarE x) (map (\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []) items))

теперь вы можете просто опустить return потому что библиотека имеет строчные варианты для всех этих элементов. Кроме того, вы можете попробовать "очистка" код немного (например,(NormalB (LitE (IntegerL b))) to (NormalB $ LitE $ IntegerL b), etc.); например, используя hlint.

maker items = do
    x <- newName "x"
    lamE [varP x] (caseE (varE x) (map (\(a,b) -> match (litP $ stringL a) (normalB $ litE $ integerL b) []) items))

на создатель здесь есть какая-то функция, которая делает/создает функцию.

осторожно бесконечные списки

имейте в виду, что компилятор будет оценивать, что он находится между долларовыми скобками $(). Если бы вы, например, использовали бесконечное список:

function = $(maker [(show i,i)|i<-[1..]]) -- Don't do this!

это будет продолжать выделять память для абстрактного дерева синтаксиса и в конечном итоге закончится память. Компилятор делает не разверните AST во время выполнения.


да

import Language.Haskell.TH

generateDict :: String -> [(String, Int)] -> Q [Dec]
generateDict fname sns = do
    let clauses = map clause sns
    return $ [FunD (mkName fname) clauses]
        where clause (s,n) =
                Clause [LitP . IntegerL $ toInteger  n]
                       (NormalB . LitE $ StringL s )
                       []

а то

generateDict "myDict" $ zip (words "One Two Tree Four") [1..]

myDict 1 -- => "One"