简体   繁体   中英

Haskell Polymorphic Recursion with Composed Maps causes Infinite Type Error

What is the right way to create a function that can dynamically create composed map?

This results in an error (also happens with fmap):

createComposedMaps list = accumulate list map
    where 
        accumulate (x:xs) m = accumulate xs (m.map)
        accumulate []     m = m

The list is irrelevant, it's just there to count the number of compositions.

The error I get back is about "cannot construct infinite type":

Occurs check: cannot construct the infinite type: a2 ~ [a2]
Expected type: (a2 -> b1) -> a2 -> b1
  Actual type: (a2 -> b1) -> [a2] -> [b1]
Relevant bindings include
  m :: (a2 -> b1) -> c (bound at dimensional_filter.hs:166:27)
  accumulate :: [t1] -> ((a2 -> b1) -> c) -> (a2 -> b1) -> c
    (bound at dimensional_filter.hs:166:9)
In the second argument of ‘(.)’, namely ‘map’
In the second argument of ‘accumulate’, namely ‘(m . map)’

Occurs check: cannot construct the infinite type: b1 ~ [b1]
Expected type: (a2 -> b1) -> a2 -> b1
  Actual type: (a2 -> b1) -> [a2] -> [b1]
Relevant bindings include
  m :: (a2 -> b1) -> c (bound at dimensional_filter.hs:166:27)
  accumulate :: [t1] -> ((a2 -> b1) -> c) -> (a2 -> b1) -> c
    (bound at dimensional_filter.hs:166:9)
In the second argument of ‘(.)’, namely ‘map’
In the second argument of ‘accumulate’, namely ‘(m . map)’

The goal is to create a dynamically composed mapping function that I can later use. While other semigroups can be appended together (lists, numbers...). Functions seems to be much harder.

Would appreciate an example showing fmap and/or map.

I found this Library function to compose a function with itself n times

But I need to use each intermediate composition, not just the final composition. And some examples still gives me infinite type error.

Turns out the problem may involve polymorphic recursion. As each recursive application of the accumulate function changes the map type.

This is a classic job for dependent types, which means that we compute return types from the values of arguments. Here we'd like to express that the nesting of the resulting list depends on a numeric input (in your case, you used the length of a list parameter as the numeric input, but it's probably better to just use numbers where numbers are needed).

Unfortunately Haskell doesn't yet have proper support for dependent typing, and existing workaround solutions involve some boilerplate and complications. Idris is a language with Haskell-like syntax and full dependent types, so I can illustrate the idea in Idris with greater clarity:

-- unary naturals from the Idris Prelude :
-- data Nat = Z | S Nat

-- compose a function n times (which can also be a type constructor!)
-- for example, iterN 3 List Int = List (List (List Int))
iterN : Nat -> (a -> a) -> a -> a
iterN Z     f a = a
iterN (S k) f a = f (iterN k f a)

mapN : (n : Nat) -> (a -> b) -> iterN n List a -> iterN n List b
mapN Z     f as = f as
mapN (S k) f as = map (mapN k f) as

-- usage:
> mapN 3 (+10) [[[0]]]
[[[10]]]
> mapN 0 id 10
10

This is the complete Idris solution. In Haskell, we can't have values or functions in types, and the only way to make the above work is to create type-level versions of functions as type families and value-level versions of types as singletons, effectively writing twice as many definitions as would be ideal. The singletons library seeks to remove the bulk of the boilerplate through Template Haskell and clever machinery. Here's a singletons-based solution:

{-# LANGUAGE DataKinds, TypeFamilies #-}

import Data.Singletons -- package: singletons
import Data.Nat        -- package: singleton-nats (by me)

type family IterN n f a where
  IterN Z     f a = a
  IterN (S n) f a = f (IterN n f a)

mapN :: Sing n -> (a -> b) -> IterN n [] a -> IterN n [] b  
mapN SZ     f a  = f a
mapN (SS n) f as = map (mapN n f) as

-- usage:
> mapN (sing :: SLit 3) (+10) [[[0]]]
[[[10]]]

The good news though is that there's ongoing research and development to add dependent types to GHC and we can expect improvements in the next few years.


Alternatively, one might be tempted to use type classes to infer the amount of nestedness in the return type. This is fairly horrible, because we have to distinguish [a] and non-list types, which at the very least requires OverlappingInstances , but in practice it works acceptably with the even worse IncoherentInstances , because we'd also like to resolve polymorphic types depending on the local context.

{-# LANGUAGE
  UndecidableInstances, IncoherentInstances, MultiParamTypeClasses,
  FlexibleInstances, TypeFamilies #-}

class MapN a b as bs where
  mapN :: (a -> b) -> as -> bs

instance (as ~ a, bs ~ b) => MapN a b as bs where
  mapN = id

instance (MapN a b as bs, bs' ~ [bs]) => MapN a b [as] bs' where
  mapN f as = map (mapN f) as

-- usage:
> mapN (+1) 0
1
> mapN (+10) [[[0]]]
[[[10]]]

-- note though that without enough context `mapN`'s type is nonsense:
> :t mapN (+0)
mapN (+0) :: Num b => b -> b

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM