HNHacker News
TopNewBestAskShowJobs

lkitching

150 karma · joined May 22, 2016

submissionscomments
lkitching··on The End of Dynamic Languages
But the IDE's ability to tell you depends on the language. IDE features for dynamic languages are worse because the static structure of the code is not available, so usually devolves into basic string matching. In the case of C#, the language and IDE support are developed together, and new features like async, linq etc are not considered done until the tooling (IDE, debugger etc.) supports them. It's unfair to dismiss the tooling for such languages when they were explicitly designed with them in mind.
lkitching··on The End of Dynamic Languages
Who is using gradual typing in a clojure codebase? I've never seen any usages of core.typed in the wild. Using schema is more common although its future seems uncertain in the wake of spec. In my experience schemas require just as much effort as specifying types without the benefit of automatic checking or the usual IDE integrations like navigation or autocompletion. As a result they're rarely used widely enough to be useful.
lkitching··on Fed Says Millennials Are Just Like Their Parents, Only Poorer
The term 'Xennial' was coined just for people in your demographic: https://en.wikipedia.org/wiki/Xennials
lkitching··on ASP.NET Core 3.0 will only run on .NET Core
You haven't needed Visual Studio to build .net projects for a long time, project files are MSBuild files.
lkitching··on Robust Clojure: The best way to handle nil
One problem with using or is if the key is associated with false, then it will be ignored in favour of the default. Keywords support a second argument which is the default to use on not-found so you can just write:

    (-> {:a 1 :b 2}
        (:c 5.5)
        inc)
lkitching··on Tuples goodness in .NET 4.7.1
I don't see how the tuple is better in this case since the first thing you need to do is check the flag to see if the value is valid. The tuple version then needs to be split over two lines instead of a single line like

     if(TryGetSomething(3, out MyType outvar)) { ... }
lkitching··on Reflecting on F# in 2017
F# has syntactic support for monads called computation expressions (https://docs.microsoft.com/en-us/dotnet/fsharp/language-refe...). There are a couple of these included in the standard library (seq and async). However the language is impure so you are free to put side-effects anywhere you want without encoding it in your types.
lkitching··on Dependent Types for F#
I'm not asking specifically about vectors, that's just a common example of a dependent type. To take another example from the documentation, can you express a type like

    type digits = RegexString (Regex "^[0-9]+$")
where all values of type digits are strings containing only digits? Then

    let s1 : digits = "1234"
would typecheck but

    let s2 : digits = "abc"
would not? That would be very surprising given the F# type system.
lkitching··on Dependent Types for F#
How do you express the type `Vect 3 Int` using this library? F# doesn't support it and all the example types appear to depend on other types, not on values like 3.
lkitching··on Dependent Types for F#
Does this library actually allow you to express dependent types or is it just for runtime validation? I can't see any examples in the documentation of a type depending on a value like the common

    let v : Vect 3 Int = [1,2,3]
example.

There aren't many types shown in the examples but it looks like

    let myNonEmptyIntSetOpt = [] |> Set.ofList |> NonEmptyIntSet.TryCreate
would just return None at runtime rather than a compilation error?
lkitching··on Write tests. Not too many. Mostly integration
> its about the fact you don't control an external dependency during testing

If your code is structured using small static functions, you don't have any dependencies in the first place, just arguments you are passed and transform. You will probably create interfaces for external services you depend on, but you can avoid needing to mock them if you express the transform directly.

> This is very assuming

I'm not assuming anything, since I wrote the static method and I also decided to call it, presumably for the result it calculates. Your argument appears to be that static methods could contain bad code but that applies to all code you depend on.

lkitching··on Write tests. Not too many. Mostly integration
I disagree that delegates and higher-order function are 'crap' or in any way more complicated than introducing interfaces that are injected though a centralised container. You could just as easily turn that argument around and say mocking and an overuse of interfaces come from your resistance to using small static methods. In C# Linq is almost entirely based on static methods and delegates and that is not harder to test as a result.

Static methods usually don't rely on any hidden state at all. The example originally given was for a graph operation which could just take the input graph as an argument and return the result. When your code is composed of small independent functions you don't need mocking and substitution at all. In my experience most uses of mocks come from functions that do too much in the first place.

lkitching··on Write tests. Not too many. Mostly integration
> that method is now dependent on the static class and you don't have any control over it.

I don't see how you have any less control over it than any other code you wrote. If you don't want it to write log statements, then don't do that. Most static methods are small and pure so don't need to write log statements anyway.

> Now your testing will dirty whatever database you're using, as well as run 10x slower.

I've never used a logging framework that didn't allow you to configure where log statements were written, or give you control over the logging threshold for individual classes. However if your method is writing logs then presumably there is a reason, which is just as useful in the tests. If you mock it out then you're testing against different code to the one you will actually run against.

> If you start using a custom delegate solution, then your code is not consistent with everything else that uses DI.

Passing functions as arguments directly is 'DI', just without the need to configure that through an external container. Reducing the amount of interfaces (often with a single implementation) and external configuration makes navigating the code easier.

lkitching··on Write tests. Not too many. Mostly integration
I don't see why TDD requires ruling out static methods and insisting on hiding everything behind an interface. Static methods are straightforward to test, certainly more than a class with multiple dependencies which need to be mocked. Usually the complaint is about coupling when calling static methods but these can be wrapped in a delegate if required.
lkitching··on Uncle Bob and Silver Bullets
By the same token, tests can only verify certain properties of a function. How could you test that a function is free from side-effects? How could you test it never returns null?
lkitching··on Uncle Bob and Silver Bullets
The problem is that without knowing the type in question you don't know the values you're testing with cover all of the possible cases. When interfacing with an external dynamically-typed function this requires attempting to reverse-engineer the types from the definition (and recursively for all the functions it calls).
lkitching··on A Preview of C# 8 [video]
It's implicit arguments, not implicit conversions that enable type classes in Scala, although the class is usually represented by a trait e.g.

    trait Show[T] { def show(v : T): String }
    implicit object ShowInt extends Show[Int] {
	def show(i: Int) = i.toString
    }
    def showLine[T](v : T)(implicit show: Show[T]): Unit = {
        println(show.show(v))
    }
lkitching··on How the .NET Runtime Loads a Type
> Microsoft could have added some unified Unit type but they did not, which makes this irrelevant.

It's not irrelevant - you're using Task/Task<T> as an example of the non-generic base type/generic subtype pattern being necessary, but it isn't.

IEnumerable<T> extending IEnumerable is a consequence of C#1 not supporting generics at all, and the non-generic version would not be necessary if it had. I was just using it as a common example of the boilerplate the pattern incurs.

lkitching··on How the .NET Runtime Loads a Type
> You are thinking that using reflection and casting at runtime is not a sensible thing because it is not optimal

No I'm not thinking that, I'm saying that implementing a non-generic base interface like

    public interface IValidator {
        bool Validate(object obj);
    }
is pointless because you'll have to do the same type checking/casting in Validate you would have to do in a wrapper which implemented IValidator<object>. Requiring a base interface is worse because you'll have to implement the same boilerplate implementation of Validate(object) in every implementing class - see IEnumerable/IEnumerable<T> as an example.

> Obviously this library will need to do run-time casting of types

You've linked to a clojure library which is dynamically typed so I don't see how this is relevant. It's not obvious to me why a C# version would require casting.

Task/Task<T> does follow the pattern but they could have simply introduced a Unit type in the BCL and replaced the non-generic Task type with Task<Unit> instead.

> To run all of these validators against a given class you have three (un)reasonable choices:

You only need to write one wrapper class which encapsulates the cast you'd have to do anyway in a non-generic IValidator interface:

    public class Wrapper<T, TBase> : IValidator<TBase>
    {
        private readonly IValidator<T> inner;
        public Wrapper(IValidator<T> v)
        {
            this.inner = v;
        }
        public bool Validate(TBase b)
        {
            if(b is T)
            {
                return this.inner.Validate((T)(object)b);
            }
            else return false;
        }
    }
lkitching··on Things that Idris improves things over Haskell
The Haskell version of printf does not statically analyse the format string and will raise an exception if the provided arguments are invalid e.g.

    Prelude Text.Printf> printf "%d\n" "invalid"
    *** Exception: printf: bad formatting char 'd'
The behaviour of F#'s printfn is hard-coded in the compiler and could not be written in F# iteself.
lkitching··on How the .NET Runtime Loads a Type
What sensible thing could you do with a list containing a ThingValidator and OtherThingValidator? Assuming there's no relationship between Thing and OtherThing you're forced to use runtime checking to find the validator you need for something typed as object, so at that point you have to use casting or reflection, which you'd also need to do when implementing a non-generic interface.
lkitching··on Ask HN: Why isn't F# more popular?
Neither callbacks nor dependency injection are functional concepts, and implicit loops are pervasive in Ruby (and non-linear control flow in general). OO in Ruby is quite a long way from structs + function pointers.
lkitching··on Idris 1.0
This isn't the same thing - Idris encodes the state machine in the type system, C++ enforces it dynamically.
lkitching··on Addendum to “Curl is C”
All bugs "shouldn't happen in the first place", the use of types is to reduce the surface area for where errors can be introduced. If you read some value from an external source and subsequently use measure types then you've isolated the scope for the introduction of errors to the read function instead of every location in the program that uses the value.
lkitching··on Beware of Developers Who Do Negative Work
So am I - storing multiple values in a cell violates first normal form.
lkitching··on Beware of Developers Who Do Negative Work
I wouldn't say someone who's never heard of first normal form has 'good CS fundamentals'.
lkitching··on Swift and the Legacy of Functional Programming
I understood from your comment that the imperative version "avoids building the name size list of persons" that you thought the declaritive version would construct an intermediate List[Person] the same size as the source list of names. Most modern languages (e.g. C#, F#, Clojure, Rust) implement map and filter using lazy sequences rather than eagerly constructing intermediate collections (admittedly I don't have much experience with the languages you list).

The use laziness will avoid the need to construct a large intermediate collection of Person instances. Obviously you'll need to iterate the entire source collection to find all valid Persons but the same applies to the imperative version. The lazy version composes better however and avoids extra work if some downstream caller only requires the first n valid Persons.

lkitching··on Swift and the Legacy of Functional Programming
Most languages implement map and filter in terms of lazy sequences so they would not allocate an intermediate list (Scala is an exception but I believe you can request laziness).
lkitching··on Google Interview University – Plan for studying to become a Google engineer
You don't need hash codes to be unique for items in the table so there doesn't need to be any relation between the size of the hash and the size of the table. The time taken by the hash function depends on the size of the keys not the size of the table.
lkitching··on The Future of Standard ML (2013) [pdf]
By 'pass around', I mean pass around instances, not definitions. You could define your own Eq interface in F#

  type IEq<'a> =
    abstract member eq: 'a -> 'a -> bool
but you then need to explicitly pass instances of this interface around into every method that requires it e.g.

  let allEq (s: 'a seq) (eq: IEq<'a>) = ...
whereas the haskell version would receive the Eq instance for the input type implicitly.

Typeclass instances are globally unique for each type, which cannot be enforced with the interface solution. If you have an ordered map type, you can be sure the Ord instance used for insertion is the same that is used for retrieval. With the interface approach, clients cannot know which instance to use since there could be multiple implementations. The Haskell approach has its own problems, such as the proliferation of newtype wrappers to manage the dispatch mechanism.

Interfaces also hide the representation of one of their arguments (the receiver) whereas typeclasses are just a dictionary of functions.

← PreviousPage 3 of 4Next →