Ejecutar una función n veces, donde n se conoce en el tiempo de compilación -- haskell campo con template-meta-programming camp codereview Relacionados El problema

Execute a function n times, where n is known at compile time


17
vote

problema

Español

Motivación

en esta pregunta , un usuario preguntó Si es posible enlinear la siguiente función:

  -- simplified version {-# INLINE nTimes #-} nTimes :: Int -> (a -> a) -> a -> a     nTimes 0 f x = x nTimes n f x = nTimes (n-1) f (f x)   

Desafortunadamente, la respuesta parece no, ya que GHC ve una función recursiva y se rinde. Incluso si usa una constante de tiempo de compilación, por ejemplo. nTimes 1 (+1) x , no terminas con x + 1 , pero con nTimes 1 (+1) x .

Si bien, por supuesto, está bien rechazar enlinar si se desconoce el número de bucles, también es una molestia si es conocida.

Código

Como puede ver en la pregunta anterior, he propuesto la siguiente solución:

  {-# LANGUAGE TemplateHaskell #-} module Times where  import Control.Monad (when) import Language.Haskell.TH  -- > item under review begins here  nTimesTH :: Int -> Q Exp nTimesTH n = do   f <- newName "f"   x <- newName "x"    when (n <= 0)    (reportWarning "nTimesTH: argument non-positive")   when (n >= 1000) (reportWarning "nTimesTH: argument large, can lead to memory exhaustion")    let go k | k <= 0 = VarE x       go k          = AppE (VarE f) (go (k - 1))   return $ LamE [VarP f,VarP x] (go n)  -- < item under review ends here   

Esto debería, para cualquier n , cree una función con los patrones llamada f y x y aplique f a x con nTimes 1 (+1) x0 nTimes 1 (+1) x1 Tiempos:

  nTimes 1 (+1) x2  

Puedo verificar que la función creada tenga el tipo correcto:

  nTimes 1 (+1) x3  

Para todos mis conocimientos, 99887776655443314 funciona exactamente como se espera.

Dado que esta es mi primera vez que se incursiona con la plantilla Haskell, ¿esto sigue las mejores prácticas? Además, estoy usando nTimes 1 (+1) x5 , 99887766555443316 y así sucesivamente. nTimes 1 (+1) x7 también proporciona algunos combinadores, para que uno pueda escribir

  nTimes 1 (+1) x8  

es esta preferencia personal, o es nTimes 1 (+1) x9 preferido? Por lo que puedo ver, los combinadores de expresión utilizan la implementación canónica, por ejemplo. x + 10 , 99887766555443321 y así sucesivamente.

Pruebas

Solo para completar las pruebas QuickCheck. Esas no son realmente parte de la revisión, pero aquí antes de la integridad:

  x + 12  
  x + 13  
Original en ingles

Motivation

In this question, a user asked whether it is possible to inline the following function:

-- simplified version {-# INLINE nTimes #-} nTimes :: Int -> (a -> a) -> a -> a     nTimes 0 f x = x nTimes n f x = nTimes (n-1) f (f x) 

Unfortunately, the answer seems no, since GHC sees a recursive function and gives up. Even if you use a compile-time constant, e.g. nTimes 1 (+1) x, you don't end up with x + 1, but with nTimes 1 (+1) x.

While it's of course fine to refuse inlining if the number of loops is unknown, it's also a hassle if it is known.

Code

As you can see in the question above, I've proposed the following solution:

{-# LANGUAGE TemplateHaskell #-} module Times where  import Control.Monad (when) import Language.Haskell.TH  -- > item under review begins here  nTimesTH :: Int -> Q Exp nTimesTH n = do   f <- newName "f"   x <- newName "x"    when (n <= 0)    (reportWarning "nTimesTH: argument non-positive")   when (n >= 1000) (reportWarning "nTimesTH: argument large, can lead to memory exhaustion")    let go k | k <= 0 = VarE x       go k          = AppE (VarE f) (go (k - 1))   return $ LamE [VarP f,VarP x] (go n)  -- < item under review ends here 

This should, for any n, create a function with patterns named f and x, and apply f to x with AppE n times:

$(nTimesTH 0) = \f x -> x $(nTimesTH 1) = \f x -> f x $(nTimesTH 2) = \f x -> f (f x) $(nTimesTH 3) = \f x -> f (f (f x)) 

I can verify that the created function has the correct type:

$ ghci -XTemplateHaskell Times.sh  ghci> :t $(nTimesTH 0) $(nTimesTH 0) :: r -> r1 -> r1  ghci> :t $(nTimesTH 1) $(nTimesTH 1) :: (r1 -> r) -> r1 -> r  ghci> :t $(nTimesTH 2) $(nTimesTH 2) :: (r -> r) -> r -> r  ghci> :t $(nTimesTH 3) $(nTimesTH 3) :: (r -> r) -> r -> r 

To all my knowledge, nTimesTH works exactly as expected.

Given that this is my first time dabbling with Template Haskell, does this follow best practices? Also, I'm using VarE, AppE and so on. Language.Haskell.TH also provides some combinators, so that one can write

  let go k | k <= 0 = varE x       go k          = appE (varE f) (go (k - 1))   lamE (map varP [f,x]) (go n) 

Is this just personal preference, or is lamE preferred? As far as I can see, the expression combinators use the canonic implementation, e.g. varE = return . VarE, appE f x = liftA2 AppE f x and so on.

Tests

Just for completeness the QuickCheck tests. Those aren't really part of the review, but here fore completeness:

module Times where import Test.QuickCheck  -- .. rest of module as above  genTestSingle :: Int -> Q Exp genTestSingle n = do   f <- newName "gTSf"   x <- newName "gTSx"    lamE [varP f, varP x] $      appsE [ [| (==) |], appsE [nTimesTH n,           varE f, varE x]                       , appsE [ [| nTimesFoldr n |], varE f, varE x]]  genAllTest :: Int -> Q Exp genAllTest n = do   f <- newName "gATf"   x <- newName "gATv"    lamE [varP f, varP x] $ doE $ (flip map) [1..n] $ \i ->     noBindS $ appE [| quickCheck |] $ appsE [genTestSingle i, varE f, varE x] 
module Main where  main = $(genAllTest 100) sin 40 
     
 
 

Lista de respuestas

4
 
vote
vote
La mejor respuesta
 

Tu implementación está bien. Pero es un poco irónico: en su respuesta, usted comienza con ", puede usar mejores funciones" , aun así reescribe if($request->input('emailText') != $request->input('confirmEmailText')) { return redirect()->route('register'); } 6 VIA 99887776655443327 . Si este fue un módulo real, probablemente querría exportar ambas funciones if($request->input('emailText') != $request->input('confirmEmailText')) { return redirect()->route('register'); } 8 y if($request->input('emailText') != $request->input('confirmEmailText')) { return redirect()->route('register'); } 9 e implementar el primero a través de este último:

  email0  

Esto elimina la necesidad de duplicación de código, y cualquier optimización futura que encuentre para email1 se aplica automáticamente en email2 , aunque eso solo importa durante el tiempo de compilación. Después de todo email3 se evalúa completamente durante la compilación.

Aparte de eso, moví las advertencias en la parte superior de la función, ya que esto trae los nombres email44 y 99887766555443335 más cerca de su uso real. También reemplacé los constructores de datos con sus funciones respectivas.

Tenga en cuenta que para un módulo real aún desea agregar documentación:

  email6  
 

Your implementation is fine. But it is a little bit ironic: In your SO answer, you start of with "you can use better functions", yet you rewrite nTimes via go. If this was a real module, you would probably want to export both functions nTimesTH and nTimes, and implement the former via the latter:

{-# LANGUAGE TemplateHaskell #-} module Times (nTimes, nTimesTH) where  import Control.Monad (when) import Language.Haskell.TH  -- example implementation nTimes :: Int -> (a -> a) -> a -> a nTimes n f x = iterate f x !! max 0 n  nTimesTH :: Int -> Q Exp nTimesTH n = do   when (n <= 0)    (reportWarning "nTimesTH: argument non-positive")   when (n >= 1000) (reportWarning "nTimesTH: argument large, can lead to memory exhaustion")    f <- newName "f"   x <- newName "x"    lamE (map varP [f,x]) $ nTimes n (appE (varE f)) (varE x) 

This removes the need for code duplication, and any future optimization you find for nTimes gets automatically applied in nTimesTH, although that only matter during the compile time. After all $(nTimesTH n) is completely evaluated during compilation.

Other than that, I moved the warnings on top of the function, since this brings the names f and x closer to their actual use. I also replaced the data constructors with their respective functions.

Note that for a real module you still want to add documentation:

-- | @'nTimes' n f x@ applies @f@ @n@ times over @x@.  -- It does not apply @f@ if @n@ is negative. In this case @x@ is returned. -- -- prop> nTimes n f x == iterate f x !! max 0 nTimes :: Int -> (a -> a) -> a -> a  -- | @'nTimesTH' n@ returns a function that applies the first  --   argument @n@ times to the second argument.  --   @n@ must be known at compile time. Needs @-XTemplateHaskell@. -- -- prop> $(nTimesTH n) f x == nTimes n f x nTimesTH :: Int -> Q Exp 
 
 

Relacionados problema

20  Construyendo una buena biblioteca de plantillas C ++ 11 - Tiempo compilado enumeró la matriz enumulada [cerrada]  ( Building a good c11 template library compile time checked enum array ) 
cerrado. Esta pregunta es off-topic . Actualmente no está aceptando respuestas. ¿Quieres ...

13  ID de tipo único en C ++  ( Unique type id in c ) 
Necesito tener una identificación única para cualquier tipo en C ++ para un tipo de variante. ¿Este código es confiable para obtener la identificación? No me ...

11  Selección Clasificación de una lista de tipos (Tiempo de compilación)  ( Selection sorting a type list compile time ) 
Esta pregunta tiene la plantilla 998877666554433665544336 que ordena la plantilla variada de una tupla usando un comparador (cualquier plantilla que toma do...

16  Implementación muy básica de la tupla  ( Very basic tuple implementation ) 
Me han metido la metaprogramación y las plantillas variadas en C ++, y me fui con esta implementación muy primitiva de una tupla: activityKey4 siendo la...

3  ¿Podría este esquema de ejecución diferido ser más simple?  ( Could this deferred execution scheme be any simpler ) 
He creado este esquema para preservar las llamadas de la función hasta que estén disponibles todos los argumentos. La stub_op8 Las clases se reemplazarán co...

11  Clon de variante de impulso  ( Clone of boost variant ) 
Como parte del aprendizaje C ++, con especial énfasis en C ++ 11, quería implementar el equivalente a la variante de Boost (ubicada AQUÍ ). Mi código está di...

6  Clase de matriz multidimensional simple en C ++ 11  ( Simple multi dimensional array class in c11 ) 
La nueva versión del código se puede revisar en Clase de matriz multidimensional simple en C ++ 11 - Seguimiento . El siguiente código implementa una clas...

2  C ++ Socket Part-2 A (Utilidades)  ( C socket part 2 a utilities ) 
Este es el código que utilizo para construir dinámicamente los mensajes de error. Utilidad.H #ifndef THORSANVIL_SOCKET_UTILITY_H #define THORSANVIL_SOCKE...

6  Biblioteca de listas de metaprogramas de pequeña plantilla  ( Small template metaprogramming list library ) 
Entonces, tiempo para explorar las profundidades de asustadizo de la metaprogramación de plantillas (bueno, aterrador para mí, de todos modos). Esta bibliot...

5  C ++ Elija entre funciones de implementación de tiempo de ejecución y tiempo de compilación  ( C choose between runtime and compile time implementation functions ) 
Escribí una pequeña función de envoltura para elegir entre Tiempo de ejecución (STD) 99887766555443318 y una versión de ConsexPR, como continuación de la cl...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos