Opened 12 years ago

Last modified 6 weeks ago

#393 new feature request

functions without implementations

Reported by: c_maeder Owned by: Iceland_jack
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 (38)

comment:1 Changed 10 years ago by igloo

Architecture: Unknown
Description: modified (diff)
difficulty: Unknown
Milestone: 6.8
Operating System: Unknown

comment:2 Changed 10 years ago by maeder@…

Architecture: UnknownMultiple
Component: NoneCompiler (Type checker)
difficulty: UnknownEasy (1 hr)
Operating System: UnknownMultiple
Owner: changed from nobody to simonpj
Priority: lowestnormal
severity: minornormal
Status: assignednew

comment:3 Changed 10 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 10 years ago by guest

Cc: tomasz.zielonka@… added

comment:5 Changed 10 years ago by maeder@…

Cc: maeder@… added

comment:6 Changed 10 years ago by guest

Cc: tomasz.zielonka@gmail.com;maeder@tzi.detomasz.zielonka@gmail.com,maeder@tzi.de

comment:7 Changed 9 years ago by simonmar

Milestone: 6.8 branch_|_

No immediate plans to implement this.

comment:8 Changed 9 years ago by PHO

Cc: pho@… added

comment:9 Changed 8 years ago by simonmar

Architecture: MultipleUnknown/Multiple

comment:10 Changed 8 years ago by simonmar

Operating System: MultipleUnknown/Multiple

comment:11 Changed 8 years ago by maeder

comment:12 Changed 7 years ago by simonmar

difficulty: Easy (1 hr)Easy (less than 1 hour)

comment:13 Changed 7 years ago by simonmar

difficulty: Easy (less than 1 hour)Moderate (less than a day)
Type of failure: 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 3 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 3 years ago by rodlogic

Cc: admin@… added

comment:22 Changed 3 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 3 years ago by maeder

also see #9049

comment:24 Changed 3 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 2 years ago by nomeata

Keywords: newcomer added

comment:26 Changed 21 months ago by thomie

Owner: simonpj deleted

comment:27 Changed 21 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 21 months ago by rodlogic (previous) (diff)

comment:28 Changed 12 months ago by thomie

Resolution: Nonewontfix
Status: newclosed

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 12 months ago by Iceland_jack

I am in favour of this, see also #11367

comment:30 Changed 8 months 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:

 :: 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 months ago by Iceland_jack (previous) (diff)

comment:31 Changed 8 months 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 8 months ago by nomeata

Resolution: wontfix
Status: closednew

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

comment:33 Changed 8 months 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 6 months 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 6 months 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.

comment:36 Changed 6 months ago by Iceland_jack

Owner: set to Iceland_jack

Code from discussion thread comment (+ some foralls) would be valid

The only way to write such a function is to use methods in the Profunctor interface of which there is only 1. Thus, if we have any value i :: Iso s a we know it must be of the form

i = dimap fwd bck
  where
    fwd :: s -> a
    bck :: a -> s

[...]

A good "canonical form" for l might be

l :: Lens s a
l = dimap fwd bck . first
  where
    fwd :: s -> Tuple a x
    bck :: Tuple a x -> s

[...]

p :: Prism s a
p = dimap fwd bck . left
  where
    fwd :: s -> Either a x
    bck :: Either a x -> s

Edit: From #12589, could be written

hcpure :: proxy c -> (forall a. c a => f a) -> h f xs

instead of hcpure _ _ = _, I always forget to add wildcards for higher ranked arguments. Same with HCofree

type f ~> g = forall a. f a -> g a

leftAdjunct :: (c f, Functor f) => (f ~> g) -> (f ~> HCofree c g)
Last edited 4 months ago by Iceland_jack (previous) (diff)

comment:37 Changed 5 months ago by Iceland_jack

example:

You could do:

data LeafNode a = LeafNode { leafMeta :: LeafMetaData, things :: [a] }

And then put the constraint in your functions instead:

myFun :: MyTypeClass a => LeafNode a -> b
myFun = undefined
myFun :: MyTypeClass a => LeafNode a -> b

would be valid syntax.

comment:38 Changed 6 weeks ago by Iceland_jack

Used in code attached to tickets, module of stubs

Last edited 6 weeks ago by Iceland_jack (previous) (diff)
Note: See TracTickets for help on using tickets.