I implemented transducers in Haskell as follows:
{-# LANGUAGE RankNTypes #-}
import Prelude hiding (foldr)
import Data.Foldable
type Reducer b a = a -> b -> b
type Transducer a b = forall t. Reducer t b -> Reducer t a
class Foldable c => Collection c where
insert :: a -> c a -> c a
empty :: c a
reduce :: Collection c => Transducer a b -> c a -> c b
reduce f = foldr (f insert) empty
mapping :: (a -> b) -> Transducer a b
mapping f g x = g (f x)
Now I want to define a generic map
function. Hence I load the above code into GHCi:
Prelude> :load Transducer
[1 of 1] Compiling Main ( Transducer.hs, interpreted )
Ok, modules loaded: Main.
*Main> let map = reduce . mapping
<interactive>:3:20:
Couldn't match type ‘Reducer t0 b1 -> Reducer t0 a1’
with ‘forall t. Reducer t b -> Reducer t a’
Expected type: (a1 -> b1) -> Transducer a b
Actual type: (a1 -> b1) -> Reducer t0 b1 -> Reducer t0 a1
Relevant bindings include
map :: (a1 -> b1) -> c a -> c b (bound at <interactive>:3:5)
In the second argument of ‘(.)’, namely ‘mapping’
In the expression: reduce . mapping
*Main> let map f = reduce (mapping f)
*Main> :t map
map :: Collection c => (a -> b) -> c a -> c b
So I can't define map = reduce . mapping
. However, I can define map f = reduce (mapping f)
.
I believe that this problem is caused by the monomorphism restriction. I would really like to write map = reduce . mapping
instead of map f = reduce (mapping f)
. Hence, I have two questions:
- What's causing this problem? Is it indeed the monomorphism restriction?
- How do I fix this problem?
let map :: Collection c => (a -> b) -> c a -> c b; map f = reduce (mapping f)
still produces the same error. – Barrancamapping
is silently changed to move theforall
to the left-hand side (try:t mapping
). This is a valid (semantics-preserving) transformation, but the typechecker expects the typeTransducer a b
proper, notReducer t a -> Reducer t b
(which could be distinct types). But when you writereduce (mapping f)
, the typechecker sees the application ofmapping f
must have typeforall t. Reducer t b -> Reducer t a
, which is the correct type for an argument toreduce
. – Alvarlet map = ((.) :: (Transducer a b -> c a -> c b) -> ((a -> b) -> Transducer a b) -> (a -> b) -> (c a -> c b)) reduce mapping
works but... yuck. It's the(.)
that needs the annotation. – Legaspidata-fix
, which gives you more general case than transducers, calledF-algebras
. Transducers essentially are F-algebras, but only for list-shaped structures. – Swornf (x :: forall a. ...)
andf
has a polymorphic typeb -> ...
, thenb
is not instantiated toforall a. ...
since type variables can be instantiated during inference to monotypes, only. What happens is thata
gets instantiated to some fresh skolem constanta0
and thenf
does no longer receive a fully polymorphic value. (Or at least, this is what I understood -- I'm definitely not an expert about how exactly GHC does inference) – Legaspireduce
is declared as having typeT
, usingreduce :: T
instead of justreduce
does not tell GHC anything it does not already know. Type signatures matter when they tell GHC how to specialize a more general type: herereduce
andmapping
are used with their full generality, so no specialization is needed. Instead,(.)
is specialized. – Legaspitype
doesn't work better there: https://mcmap.net/q/1779682/-rankntypes-doesn-39-t-match-return-type – Khachaturian