7

Hello how can you enforce GHC type for functions such as Data.Text.read or the =~ operator from Text.Regex.Posix when composing methods?

example:
a=["1.22","3.33","5.55"]

Without point free:
b= map (\x-> read x ::Double) a

How to enforce a type for read with point free notation ?

b=map read::Double a or
b= map (read . f1 .f2 .f3... . fn )::Double a (when composing methods)
where f1 , f2 ...fn are methods

Or better how do you specify the read type when it belongs in a chain of methods ,BUT not at the end of the chain ! :
b=map (f2 . read . f1 ) a

Bercovici Adrian
  • 8,794
  • 17
  • 73
  • 152

2 Answers2

11

The best way in modern Haskell is with a type application.

Prelude> :set -XTypeApplications 
Prelude> map (read @Double) ["1.22","3.33","5.55"]
[1.22,3.33,5.55]
Prelude> map (read @Int) ["1.22","3.33","5.55"]
[*** Exception: Prelude.read: no parse

This works because read has the signature

read :: ∀ a . Read a => String -> a

and therefore read @Double specialises a ~ Double and thus

read @Double :: String -> Double
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • FWIW I'm not a fan of this extension, so at least one person disagrees this is "The best way..." ;) – jberryman Sep 11 '18 at 16:00
  • 1
    You wouldn't care to explain why you don't like it? – leftaroundabout Sep 11 '18 at 16:20
  • sure, sorry: 1) suddenly class constraints stop being a set (the ordering matters; do I now have to worry about re-ordering constraints in my own libraries (imagine my editor automatically alphabetizes them) to stop breakage for users who choose to use type application? Might it even cause bugs that pass the typechecker? I'm honestly not sure). 2) Reuse of `@` for some totally unrelated meaning 3) applying a function to a type doesn't really make sense (I think this is a GHC internal implementation detail (dictionary passing) leaking upwards). It might be I have wrong ideas about it though – jberryman Sep 11 '18 at 21:14
  • With 1) you definitely have a point. 2) and 3) don't seem that critical to me. – leftaroundabout Sep 11 '18 at 21:59
  • @jberryman: #1 is at least no worse than other languages like C++ where you always write explicit quantifiers and their order matters. Order will be important for Dependent Haskell too, since you can’t freely move pi types around like `forall`s. Explicit quantifiers also have the advantage of enabling `ScopedTypeVariables` without changing a signature. Honestly, it seems a bit silly in retrospect that Haskell imposed lexical restrictions to differentiate type constructors & variables just to avoid `forall` (or sigils like OCaml’s `'`)—`@` would’ve been a perfect ASCII substitute for `∀`, too! – Jon Purdy Sep 12 '18 at 09:53
  • @JonPurdy I'm not sure we'd settle for “at least no worse than C++” in Haskell... I tend to agree, it would have been fine if we'd always had mandatory explicit quantors, but I also agree with Brandon that it was nice that _order_ of quantors used to not matter. But don't know if it would even be possible to retain that in Dependent Haskell. – leftaroundabout Sep 12 '18 at 10:43
  • @leftaroundabout: Haha, fair, I only used C++ as an example since it’s the only one off the top of my head where you regularly partially apply type arguments. Explicit quantifiers are the norm in every imperative language with generics. – Jon Purdy Sep 12 '18 at 20:29
3

read has type String -> a, so read x has type a. Just like forcing read x to have type Double instead of a with read x :: Double, you can force read to have type String -> Double instead:

b = map (read :: String -> Double) a
chepner
  • 497,756
  • 71
  • 530
  • 681
  • So if i have a chain of composed methods `map ( f1 . read . f2 ) a` i could say : `map ( f2 . read::t0->t1 . f0 ) a ` where `f0::t0` and `read::t1` ? – Bercovici Adrian Sep 11 '18 at 16:18
  • 1
    Probably not, because `read . f0` doesn't really make sense if `f0` isn't a function that returns a `String`. You can't make wholesale changes to the type of `read`; you can only specialize its type. – chepner Sep 11 '18 at 16:25
  • 1
    @BercoviciAdrian With appropriate parentheses, that is syntactically correct: `map (f2 . (read :: t0 -> t1) . f0) a`. That's likely to be a type error except in some very special circumstances where the context constrains `t0` and `t1` a lot, though. – Daniel Wagner Sep 11 '18 at 18:26