Opened 11 years ago

Last modified 3 days ago

#393 new feature request

functions without implementations

Reported by: c_maeder Owned by:
Priority: normal Milestone:
Component: Compiler (Type checker) Version: None
Keywords: newcomer Cc: tomasz.zielonka@…, pho@…, admin@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description (last modified by igloo)

Allow to declare a function by only supplying its type
signature.
This feature shall enhance rapid prototyping by fixing
an interface but leaving some functions unimplemented.

Currently this can be (only) simulated by supplying
dummy implementations, like 

f :: ...
f = undefined

Since it is possible to supply dummy data types by
"data T" (not followed by "="), allowing functions
without implementations seems almost to be a logical
consequence. Surely, the compiler should emit warnings
for missing implementations.

It would be nice if such function declarations via type
signatures could be repeated at any position within a
module. 

Change History (35)

comment:1 Changed 10 years ago by igloo

  • Architecture set to Unknown
  • Description modified (diff)
  • difficulty set to Unknown
  • Milestone set to 6.8
  • Operating System set to Unknown

comment:2 Changed 9 years ago by maeder@…

  • Architecture changed from Unknown to Multiple
  • Component changed from None to Compiler (Type checker)
  • difficulty changed from Unknown to Easy (1 hr)
  • Operating System changed from Unknown to Multiple
  • Owner changed from nobody to simonpj
  • Priority changed from lowest to normal
  • severity changed from minor to normal
  • Status changed from assigned to new

comment:3 Changed 9 years ago by guest

from my POV, it's even faster to write "f=undefined" rather than signature, but i don't use sugnatures anyway :)

btw, if this feature will be implemented, it will be better to generate

f = error "Call to undefined f declared at Foo.hs:63"

comment:4 Changed 9 years ago by guest

  • Cc tomasz.zielonka@… added

comment:5 Changed 9 years ago by maeder@…

  • Cc maeder@… added

comment:6 Changed 9 years ago by guest

  • Cc changed from tomasz.zielonka@gmail.com;maeder@tzi.de to tomasz.zielonka@gmail.com,maeder@tzi.de

comment:7 Changed 9 years ago by simonmar

  • Milestone changed from 6.8 branch to _|_

No immediate plans to implement this.

comment:8 Changed 8 years ago by PHO

  • Cc pho@… added

comment:9 Changed 8 years ago by simonmar

  • Architecture changed from Multiple to Unknown/Multiple

comment:10 Changed 8 years ago by simonmar

  • Operating System changed from Multiple to Unknown/Multiple

comment:11 Changed 8 years ago by maeder

comment:12 Changed 7 years ago by simonmar

  • difficulty changed from Easy (1 hr) to Easy (less than 1 hour)

comment:13 Changed 7 years ago by simonmar

  • difficulty changed from Easy (less than 1 hour) to Moderate (less than a day)
  • Type of failure set to None/Unknown

comment:14 Changed 3 years ago by jgallag8

I am new to GHC, and I am interested in tackling this as my first task. Is there still interest in adding this feature? If so, I'll dive right in. Any pointers are appreciated.

comment:15 Changed 3 years ago by goldfire

I'd like this, for one. As someone said previously, GHC should surely emit a warning when compiling an undefined function. And, I like the suggestion above of an informative error call instead of just using undefined. Thanks for rolling up your sleeves!

comment:16 Changed 3 years ago by simonpj

I suggest

  • A language extension flag -XUndefinedFunctions or something
  • In the renamer you'll have to arrange to create a binder for a signature that lacks a corresponding binding; and give an warning (rather than an error) for such signatures.
  • I suggest that you actually add the definition f = error "Missing definition for f" (or whatever) in the desugarer. GHC generally tries NOT to mess with the source code until desugaring, so that you can always show exactly what the user wrote.

Happy to discuss when you get a bit further

Simon

comment:17 Changed 3 years ago by simonmar

For what it's worth, this is part of #5791

comment:18 Changed 3 years ago by jgallag8

Simonpj - Thanks very much for the advice. If I need more pointers, I'll let you know once I have become a little more familiar with the code base.

Simonmar - Do you suggest the two be handled together?

comment:19 Changed 3 years ago by simonmar

I think that this is another kind of "deferred error", in the same sense of -fdefer-type-errors, and there are lots of other kinds of errors that we want to defer (e.g. out-of-scope identifiers). So all I'm suggesting is that we should bear this in mind, and perhaps use a consistent naming convention for flags, e.g. this particular one could be -fdefer-missing-decl-errors, which would eventually become part of -fdefer-renamer-errors.

comment:20 Changed 2 years ago by rodlogic

Adding the source file and line number is also quite useful. So shouldn't the definition proposed by @simonpj include them? Or is there a specific reason not to?

In fact, shouldn't the exception thrown by undefined include the file & lineno so that the added definition would then become simply f = undefined, as requested in the original ticket? I have already been at a loss by an error such as *** Exception: Prelude.undefined without any references to where.

comment:21 Changed 2 years ago by rodlogic

  • Cc admin@… added

comment:22 Changed 2 years ago by maeder

  • Cc maeder@… removed

Wow, I've created this ticket 9 years ago and I got used to live without this feature, although it should be simple to implement. (I hope my old username c_maeder was deleted.)

To answer your question. Yes, source file and line number should be included (indicated by "or whatever")

Adding source file and line number to the Exception for undefined is obviously not so easy.

comment:23 Changed 2 years ago by maeder

also see #9049

comment:24 Changed 2 years ago by simonpj

Actually using this

f :: Int -> Int
f = _           -- Note "_" not "undefined"

plus compiling with -fdefer-type-errors, will give a runtime error when (and only when) f is called, giving file and line number. See the manual on typed holes.

See #9497 for making this a bit better.

Simon

comment:25 Changed 22 months ago by nomeata

  • Keywords newcomer added

comment:26 Changed 15 months ago by thomie

  • Owner simonpj deleted

comment:27 Changed 15 months ago by rodlogic

Could functions without implementation cause a bit of confusion when we have Backpack signatures? It seems that the existing alternative described in comment:24 is a great middle ground, imho.

Last edited 15 months ago by rodlogic (previous) (diff)

comment:28 Changed 6 months ago by thomie

  • Resolution changed from None to wontfix
  • Status changed from new to closed

As mentioned in comment:24, the preferred way to do define a function without definition is to use -fdefer-typed-holes:

f = _ 

Let's use our spare development cycles somewhere else, and close this ticket wontfix.

Don't hesitate to reopen if you disagree.

comment:29 Changed 6 months ago by Iceland_jack

I am in favour of this, see also #11367

comment:30 Changed 8 weeks ago by Iceland_jack

Here is a use case: Rank-N types

f :: (forall a. a -> a) -> (Int, Char) -> (Int, Char)
f = undefined

does not compile. You'd have to write

f :: (forall a. a -> a) -> (Int, Char) -> (Int, Char)
f _ = undefined

It would be cool if omitting the function body worked

f :: (forall a. a -> a) -> (Int, Char) -> (Int, Char)

-- As if the user had written
-- 
--   f :: (forall a. a -> a) -> (Int, Char) -> (Int, Char)
--   f _ _ = error "Unimplemented function ‘f’.”
-- 

I don't think functions with a polymorphic return type poses a problem:

ið :: a -> a

-- As if the user had written
-- 
--   ið :: a -> a
--   ið _ = error "Unimplemented function ‘ið’.”
-- 

It would also be nice if this worked in local definitions

g = h 'a' where
  h :: Char -> Int

-- As if the user had written
-- 
--   g = h 'a' where
--     h :: Char -> Int
--     h _ = error "Unimplemented local function ‘h’ in definition ‘g’.”
-- 

This means ‘type APIs’ presented in papers such as There is no Fork (given Fetch / ...)

getPostIds     :: Fetch [PostId]
getPostInfo    :: PostId -> Fetch PostInfo
getPostContent :: PostId -> Fetch PostContent
getPostViews   :: PostId -> Fetch Int

or A Monad for Deterministic Parallelism which uses a rank-2 type (would compile if it weren't for newtype)

newtype Par s a

runPar :: (forall s . Par s a) -> a

data IVar s a
new :: Par s (IVar s a)
get :: IVar s a -> Par s a
put :: IVar s a -> a -> Par s ()

or Parallel and Concurrent Programming in Haskell

data MVar a  -- abstract

newEmptyMVar :: IO (MVar a)
newMVar      :: a -> IO (MVar a)
takeMVar     :: MVar a -> IO a
putMVar      :: MVar a -> a -> IO ()
data Chan a

newChan    :: IO (Chan a)
readChan   :: Chan a -> IO a
writeChan  :: Chan a -> a -> IO ()

(before Applicative became superclass of Monad)

STM a -- abstract
instance Monad STM -- amongst other things

atomically :: STM a -> IO a

data TVar a -- abstract

newTVar    :: a -> STM (TVar a)
readTVar   :: TVar a -> STM a
writeTVar  :: TVar a -> a -> STM ()
retry      :: STM a
orElse     :: STM a -> STM a -> STM a
throwSTM   :: Exception e => e -> STM a
catchSTM   :: Exception e => STM a -> (e -> STM a) -> STM a

can be copied and compiled directly (with EmptyDataDecls) to be filled in later.

Last edited 8 weeks ago by Iceland_jack (previous) (diff)

comment:31 Changed 7 weeks ago by Iceland_jack

Useful when inferring types in GHCi. (ticket:2256#comment:25) I want to be able to write

>>> data A; data B a; data D a; data S a; data Z

>>> k1 :: (A -> B x) -> D (S x)
>>> k2 :: A -> D x -> B (S x)
>>> k3 :: D Z
>>> :t k1 (\a1 -> k2 a1 (k1 (\a2 -> k2 a1 k3)))
k1 (\a1 -> k2 a1 (k1 (\a2 -> k2 a1 k3))) :: D (S (S (S (S Z))))

>>> :t k1 (\a1 -> k2 a1 (k1 (\a2 -> k2 a2 k3)))
k1 (\a1 -> k2 a1 (k1 (\a2 -> k2 a2 k3))) :: D (S (S (S (S Z))))

comment:32 Changed 7 weeks ago by nomeata

  • Resolution wontfix deleted
  • Status changed from closed to new

I find Iceland_jack’s examples convincing, and this is a fun nice newcomer task, hence reopening.

comment:33 Changed 6 weeks ago by Iceland_jack

Should also work for methods. This currently fails:

class FFunctor p where
  ffmap :: (forall x. f x -> g x) -> (p f -> p g)

instance FFunctor (TList l) where
  ffmap :: (forall x. f x -> g x) -> (TList l f -> TList l g)
  ffmap = undefined 

I would like

class FFunctor p where
  ffmap :: (forall x. f x -> g x) -> (p f -> p g)

instance FFunctor (TList l) where
  ffmap :: (forall x. f x -> g x) -> (TList l f -> TList l g)

comment:34 Changed 10 days ago by Iceland_jack

Unpolished ideas: use djinn, Exference or any other program synthesiser) to supply the function body

{-# SYNTHESISE id #-}
id :: forall a. a -> a

would be as if the user had written

id :: forall a. a -> a
id x = x

Perhaps this could be implemented using plugin + annotations, I'm not familiar with the details:

{-# ANN f Exference #-}
f :: Show b => (a -> b) -> [a] -> [String]

producing

f :: Show b => (a -> b) -> [a] -> [String]
f b = fmap (\g -> show (b g))

comment:35 Changed 3 days ago by Iceland_jack

Probably not worth it, but fake definitions are generated for Commentary/PrimOps

newArray# :: Int# -> a -> State# s -> (# State# s,MutableArray# s a #)
newArray# = let x = x in x

sameMutableArray# :: MutableArray# s a -> MutableArray# s a -> Int#
sameMutableArray# = let x = x in x

nullAddr# :: Addr#
nullAddr# = let x = x in x

If this feature is enabled the bodies could be omitted and if we make holes representation-polymorphic (#12531) it's not such a reach to allow top-level unlifted bindings as well when body is omitted:

newArray# :: Int# -> a -> State# s -> (# State# s,MutableArray# s a #)

sameMutableArray# :: MutableArray# s a -> MutableArray# s a -> Int#

nullAddr# :: NullAddr#

This might avoid hacky tests mentioned in [Compiling GHC.Prim].


Note [Placeholder declarations]

We are generating fake declarations for things in GHC.Prim, just to keep GHC's renamer and typechecker happy enough for what Haddock needs. Our main plan is to say

foo :: <type>
foo = foo

We have to silence GHC's complaints about unboxed-top-level declarations with an ad-hoc fix in TcBinds: see Note [Compiling GHC.Prim] in TcBinds.

That works for all the primitive functions except tagToEnum#. If we generate the binding

tagToEnum# = tagToEnum#

GHC will complain about "tagToEnum# must appear applied to one argument". We could hack GHC to silence this complaint when compiling GHC.Prim, but it seems easier to generate

tagToEnum# = let x = x in x

We don't do this for *all* bindings because for ones with an unboxed RHS we would get other complaints (e.g.can't unify "*" with "#").

Note [Compiling GHC.Prim]

Module GHC.Prim has no source code: it is the host module for primitive, built-in functions and types. However, for Haddock-ing purposes we generate (via utils/genprimopcode) a fake source file GHC/Prim.hs, and give it to Haddock, so that it can generate documentation. It contains definitions like

nullAddr# :: NullAddr#

which would normally be rejected as a top-level unlifted binding. But we don't want to complain, because we are only "compiling" this fake mdule [sic] for documentation purposes. Hence this hacky test for gHC_PRIM in checkStrictBinds.

Note: See TracTickets for help on using tickets.